File manager - Edit - /var/www/payraty/helpdesk/public/storage/branding_media/images/src.tar
Back
WaitTimeCalculator.php 0000755 00000010272 00000000000 0010760 0 ustar 00 <?php namespace Laravel\Horizon; use Illuminate\Contracts\Queue\Factory as QueueFactory; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\MetricsRepository; use Laravel\Horizon\Contracts\SupervisorRepository; class WaitTimeCalculator { /** * The queue factory implementation. * * @var \Illuminate\Contracts\Queue\Factory */ public $queue; /** * The supervisor repository implementation. * * @var \Laravel\Horizon\Contracts\SupervisorRepository */ public $supervisors; /** * The metrics repository implementation. * * @var \Laravel\Horizon\Contracts\MetricsRepository */ public $metrics; /** * Create a new calculator instance. * * @param \Illuminate\Contracts\Queue\Factory $queue * @param \Laravel\Horizon\Contracts\SupervisorRepository $supervisors * @param \Laravel\Horizon\Contracts\MetricsRepository $metrics * @return void */ public function __construct(QueueFactory $queue, SupervisorRepository $supervisors, MetricsRepository $metrics) { $this->queue = $queue; $this->metrics = $metrics; $this->supervisors = $supervisors; } /** * Calculate the time to clear a given queue in seconds. * * @param string $queue * @return float */ public function calculateFor($queue) { return array_values($this->calculate($queue))[0] ?? 0; } /** * Calculate the time to clear per queue in seconds. * * @param string|null $queue * @return array */ public function calculate($queue = null) { $queues = $this->queueNames( $supervisors = collect($this->supervisors->all()), $queue ); return $queues->mapWithKeys(function ($queue) use ($supervisors) { $totalProcesses = $this->totalProcessesFor($supervisors, $queue); [$connection, $queueName] = explode(':', $queue, 2); return [$queue => $this->calculateTimeToClear($connection, $queueName, $totalProcesses)]; })->sort()->reverse()->all(); } /** * Get all of the queue names. * * @param \Illuminate\Support\Collection $supervisors * @param string|null $queue * @return \Illuminate\Support\Collection */ protected function queueNames($supervisors, $queue = null) { $queues = $supervisors->map(function ($supervisor) { return array_keys($supervisor->processes); })->collapse()->unique()->values(); return $queue ? $queues->intersect([$queue]) : $queues; } /** * Get the total process count for a given queue. * * @param \Illuminate\Support\Collection $allSupervisors * @param string $queue * @return int */ protected function totalProcessesFor($allSupervisors, $queue) { return $allSupervisors->sum(function ($supervisor) use ($queue) { return $supervisor->processes[$queue] ?? 0; }); } /** * Calculate the time to clear for the given queue in seconds distributed over the given amount of processes. * * @param string $connection * @param string $queue * @param int $totalProcesses * @return int */ public function calculateTimeToClear($connection, $queue, $totalProcesses) { $timeToClear = ! Str::contains($queue, ',') ? $this->timeToClearFor($connection, $queue) : collect(explode(',', $queue))->sum(function ($queueName) use ($connection) { return $this->timeToClearFor($connection, $queueName); }); return $totalProcesses === 0 ? round($timeToClear / 1000) : round(($timeToClear / $totalProcesses) / 1000); } /** * Get the total time to clear (in milliseconds) for a given queue. * * @param string $connection * @param string $queue * @return float */ protected function timeToClearFor($connection, $queue) { $size = $this->queue->connection($connection)->readyNow($queue); return $size * $this->metrics->runtimeForQueue($queue); } } Notifications/LongWaitDetected.php 0000755 00000011115 00000000000 0013217 0 ustar 00 <?php namespace Laravel\Horizon\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\NexmoMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock; use Illuminate\Notifications\Slack\SlackMessage as ChannelIdSlackMessage; use Illuminate\Support\Str; use Laravel\Horizon\Horizon; class LongWaitDetected extends Notification { use Queueable; /** * The queue connection name. * * @var string */ public $longWaitConnection; /** * The queue name. * * @var string */ public $longWaitQueue; /** * The wait time in seconds. * * @var int */ public $seconds; /** * Create a new notification instance. * * @param string $connection * @param string $queue * @param int $seconds * @return void */ public function __construct($connection, $queue, $seconds) { $this->longWaitQueue = $queue; $this->seconds = $seconds; $this->longWaitConnection = $connection; } /** * Get the notification's delivery channels. * * @param mixed $notifiable * @return array */ public function via($notifiable) { return array_filter([ Horizon::$slackWebhookUrl ? 'slack' : null, Horizon::$smsNumber ? 'nexmo' : null, Horizon::$email ? 'mail' : null, ]); } /** * Get the mail representation of the notification. * * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) { return (new MailMessage) ->error() ->subject(config('app.name').': Long Queue Wait Detected') ->greeting('Oh no! Something needs your attention.') ->line(sprintf( 'The "%s" queue on the "%s" connection has a wait time of %s seconds.', $this->longWaitQueue, $this->longWaitConnection, $this->seconds )); } /** * Get the Slack representation of the notification. * * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\SlackMessage */ public function toSlack($notifiable) { $fromName = 'Laravel Horizon'; $title = 'Long Wait Detected'; $text = 'Oh no! Something needs your attention.'; $imageUrl = 'https://laravel.com/assets/img/horizon-48px.png'; $content = sprintf( '[%s] The "%s" queue on the "%s" connection has a wait time of %s seconds.', config('app.name'), $this->longWaitQueue, $this->longWaitConnection, $this->seconds ); if (class_exists('\Illuminate\Notifications\Slack\SlackMessage') && class_exists('\Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock') && ! (is_string(Horizon::$slackWebhookUrl) && Str::startsWith(Horizon::$slackWebhookUrl, ['http://', 'https://']))) { return (new ChannelIdSlackMessage) ->username($fromName) ->image($imageUrl) ->text($text) ->headerBlock($title) ->sectionBlock(function (SectionBlock $block) use ($content): void { // @phpstan-ignore-line $block->text($content); }); } return (new SlackMessage) // @phpstan-ignore-line ->from($fromName) ->to(Horizon::$slackChannel) ->image($imageUrl) ->error() ->content($text) ->attachment(function ($attachment) use ($title, $content) { $attachment->title($title) ->content($content); }); } /** * Get the Nexmo / SMS representation of the notification. * * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\NexmoMessage */ public function toNexmo($notifiable) { return (new NexmoMessage)->content(sprintf( // @phpstan-ignore-line '[%s] The "%s" queue on the "%s" connection has a wait time of %s seconds.', config('app.name'), $this->longWaitQueue, $this->longWaitConnection, $this->seconds )); } /** * The unique signature of the notification. * * @return string */ public function signature() { return md5($this->longWaitConnection.$this->longWaitQueue); } } SystemProcessCounter.php 0000755 00000001167 00000000000 0011411 0 ustar 00 <?php namespace Laravel\Horizon; use Symfony\Component\Process\Process; class SystemProcessCounter { /** * The base command to search for. * * @var string */ public static $command = 'horizon:work'; /** * Get the number of Horizon workers for a given supervisor. * * @param string $name * @return int */ public function get($name) { $process = Process::fromShellCommandline('exec ps aux | grep '.static::$command, null, ['COLUMNS' => '2000']); $process->run(); return substr_count($process->getOutput(), 'supervisor='.$name); } } Console/SupervisorStatusCommand.php 0000755 00000002725 00000000000 0013515 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\MasterSupervisor; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:supervisor-status')] class SupervisorStatusCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:supervisor-status {name : The name of the supervisor}'; /** * The console command description. * * @var string */ protected $description = 'Show the status for a given supervisor'; /** * Execute the console command. * * @param \Laravel\Horizon\Contracts\SupervisorRepository $supervisors * @return void */ public function handle(SupervisorRepository $supervisors) { $name = $this->argument('name'); $supervisorStatus = optional(collect($supervisors->all())->first(function ($supervisor) use ($name) { return Str::startsWith($supervisor->name, MasterSupervisor::basename()) && Str::endsWith($supervisor->name, $name); }))->status; if (is_null($supervisorStatus)) { $this->components->error('Unable to find a supervisor with this name.'); return 1; } $this->components->info("{$name} is {$supervisorStatus}"); } } Console/PublishCommand.php 0000755 00000001355 00000000000 0011534 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:publish')] class PublishCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:publish'; /** * The console command description. * * @var string */ protected $description = 'Publish all of the Horizon resources'; /** * Execute the console command. * * @return void */ public function handle() { $this->components->warn('Horizon no longer publishes its assets. You may stop calling the `horizon:publish` command.'); } } Console/TimeoutCommand.php 0000755 00000002333 00000000000 0011551 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Laravel\Horizon\MasterSupervisor; use Laravel\Horizon\ProvisioningPlan; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:timeout')] class TimeoutCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:timeout {environment=production : The environment name}'; /** * The console command description. * * @var string */ protected $description = 'Get the maximum timeout for the given environment'; /** * Indicates whether the command should be shown in the Artisan command list. * * @var bool */ protected $hidden = true; /** * Execute the console command. * * @return void */ public function handle() { $plan = ProvisioningPlan::get(MasterSupervisor::name())->plan; $environment = $this->argument('environment'); $timeout = collect($plan[$this->argument('environment')] ?? [])->max('timeout') ?? 60; $this->components->info('Maximum timeout for '.$environment.' environment: '.$timeout.' seconds.'); } } Console/TerminateCommand.php 0000755 00000004511 00000000000 0012053 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Illuminate\Contracts\Cache\Factory as CacheFactory; use Illuminate\Support\Arr; use Illuminate\Support\InteractsWithTime; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\MasterSupervisor; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:terminate')] class TerminateCommand extends Command { use InteractsWithTime; /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:terminate {--wait : Wait for all workers to terminate}'; /** * The console command description. * * @var string */ protected $description = 'Terminate the master supervisor so it can be restarted'; /** * Execute the console command. * * @param \Illuminate\Contracts\Cache\Factory $cache * @param \Laravel\Horizon\Contracts\MasterSupervisorRepository $masters * @return void */ public function handle(CacheFactory $cache, MasterSupervisorRepository $masters) { if (config('horizon.fast_termination')) { $cache->forever( 'horizon:terminate:wait', $this->option('wait') ); } $masters = collect($masters->all())->filter(function ($master) { return Str::startsWith($master->name, MasterSupervisor::basename()); })->all(); collect(Arr::pluck($masters, 'pid')) ->whenNotEmpty(fn () => $this->components->info('Sending TERM signal to processes.')) ->whenEmpty(fn () => $this->components->info('No processes to terminate.')) ->each(function ($processId) { $result = true; $this->components->task("Process: $processId", function () use ($processId, &$result) { return $result = posix_kill($processId, SIGTERM); }); if (! $result) { $this->components->error("Failed to kill process: {$processId} (".posix_strerror(posix_get_last_error()).')'); } })->whenNotEmpty(fn () => $this->output->writeln('')); $this->laravel['cache']->forever('illuminate:queue:restart', $this->currentTime()); } } Console/ClearCommand.php 0000755 00000004034 00000000000 0011151 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Illuminate\Console\ConfirmableTrait; use Illuminate\Queue\QueueManager; use Illuminate\Support\Arr; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\RedisQueue; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:clear')] class ClearCommand extends Command { use ConfirmableTrait; /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:clear {--queue= : The name of the queue to clear} {--force : Force the operation to run when in production}'; /** * The console command description. * * @var string */ protected $description = 'Delete all of the jobs from the specified queue'; /** * Execute the console command. * * @return int|null */ public function handle(JobRepository $jobRepository, QueueManager $manager) { if (! $this->confirmToProceed()) { return 1; } if (! method_exists(RedisQueue::class, 'clear')) { $this->components->error('Clearing queues is not supported on this version of Laravel.'); return 1; } $connection = Arr::first($this->laravel['config']->get('horizon.defaults'))['connection'] ?? 'redis'; if (method_exists($jobRepository, 'purge')) { $jobRepository->purge($queue = $this->getQueue($connection)); } $count = $manager->connection($connection)->clear($queue); $this->components->info('Cleared '.$count.' jobs from the ['.$queue.'] queue.'); return 0; } /** * Get the queue name to clear. * * @param string $connection * @return string */ protected function getQueue($connection) { return $this->option('queue') ?: $this->laravel['config']->get( "queue.connections.{$connection}.queue", 'default' ); } } Console/PurgeCommand.php 0000755 00000010244 00000000000 0011205 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\Contracts\ProcessRepository; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\MasterSupervisor; use Laravel\Horizon\ProcessInspector; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:purge')] class PurgeCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:purge {--signal=SIGTERM : The signal to send to the rogue processes}'; /** * The console command description. * * @var string */ protected $description = 'Terminate any rogue Horizon processes'; /** * @var \Laravel\Horizon\Contracts\SupervisorRepository */ private $supervisors; /** * @var \Laravel\Horizon\Contracts\ProcessRepository */ private $processes; /** * @var \Laravel\Horizon\ProcessInspector */ private $inspector; /** * Create a new command instance. * * @param \Laravel\Horizon\Contracts\SupervisorRepository $supervisors * @param \Laravel\Horizon\Contracts\ProcessRepository $processes * @param \Laravel\Horizon\ProcessInspector $inspector * @return void */ public function __construct( SupervisorRepository $supervisors, ProcessRepository $processes, ProcessInspector $inspector ) { parent::__construct(); $this->supervisors = $supervisors; $this->processes = $processes; $this->inspector = $inspector; } /** * Execute the console command. * * @param \Laravel\Horizon\Contracts\MasterSupervisorRepository $masters * @return void */ public function handle(MasterSupervisorRepository $masters) { $signal = is_numeric($signal = $this->option('signal')) ? $signal : constant($signal); foreach ($masters->names() as $master) { if (Str::startsWith($master, MasterSupervisor::basename())) { $this->purge($master, $signal); } } } /** * Purge any orphan processes. * * @param string $master * @param int $signal * @return void */ public function purge($master, $signal = SIGTERM) { $this->recordOrphans($master, $signal); $expired = $this->processes->orphanedFor( $master, $this->supervisors->longestActiveTimeout() ); collect($expired) ->whenNotEmpty(fn () => $this->components->info('Sending TERM signal to expired processes of ['.$master.']')) ->each(function ($processId) use ($master, $signal) { $this->components->task("Process: $processId", function () use ($processId, $signal) { exec("kill -s {$signal} {$processId}"); }); $this->processes->forgetOrphans($master, [$processId]); })->whenNotEmpty(fn () => $this->output->writeln('')); } /** * Record the orphaned Horizon processes. * * @param string $master * @param int $signal * @return void */ protected function recordOrphans($master, $signal) { $this->processes->orphaned( $master, $orphans = $this->inspector->orphaned() ); collect($orphans) ->whenNotEmpty(fn () => $this->components->info('Sending TERM signal to orphaned processes of ['.$master.']')) ->each(function ($processId) use ($signal) { $result = true; $this->components->task("Process: $processId", function () use ($processId, $signal, &$result) { return $result = posix_kill($processId, $signal); }); if (! $result) { $this->components->error("Failed to kill orphan process: {$processId} (".posix_strerror(posix_get_last_error()).')'); } })->whenNotEmpty(fn () => $this->output->writeln('')); } } Console/ForgetFailedCommand.php 0000755 00000004070 00000000000 0012456 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Laravel\Horizon\Contracts\JobRepository; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:forget')] class ForgetFailedCommand extends Command { /** * The console command signature. * * @var string */ protected $signature = 'horizon:forget {id? : The ID of the failed job} {--all : Delete all failed jobs}'; /** * The console command description. * * @var string */ protected $description = 'Delete a failed queue job'; /** * Execute the console command. * * @return int|null */ public function handle(JobRepository $repository) { if ($this->option('all')) { $totalFailedCount = $repository->totalFailed(); do { $failedJobs = collect($repository->getFailed()); $failedJobs->pluck('id')->each(function ($failedId) use ($repository): void { $repository->deleteFailed($failedId); if ($this->laravel['queue.failer']->forget($failedId)) { $this->components->info('Failed job (id): '.$failedId.' deleted successfully!'); } }); } while ($repository->totalFailed() !== 0 && $failedJobs->isNotEmpty()); if ($totalFailedCount) { $this->components->info($totalFailedCount.' failed jobs deleted successfully!'); } else { $this->components->info('No failed jobs detected.'); } return; } if (! $this->argument('id')) { $this->components->error('No failed job ID provided.'); } $repository->deleteFailed($this->argument('id')); if ($this->laravel['queue.failer']->forget($this->argument('id'))) { $this->components->info('Failed job deleted successfully!'); } else { $this->components->error('No failed job matches the given ID.'); return 1; } } } Console/WorkCommand.php 0000755 00000004205 00000000000 0011045 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Queue\Console\WorkCommand as BaseWorkCommand; class WorkCommand extends BaseWorkCommand { /** * The console command name. * * @var string */ protected $signature = 'horizon:work {connection? : The name of the queue connection to work} {--name=default : The name of the worker} {--delay=0 : The number of seconds to delay failed jobs (Deprecated)} {--backoff=0 : The number of seconds to wait before retrying a job that encountered an uncaught exception} {--max-jobs=0 : The number of jobs to process before stopping} {--max-time=0 : The maximum number of seconds the worker should run} {--daemon : Run the worker in daemon mode (Deprecated)} {--force : Force the worker to run even in maintenance mode} {--memory=128 : The memory limit in megabytes} {--once : Only process the next job on the queue} {--stop-when-empty : Stop when the queue is empty} {--queue= : The names of the queues to work} {--sleep=3 : Number of seconds to sleep when no job is available} {--rest=0 : Number of seconds to rest between jobs} {--supervisor= : The name of the supervisor the worker belongs to} {--timeout=60 : The number of seconds a child process can run} {--tries=0 : Number of times to attempt a job before logging it failed}'; /** * Indicates whether the command should be shown in the Artisan command list. * * @var bool */ protected $hidden = true; /** * Execute the console command. * * @return void */ public function handle() { if (config('horizon.fast_termination')) { ignore_user_abort(true); } parent::handle(); } } Console/ContinueCommand.php 0000755 00000003454 00000000000 0011714 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Illuminate\Support\Arr; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\MasterSupervisor; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:continue')] class ContinueCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:continue'; /** * The console command description. * * @var string */ protected $description = 'Instruct the master supervisor to continue processing jobs'; /** * Execute the console command. * * @param \Laravel\Horizon\Contracts\MasterSupervisorRepository $masters * @return void */ public function handle(MasterSupervisorRepository $masters) { $masters = collect($masters->all())->filter(function ($master) { return Str::startsWith($master->name, MasterSupervisor::basename()); })->all(); collect(Arr::pluck($masters, 'pid')) ->whenNotEmpty(fn () => $this->components->info('Sending CONT signal to processes.')) ->whenEmpty(fn () => $this->components->info('No processes to continue.')) ->each(function ($processId) { $result = true; $this->components->task("Process: $processId", function () use ($processId, &$result) { return $result = posix_kill($processId, SIGCONT); }); if (! $result) { $this->components->error("Failed to kill process: {$processId} (".posix_strerror(posix_get_last_error()).')'); } })->whenNotEmpty(fn () => $this->output->writeln('')); } } Console/SupervisorsCommand.php 0000755 00000003017 00000000000 0012467 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Laravel\Horizon\Contracts\SupervisorRepository; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:supervisors')] class SupervisorsCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:supervisors'; /** * The console command description. * * @var string */ protected $description = 'List all of the supervisors'; /** * Execute the console command. * * @param \Laravel\Horizon\Contracts\SupervisorRepository $supervisors * @return void */ public function handle(SupervisorRepository $supervisors) { $supervisors = $supervisors->all(); if (empty($supervisors)) { return $this->components->info('No supervisors are running.'); } $this->output->writeln(''); $this->table([ 'Name', 'PID', 'Status', 'Workers', 'Balancing', ], collect($supervisors)->map(function ($supervisor) { return [ $supervisor->name, $supervisor->pid, $supervisor->status, collect($supervisor->processes)->map(function ($count, $queue) { return $queue.' ('.$count.')'; })->implode(', '), $supervisor->options['balance'], ]; })->all()); $this->output->writeln(''); } } Console/SupervisorCommand.php 0000755 00000013132 00000000000 0012303 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Exception; use Illuminate\Console\Command; use Laravel\Horizon\SupervisorFactory; use Laravel\Horizon\SupervisorOptions; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:supervisor')] class SupervisorCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:supervisor {name : The name of supervisor} {connection : The name of the connection to work} {--balance= : The balancing strategy the supervisor should apply} {--delay=0 : The number of seconds to delay failed jobs (Deprecated)} {--backoff=0 : The number of seconds to wait before retrying a job that encountered an uncaught exception} {--max-jobs=0 : The number of jobs to process before stopping a child process} {--max-time=0 : The maximum number of seconds a child process should run} {--force : Force the worker to run even in maintenance mode} {--max-processes=1 : The maximum number of total workers to start} {--min-processes=1 : The minimum number of workers to assign per queue} {--memory=128 : The memory limit in megabytes} {--nice=0 : The process priority} {--paused : Start the supervisor in a paused state} {--queue= : The names of the queues to work} {--sleep=3 : Number of seconds to sleep when no job is available} {--timeout=60 : The number of seconds a child process can run} {--tries=0 : Number of times to attempt a job before logging it failed} {--auto-scaling-strategy=time : If supervisor should scale by jobs or time to complete} {--balance-cooldown=3 : The number of seconds to wait in between auto-scaling attempts} {--balance-max-shift=1 : The maximum number of processes to increase or decrease per one scaling} {--workers-name=default : The name that should be assigned to the workers} {--parent-id=0 : The parent process ID} {--rest=0 : Number of seconds to rest between jobs}'; /** * The console command description. * * @var string */ protected $description = 'Start a new supervisor'; /** * Indicates whether the command should be shown in the Artisan command list. * * @var bool */ protected $hidden = true; /** * Execute the console command. * * @param \Laravel\Horizon\SupervisorFactory $factory * @return int|null */ public function handle(SupervisorFactory $factory) { $supervisor = $factory->make( $this->supervisorOptions() ); try { $supervisor->ensureNoDuplicateSupervisors(); } catch (Exception $e) { $this->components->error('A supervisor with this name is already running.'); return 13; } $this->start($supervisor); } /** * Start the given supervisor. * * @param \Laravel\Horizon\Supervisor $supervisor * @return void */ protected function start($supervisor) { if ($supervisor->options->nice) { proc_nice($supervisor->options->nice); } $supervisor->handleOutputUsing(function ($type, $line) { $this->output->write($line); }); $supervisor->working = ! $this->option('paused'); $supervisor->scale(max( 0, $this->option('max-processes') - $supervisor->totalSystemProcessCount() )); $supervisor->monitor(); } /** * Get the supervisor options. * * @return \Laravel\Horizon\SupervisorOptions */ protected function supervisorOptions() { $backoff = $this->hasOption('backoff') ? $this->option('backoff') : $this->option('delay'); $balance = $this->option('balance'); $autoScalingStrategy = $balance === 'auto' ? $this->option('auto-scaling-strategy') : null; return new SupervisorOptions( $this->argument('name'), $this->argument('connection'), $this->getQueue($this->argument('connection')), $this->option('workers-name'), $balance, $backoff, $this->option('max-time'), $this->option('max-jobs'), $this->option('max-processes'), $this->option('min-processes'), $this->option('memory'), $this->option('timeout'), $this->option('sleep'), $this->option('tries'), $this->option('force'), $this->option('nice'), $this->option('balance-cooldown'), $this->option('balance-max-shift'), $this->option('parent-id'), $this->option('rest'), $autoScalingStrategy ); } /** * Get the queue name for the worker. * * @param string $connection * @return string */ protected function getQueue($connection) { return $this->option('queue') ?: $this->laravel['config']->get( "queue.connections.{$connection}.queue", 'default' ); } } Console/PauseSupervisorCommand.php 0000755 00000003146 00000000000 0013305 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\MasterSupervisor; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:pause-supervisor')] class PauseSupervisorCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:pause-supervisor {name : The name of the supervisor to pause}'; /** * The console command description. * * @var string */ protected $description = 'Pause a supervisor'; /** * Execute the console command. * * @param \Laravel\Horizon\Contracts\SupervisorRepository $supervisors * @return void */ public function handle(SupervisorRepository $supervisors) { $processId = optional(collect($supervisors->all())->first(function ($supervisor) { return Str::startsWith($supervisor->name, MasterSupervisor::basename()) && Str::endsWith($supervisor->name, $this->argument('name')); }))->pid; if (is_null($processId)) { $this->components->error('Failed to find a supervisor with this name'); return 1; } $this->components->info("Sending USR2 signal to process: {$processId}"); if (! posix_kill($processId, SIGUSR2)) { $this->components->error("Failed to send USR2 signal to process: {$processId} (".posix_strerror(posix_get_last_error()).')'); } } } Console/ContinueSupervisorCommand.php 0000755 00000003221 00000000000 0014006 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\MasterSupervisor; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:continue-supervisor')] class ContinueSupervisorCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:continue-supervisor {name : The name of the supervisor to resume}'; /** * The console command description. * * @var string */ protected $description = 'Instruct the supervisor to continue processing jobs'; /** * Execute the console command. * * @param \Laravel\Horizon\Contracts\SupervisorRepository $supervisors * @return void */ public function handle(SupervisorRepository $supervisors) { $processId = optional(collect($supervisors->all())->first(function ($supervisor) { return Str::startsWith($supervisor->name, MasterSupervisor::basename()) && Str::endsWith($supervisor->name, $this->argument('name')); }))->pid; if (is_null($processId)) { $this->components->error('Failed to find a supervisor with this name'); return 1; } $this->components->info("Sending CONT signal to process: {$processId}"); if (! posix_kill($processId, SIGCONT)) { $this->components->error("Failed to send CONT signal to process: {$processId} (".posix_strerror(posix_get_last_error()).')'); } } } Console/InstallCommand.php 0000755 00000004753 00000000000 0011541 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:install')] class InstallCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:install'; /** * The console command description. * * @var string */ protected $description = 'Install all of the Horizon resources'; /** * Execute the console command. * * @return void */ public function handle() { $this->components->info('Installing Horizon resources.'); collect([ 'Service Provider' => fn () => $this->callSilent('vendor:publish', ['--tag' => 'horizon-provider']) == 0, 'Configuration' => fn () => $this->callSilent('vendor:publish', ['--tag' => 'horizon-config']) == 0, ])->each(fn ($task, $description) => $this->components->task($description, $task)); $this->registerHorizonServiceProvider(); $this->components->info('Horizon scaffolding installed successfully.'); } /** * Register the Horizon service provider in the application configuration file. * * @return void */ protected function registerHorizonServiceProvider() { $namespace = Str::replaceLast('\\', '', $this->laravel->getNamespace()); if (file_exists($this->laravel->bootstrapPath('providers.php'))) { ServiceProvider::addProviderToBootstrapFile("{$namespace}\\Providers\\HorizonServiceProvider"); } else { $appConfig = file_get_contents(config_path('app.php')); if (Str::contains($appConfig, $namespace.'\\Providers\\HorizonServiceProvider::class')) { return; } file_put_contents(config_path('app.php'), str_replace( "{$namespace}\\Providers\EventServiceProvider::class,".PHP_EOL, "{$namespace}\\Providers\EventServiceProvider::class,".PHP_EOL." {$namespace}\Providers\HorizonServiceProvider::class,".PHP_EOL, $appConfig )); } file_put_contents(app_path('Providers/HorizonServiceProvider.php'), str_replace( "namespace App\Providers;", "namespace {$namespace}\Providers;", file_get_contents(app_path('Providers/HorizonServiceProvider.php')) )); } } Console/StatusCommand.php 0000755 00000002373 00000000000 0011412 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:status')] class StatusCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:status'; /** * The console command description. * * @var string */ protected $description = 'Get the current status of Horizon'; /** * Execute the console command. * * @param \Laravel\Horizon\Contracts\MasterSupervisorRepository $masterSupervisorRepository * @return int */ public function handle(MasterSupervisorRepository $masterSupervisorRepository) { if (! $masters = $masterSupervisorRepository->all()) { $this->components->error('Horizon is inactive.'); return 2; } if (collect($masters)->contains(function ($master) { return $master->status === 'paused'; })) { $this->components->warn('Horizon is paused.'); return 1; } $this->components->info('Horizon is running.'); return 0; } } Console/SnapshotCommand.php 0000755 00000002060 00000000000 0011717 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Laravel\Horizon\Contracts\MetricsRepository; use Laravel\Horizon\Lock; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:snapshot')] class SnapshotCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:snapshot'; /** * The console command description. * * @var string */ protected $description = 'Store a snapshot of the queue metrics'; /** * Execute the console command. * * @param \Laravel\Horizon\Lock $lock * @param \Laravel\Horizon\Contracts\MetricsRepository $metrics * @return void */ public function handle(Lock $lock, MetricsRepository $metrics) { if ($lock->get('metrics:snapshot', config('horizon.metrics.snapshot_lock', 300) - 30)) { $metrics->snapshot(); $this->components->info('Metrics snapshot stored successfully.'); } } } Console/PauseCommand.php 0000755 00000003401 00000000000 0011175 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Illuminate\Support\Arr; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\MasterSupervisor; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:pause')] class PauseCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:pause'; /** * The console command description. * * @var string */ protected $description = 'Pause the master supervisor'; /** * Execute the console command. * * @param \Laravel\Horizon\Contracts\MasterSupervisorRepository $masters * @return void */ public function handle(MasterSupervisorRepository $masters) { $masters = collect($masters->all())->filter(function ($master) { return Str::startsWith($master->name, MasterSupervisor::basename()); })->all(); collect(Arr::pluck($masters, 'pid')) ->whenNotEmpty(fn () => $this->components->info('Sending USR2 signal to processes.')) ->whenEmpty(fn () => $this->components->info('No processes to pause.')) ->each(function ($processId) { $result = true; $this->components->task("Process: $processId", function () use ($processId, &$result) { return $result = posix_kill($processId, SIGUSR2); }); if (! $result) { $this->components->error("Failed to kill process: {$processId} (".posix_strerror(posix_get_last_error()).')'); } })->whenNotEmpty(fn () => $this->output->writeln('')); } } Console/ListCommand.php 0000755 00000002721 00000000000 0011037 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:list')] class ListCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:list'; /** * The console command description. * * @var string */ protected $description = 'List all of the deployed machines'; /** * Execute the console command. * * @param \Laravel\Horizon\Contracts\MasterSupervisorRepository $masters * @return void */ public function handle(MasterSupervisorRepository $masters) { $masters = $masters->all(); if (empty($masters)) { return $this->components->info('No machines are running.'); } $this->output->writeln(''); $this->table([ 'Name', 'PID', 'Supervisors', 'Status', ], collect($masters)->map(function ($master) { return [ $master->name, $master->pid, $master->supervisors ? collect($master->supervisors)->map(function ($supervisor) { return explode(':', $supervisor, 2)[1]; })->implode(', ') : 'None', $master->status, ]; })->all()); $this->output->writeln(''); } } Console/HorizonCommand.php 0000755 00000003317 00000000000 0011556 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\MasterSupervisor; use Laravel\Horizon\ProvisioningPlan; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon')] class HorizonCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon {--environment= : The environment name}'; /** * The console command description. * * @var string */ protected $description = 'Start a master supervisor in the foreground'; /** * Execute the console command. * * @param \Laravel\Horizon\Contracts\MasterSupervisorRepository $masters * @return void */ public function handle(MasterSupervisorRepository $masters) { if ($masters->find(MasterSupervisor::name())) { return $this->components->warn('A master supervisor is already running on this machine.'); } $environment = $this->option('environment') ?? config('horizon.env') ?? config('app.env'); $master = (new MasterSupervisor($environment))->handleOutputUsing(function ($type, $line) { $this->output->write($line); }); ProvisioningPlan::get(MasterSupervisor::name())->deploy($environment); $this->components->info('Horizon started successfully.'); pcntl_async_signals(true); pcntl_signal(SIGINT, function () use ($master) { $this->output->writeln(''); $this->components->info('Shutting down.'); return $master->terminate(); }); $master->monitor(); } } Console/ClearMetricsCommand.php 0000755 00000001557 00000000000 0012507 0 ustar 00 <?php namespace Laravel\Horizon\Console; use Illuminate\Console\Command; use Laravel\Horizon\Contracts\MetricsRepository; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'horizon:clear-metrics')] class ClearMetricsCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'horizon:clear-metrics'; /** * The console command description. * * @var string */ protected $description = 'Delete metrics for all jobs and queues'; /** * Execute the console command. * * @param \Laravel\Horizon\Contracts\MetricsRepository $metrics * @return void */ public function handle(MetricsRepository $metrics) { $metrics->clear(); $this->components->info('Metrics cleared successfully.'); } } Jobs/StopMonitoringTag.php 0000755 00000002066 00000000000 0011551 0 ustar 00 <?php namespace Laravel\Horizon\Jobs; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Contracts\TagRepository; class StopMonitoringTag { /** * The tag to stop monitoring. * * @var string */ public $tag; /** * Create a new job instance. * * @param string $tag * @return void */ public function __construct($tag) { $this->tag = $tag; } /** * Execute the job. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @param \Laravel\Horizon\Contracts\TagRepository $tags * @return void */ public function handle(JobRepository $jobs, TagRepository $tags) { $tags->stopMonitoring($this->tag); $monitored = $tags->paginate($this->tag); while (count($monitored) > 0) { $jobs->deleteMonitored($monitored); $offset = array_keys($monitored)[count($monitored) - 1] + 1; $monitored = $tags->paginate($this->tag, $offset); } $tags->forget($this->tag); } } Jobs/RetryFailedJob.php 0000755 00000003734 00000000000 0010772 0 ustar 00 <?php namespace Laravel\Horizon\Jobs; use Carbon\CarbonImmutable; use Illuminate\Contracts\Queue\Factory as Queue; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\JobRepository; class RetryFailedJob { /** * The job ID. * * @var string */ public $id; /** * Create a new job instance. * * @param string $id * @return void */ public function __construct($id) { $this->id = $id; } /** * Execute the job. * * @param \Illuminate\Contracts\Queue\Factory $queue * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @return void */ public function handle(Queue $queue, JobRepository $jobs) { if (is_null($job = $jobs->findFailed($this->id))) { return; } $queue->connection($job->connection)->pushRaw( $this->preparePayload($id = Str::uuid(), $job->payload), $job->queue ); $jobs->storeRetryReference($this->id, $id); } /** * Prepare the payload for queueing. * * @param string $id * @param string $payload * @return string */ protected function preparePayload($id, $payload) { $payload = json_decode($payload, true); return json_encode(array_merge($payload, [ 'id' => $id, 'uuid' => $id, 'attempts' => 0, 'retry_of' => $this->id, 'retryUntil' => $this->prepareNewTimeout($payload), ])); } /** * Prepare the timeout. * * @param array $payload * @return int|null */ protected function prepareNewTimeout($payload) { $retryUntil = $payload['retryUntil'] ?? $payload['timeoutAt'] ?? null; $pushedAt = $payload['pushedAt'] ?? microtime(true); return $retryUntil ? CarbonImmutable::now()->addSeconds(ceil($retryUntil - $pushedAt))->getTimestamp() : null; } } Jobs/MonitorTag.php 0000755 00000001133 00000000000 0010177 0 ustar 00 <?php namespace Laravel\Horizon\Jobs; use Laravel\Horizon\Contracts\TagRepository; class MonitorTag { /** * The tag to monitor. * * @var string */ public $tag; /** * Create a new job instance. * * @param string $tag * @return void */ public function __construct($tag) { $this->tag = $tag; } /** * Execute the job. * * @param \Laravel\Horizon\Contracts\TagRepository $tags * @return void */ public function handle(TagRepository $tags) { $tags->monitor($this->tag); } } HorizonApplicationServiceProvider.php 0000755 00000002203 00000000000 0014066 0 ustar 00 <?php namespace Laravel\Horizon; use Illuminate\Support\Facades\Gate; use Illuminate\Support\ServiceProvider; class HorizonApplicationServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { $this->authorization(); } /** * Configure the Horizon authorization services. * * @return void */ protected function authorization() { $this->gate(); Horizon::auth(function ($request) { return Gate::check('viewHorizon', [$request->user()]) || app()->environment('local'); }); } /** * Register the Horizon gate. * * This gate determines who can access Horizon in non-local environments. * * @return void */ protected function gate() { Gate::define('viewHorizon', function ($user) { return in_array($user->email, [ // ]); }); } /** * Register any application services. * * @return void */ public function register() { // } } Connectors/RedisConnector.php 0000755 00000001305 00000000000 0012256 0 ustar 00 <?php namespace Laravel\Horizon\Connectors; use Illuminate\Queue\Connectors\RedisConnector as BaseConnector; use Illuminate\Support\Arr; use Laravel\Horizon\RedisQueue; class RedisConnector extends BaseConnector { /** * Establish a queue connection. * * @param array $config * @return \Laravel\Horizon\RedisQueue */ public function connect(array $config) { return new RedisQueue( $this->redis, $config['queue'], Arr::get($config, 'connection', $this->connection), Arr::get($config, 'retry_after', 60), Arr::get($config, 'block_for', null), Arr::get($config, 'after_commit', null) ); } } RedisQueue.php 0000755 00000012722 00000000000 0007300 0 ustar 00 <?php namespace Laravel\Horizon; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Queue\RedisQueue as BaseQueue; use Illuminate\Support\Str; use Laravel\Horizon\Events\JobDeleted; use Laravel\Horizon\Events\JobPushed; use Laravel\Horizon\Events\JobReleased; use Laravel\Horizon\Events\JobReserved; use Laravel\Horizon\Events\JobsMigrated; class RedisQueue extends BaseQueue { /** * The job that last pushed to queue via the "push" method. * * @var object|string */ protected $lastPushed; /** * Get the number of queue jobs that are ready to process. * * @param string|null $queue * @return int */ public function readyNow($queue = null) { return $this->getConnection()->llen($this->getQueue($queue)); } /** * Push a new job onto the queue. * * @param object|string $job * @param mixed $data * @param string|null $queue * @return mixed */ public function push($job, $data = '', $queue = null) { return $this->enqueueUsing( $job, $this->createPayload($job, $this->getQueue($queue), $data), $queue, null, function ($payload, $queue) use ($job) { $this->lastPushed = $job; return $this->pushRaw($payload, $queue); } ); } /** * Push a raw payload onto the queue. * * @param string $payload * @param string $queue * @param array $options * @return mixed */ public function pushRaw($payload, $queue = null, array $options = []) { $payload = (new JobPayload($payload))->prepare($this->lastPushed); parent::pushRaw($payload->value, $queue, $options); $this->event($this->getQueue($queue), new JobPushed($payload->value)); return $payload->id(); } /** * Create a payload string from the given job and data. * * @param string $job * @param string $queue * @param mixed $data * @return array */ protected function createPayloadArray($job, $queue, $data = '') { $payload = parent::createPayloadArray($job, $queue, $data); $payload['id'] = $payload['uuid']; return $payload; } /** * Push a new job onto the queue after a delay. * * @param \DateTimeInterface|\DateInterval|int $delay * @param string $job * @param mixed $data * @param string $queue * @return mixed */ public function later($delay, $job, $data = '', $queue = null) { $payload = (new JobPayload($this->createPayload($job, $queue, $data)))->prepare($job)->value; if (method_exists($this, 'enqueueUsing')) { return $this->enqueueUsing( $job, $payload, $queue, $delay, function ($payload, $queue, $delay) { return tap(parent::laterRaw($delay, $payload, $queue), function () use ($payload, $queue) { $this->event($this->getQueue($queue), new JobPushed($payload)); }); } ); } return tap(parent::laterRaw($delay, $payload, $queue), function () use ($payload, $queue) { $this->event($this->getQueue($queue), new JobPushed($payload)); }); } /** * Pop the next job off of the queue. * * @param string $queue * @return \Illuminate\Contracts\Queue\Job|null */ public function pop($queue = null) { return tap(parent::pop($queue), function ($result) use ($queue) { if ($result) { $this->event($this->getQueue($queue), new JobReserved($result->getReservedJob())); } }); } /** * Migrate the delayed jobs that are ready to the regular queue. * * @param string $from * @param string $to * @return void */ public function migrateExpiredJobs($from, $to) { return tap(parent::migrateExpiredJobs($from, $to), function ($jobs) use ($to) { $this->event($to, new JobsMigrated($jobs)); }); } /** * Delete a reserved job from the queue. * * @param string $queue * @param \Illuminate\Queue\Jobs\RedisJob $job * @return void */ public function deleteReserved($queue, $job) { parent::deleteReserved($queue, $job); $this->event($this->getQueue($queue), new JobDeleted($job, $job->getReservedJob())); } /** * Delete a reserved job from the reserved queue and release it. * * @param string $queue * @param \Illuminate\Queue\Jobs\RedisJob $job * @param int $delay * @return void */ public function deleteAndRelease($queue, $job, $delay) { parent::deleteAndRelease($queue, $job, $delay); $this->event($this->getQueue($queue), new JobReleased($job->getReservedJob())); } /** * Fire the given event if a dispatcher is bound. * * @param string $queue * @param mixed $event * @return void */ protected function event($queue, $event) { if ($this->container && $this->container->bound(Dispatcher::class)) { $queue = Str::replaceFirst('queues:', '', $queue); $this->container->make(Dispatcher::class)->dispatch( $event->connection($this->getConnectionName())->queue($queue) ); } } } ProcessPool.php 0000755 00000017542 00000000000 0007502 0 ustar 00 <?php namespace Laravel\Horizon; use Carbon\CarbonImmutable; use Closure; use Countable; use Symfony\Component\Process\Process; class ProcessPool implements Countable { /** * All of the active processes. * * @var array */ public $processes = []; /** * The processes that are terminating. * * @var array */ public $terminatingProcesses = []; /** * Indicates if the process pool is currently running. * * @var array */ public $working = true; /** * The supervisor options for the process pool. * * @var SupervisorOptions */ public $options; /** * The output handler. * * @var \Closure|null */ public $output; /** * Create a new process pool instance. * * @param \Laravel\Horizon\SupervisorOptions $options * @param \Closure|null $output * @return void */ public function __construct(SupervisorOptions $options, Closure $output = null) { $this->options = $options; $this->output = $output ?: function () { // }; } /** * Scale the process count. * * @param int $processes * @return void */ public function scale($processes) { $processes = max(0, (int) $processes); if ($processes === count($this->processes)) { return; } if ($processes > count($this->processes)) { $this->scaleUp($processes); } else { $this->scaleDown($processes); } } /** * Scale up to the given number of processes. * * @param int $processes * @return void */ protected function scaleUp($processes) { $difference = $processes - count($this->processes); for ($i = 0; $i < $difference; $i++) { $this->start(); } } /** * Scale down to the given number of processes. * * @param int $processes * @return void */ protected function scaleDown($processes) { $difference = count($this->processes) - $processes; // Here we will slice off the correct number of processes that we need to terminate // and remove them from the active process array. We'll be adding them the array // of terminating processes where they'll run until they are fully terminated. $terminatingProcesses = array_slice( $this->processes, 0, $difference ); collect($terminatingProcesses)->each(function ($process) { $this->markForTermination($process); })->all(); $this->removeProcesses($difference); // Finally we will call the terminate method on each of the processes that need get // terminated so they can start terminating. Terminating is a graceful operation // so any jobs they are already running will finish running before these quit. collect($this->terminatingProcesses) ->each(function ($process) { $process['process']->terminate(); }); } /** * Mark the given worker process for termination. * * @param \Laravel\Horizon\WorkerProcess $process * @return void */ public function markForTermination(WorkerProcess $process) { $this->terminatingProcesses[] = [ 'process' => $process, 'terminatedAt' => CarbonImmutable::now(), ]; } /** * Remove the given number of processes from the process array. * * @param int $count * @return void */ protected function removeProcesses($count) { array_splice($this->processes, 0, $count); $this->processes = array_values($this->processes); } /** * Add a new worker process to the pool. * * @return $this */ protected function start() { $this->processes[] = $this->createProcess()->handleOutputUsing(function ($type, $line) { call_user_func($this->output, $type, $line); }); return $this; } /** * Create a new process instance. * * @return \Laravel\Horizon\WorkerProcess */ protected function createProcess() { $class = config('horizon.fast_termination') ? BackgroundProcess::class : Process::class; return new WorkerProcess($class::fromShellCommandline( $this->options->toWorkerCommand(), $this->options->directory )->setTimeout(null)->disableOutput()); } /** * Evaluate the current state of all of the processes. * * @return void */ public function monitor() { $this->processes()->each->monitor(); } /** * Terminate all current workers and start fresh ones. * * @return void */ public function restart() { $count = count($this->processes); $this->scale(0); $this->scale($count); } /** * Pause all of the worker processes. * * @return void */ public function pause() { $this->working = false; collect($this->processes)->each->pause(); } /** * Instruct all of the worker processes to continue working. * * @return void */ public function continue() { $this->working = true; collect($this->processes)->each->continue(); } /** * Get the processes that are still terminating. * * @return \Illuminate\Support\Collection */ public function terminatingProcesses() { $this->pruneTerminatingProcesses(); return collect($this->terminatingProcesses); } /** * Remove any non-running processes from the terminating process list. * * @return void */ public function pruneTerminatingProcesses() { $this->stopTerminatingProcessesThatAreHanging(); $this->terminatingProcesses = collect( $this->terminatingProcesses )->filter(function ($process) { return $process['process']->isRunning(); })->all(); } /** * Stop any terminating processes that are hanging too long. * * @return void */ protected function stopTerminatingProcessesThatAreHanging() { foreach ($this->terminatingProcesses as $process) { $timeout = $this->options->timeout; if ($process['terminatedAt']->addSeconds((int) $timeout)->lte(CarbonImmutable::now())) { $process['process']->stop(); } } } /** * Get all of the current processes as a collection. * * @return \Illuminate\Support\Collection */ public function processes() { return collect($this->processes); } /** * Get all of the current running processes as a collection. * * @return \Illuminate\Support\Collection */ public function runningProcesses() { $terminatingProcesses = $this->terminatingProcesses()->map(function ($process) { return $process['process']; }); return collect($this->processes)->concat($terminatingProcesses)->filter(function ($process) { return $process->process->isRunning(); }); } /** * Get the total active process count, including processes pending termination. * * @return int */ public function totalProcessCount() { return count($this->processes()) + count($this->terminatingProcesses); } /** * The name of the queue(s) being worked by the pool. * * @return string */ public function queue() { return $this->options->queue; } /** * Count the total number of processes in the pool. * * @return int */ public function count(): int { return count($this->processes); } } SupervisorCommands/ContinueWorking.php 0000755 00000000531 00000000000 0014210 0 ustar 00 <?php namespace Laravel\Horizon\SupervisorCommands; use Laravel\Horizon\Contracts\Pausable; class ContinueWorking { /** * Process the command. * * @param \Laravel\Horizon\Contracts\Pausable $pausable * @return void */ public function process(Pausable $pausable) { $pausable->continue(); } } SupervisorCommands/Scale.php 0000755 00000000604 00000000000 0012113 0 ustar 00 <?php namespace Laravel\Horizon\SupervisorCommands; use Laravel\Horizon\Supervisor; class Scale { /** * Process the command. * * @param \Laravel\Horizon\Supervisor $supervisor * @param array $options * @return void */ public function process(Supervisor $supervisor, array $options) { $supervisor->scale($options['scale']); } } SupervisorCommands/Terminate.php 0000755 00000000646 00000000000 0013022 0 ustar 00 <?php namespace Laravel\Horizon\SupervisorCommands; use Laravel\Horizon\Contracts\Terminable; class Terminate { /** * Process the command. * * @param \Laravel\Horizon\Contracts\Terminable $terminable * @param array $options * @return void */ public function process(Terminable $terminable, array $options) { $terminable->terminate($options['status'] ?? 0); } } SupervisorCommands/Balance.php 0000755 00000000577 00000000000 0012422 0 ustar 00 <?php namespace Laravel\Horizon\SupervisorCommands; use Laravel\Horizon\Supervisor; class Balance { /** * Process the command. * * @param \Laravel\Horizon\Supervisor $supervisor * @param array $options * @return void */ public function process(Supervisor $supervisor, array $options) { $supervisor->balance($options); } } SupervisorCommands/Restart.php 0000755 00000000542 00000000000 0012511 0 ustar 00 <?php namespace Laravel\Horizon\SupervisorCommands; use Laravel\Horizon\Contracts\Restartable; class Restart { /** * Process the command. * * @param \Laravel\Horizon\Contracts\Restartable $restartable * @return void */ public function process(Restartable $restartable) { $restartable->restart(); } } SupervisorCommands/Pause.php 0000755 00000000514 00000000000 0012141 0 ustar 00 <?php namespace Laravel\Horizon\SupervisorCommands; use Laravel\Horizon\Contracts\Pausable; class Pause { /** * Process the command. * * @param \Laravel\Horizon\Contracts\Pausable $pausable * @return void */ public function process(Pausable $pausable) { $pausable->pause(); } } PhpBinary.php 0000755 00000000446 00000000000 0007121 0 ustar 00 <?php namespace Laravel\Horizon; class PhpBinary { /** * Get the path to the PHP executable. * * @return string */ public static function path() { $escape = '\\' === DIRECTORY_SEPARATOR ? '"' : '\''; return $escape.PHP_BINARY.$escape; } } Tags.php 0000755 00000011560 00000000000 0006122 0 ustar 00 <?php namespace Laravel\Horizon; use Illuminate\Broadcasting\BroadcastEvent; use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Events\CallQueuedListener; use Illuminate\Mail\SendQueuedMailable; use Illuminate\Notifications\SendQueuedNotifications; use ReflectionClass; use ReflectionProperty; use stdClass; class Tags { /** * The event that was last handled. * * @var object|null */ protected static $event; /** * Determine the tags for the given job. * * @param mixed $job * @return array */ public static function for($job) { if ($tags = static::extractExplicitTags($job)) { return $tags; } return static::modelsFor(static::targetsFor($job))->map(function ($model) { return get_class($model).':'.$model->getKey(); })->all(); } /** * Extract tags from job object. * * @param mixed $job * @return array */ public static function extractExplicitTags($job) { return $job instanceof CallQueuedListener ? static::tagsForListener($job) : static::explicitTags(static::targetsFor($job)); } /** * Determine tags for the given queued listener. * * @param mixed $job * @return array */ protected static function tagsForListener($job) { $event = static::extractEvent($job); static::setEvent($event); return collect( [static::extractListener($job), $event] )->map(function ($job) { return static::for($job); })->collapse()->unique()->tap(function () { static::flushEventState(); })->toArray(); } /** * Determine tags for the given job. * * @param array $jobs * @return array */ protected static function explicitTags(array $jobs) { return collect($jobs)->map(function ($job) { return method_exists($job, 'tags') ? $job->tags(static::$event) : []; })->collapse()->unique()->all(); } /** * Get the actual target for the given job. * * @param mixed $job * @return array */ public static function targetsFor($job) { return match (true) { $job instanceof BroadcastEvent => [$job->event], $job instanceof CallQueuedListener => [static::extractEvent($job)], $job instanceof SendQueuedMailable => [$job->mailable], $job instanceof SendQueuedNotifications => [$job->notification], default => [$job], }; } /** * Get the models from the given object. * * @param array $targets * @return \Illuminate\Support\Collection */ public static function modelsFor(array $targets) { $models = []; foreach ($targets as $target) { $models[] = collect( (new ReflectionClass($target))->getProperties() )->map(function ($property) use ($target) { $property->setAccessible(true); $value = static::getValue($property, $target); if ($value instanceof Model) { return [$value]; } elseif ($value instanceof EloquentCollection) { return $value->all(); } })->collapse()->filter()->all(); } return collect($models)->collapse()->unique(); } /** * Get the value of the given ReflectionProperty. * * @param \ReflectionProperty $property * @param mixed $target */ protected static function getValue(ReflectionProperty $property, $target) { if (method_exists($property, 'isInitialized') && ! $property->isInitialized($target)) { return; } return $property->getValue($target); } /** * Extract the listener from a queued job. * * @param mixed $job * @return mixed */ protected static function extractListener($job) { return (new ReflectionClass($job->class))->newInstanceWithoutConstructor(); } /** * Extract the event from a queued job. * * @param mixed $job * @return mixed */ protected static function extractEvent($job) { return isset($job->data[0]) && is_object($job->data[0]) ? $job->data[0] : new stdClass; } /** * Set the event currently being handled. * * @param object $event * @return void */ protected static function setEvent($event) { static::$event = $event; } /** * Flush the event currently being handled. * * @return void */ protected static function flushEventState() { static::$event = null; } } JobPayload.php 0000755 00000013052 00000000000 0007246 0 ustar 00 <?php namespace Laravel\Horizon; use ArrayAccess; use Illuminate\Broadcasting\BroadcastEvent; use Illuminate\Events\CallQueuedListener; use Illuminate\Mail\SendQueuedMailable; use Illuminate\Notifications\SendQueuedNotifications; use Illuminate\Support\Arr; use Laravel\Horizon\Contracts\Silenced; class JobPayload implements ArrayAccess { /** * The raw payload string. * * @var string */ public $value; /** * The decoded payload array. * * @var array */ public $decoded; /** * Create a new raw job payload instance. * * @param string $value * @return void */ public function __construct($value) { $this->value = $value; $this->decoded = json_decode($value, true); } /** * Get the job ID from the payload. * * @return string */ public function id() { return $this->decoded['uuid'] ?? $this->decoded['id']; } /** * Get the job tags from the payload. * * @return array */ public function tags() { return Arr::get($this->decoded, 'tags', []); } /** * Determine if the job is a retry of a previous job. * * @return bool */ public function isRetry() { return isset($this->decoded['retry_of']); } /** * Get the ID of the job this job is a retry of. * * @return string|null */ public function retryOf() { return $this->decoded['retry_of'] ?? null; } /** * Determine if the job has been silenced. * * @return bool */ public function isSilenced() { return $this->decoded['silenced'] ?? false; } /** * Prepare the payload for storage on the queue by adding tags, etc. * * @param mixed $job * @return $this */ public function prepare($job) { return $this->set([ 'type' => $this->determineType($job), 'tags' => $this->determineTags($job), 'silenced' => $this->shouldBeSilenced($job), 'pushedAt' => str_replace(',', '.', microtime(true)), ]); } /** * Get the "type" of job being queued. * * @param mixed $job * @return string */ protected function determineType($job) { return match (true) { $job instanceof BroadcastEvent => 'broadcast', $job instanceof CallQueuedListener => 'event', $job instanceof SendQueuedMailable => 'mail', $job instanceof SendQueuedNotifications => 'notification', default => 'job', }; } /** * Get the appropriate tags for the job. * * @param mixed $job * @return array */ protected function determineTags($job) { return array_merge( $this->decoded['tags'] ?? [], ! $job || is_string($job) ? [] : Tags::for($job) ); } /** * Determine if the underlying job class should be silenced. * * @param mixed $job * @return bool */ protected function shouldBeSilenced($job) { if (! $job) { return false; } $underlyingJob = $this->underlyingJob($job); $jobClass = is_string($underlyingJob) ? $underlyingJob : get_class($underlyingJob); return in_array($jobClass, config('horizon.silenced', [])) || is_a($jobClass, Silenced::class, true); } /** * Get the underlying queued job. * * @param mixed $job * @return mixed */ protected function underlyingJob($job) { return match (true) { $job instanceof BroadcastEvent => $job->event, $job instanceof CallQueuedListener => $job->class, $job instanceof SendQueuedMailable => $job->mailable, $job instanceof SendQueuedNotifications => $job->notification, default => $job, }; } /** * Set the given key / value pairs on the payload. * * @param array $values * @return $this */ public function set(array $values) { $this->decoded = array_merge($this->decoded, $values); $this->value = json_encode($this->decoded); return $this; } /** * Get the "command name" for the job. * * @return string */ public function commandName() { return Arr::get($this->decoded, 'data.commandName'); } /** * Get the "display name" for the job. * * @return string */ public function displayName() { return Arr::get($this->decoded, 'displayName'); } /** * Determine if the given offset exists. * * @param string $offset * @return bool */ public function offsetExists($offset): bool { return array_key_exists($offset, $this->decoded); } /** * Get the value at the current offset. * * @param string $offset * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->decoded[$offset]; } /** * Set the value at the current offset. * * @param string $offset * @param mixed $value * @return void */ public function offsetSet($offset, $value): void { $this->decoded[$offset] = $value; } /** * Unset the value at the current offset. * * @param string $offset * @return void */ public function offsetUnset($offset): void { unset($this->decoded[$offset]); } } Exceptions/ForbiddenException.php 0000755 00000000477 00000000000 0013125 0 ustar 00 <?php namespace Laravel\Horizon\Exceptions; use Symfony\Component\HttpKernel\Exception\HttpException; class ForbiddenException extends HttpException { /** * Create a new exception instance. * * @return static */ public static function make() { return new static(403); } } Http/Controllers/MonitoringController.php 0000755 00000005357 00000000000 0014651 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Illuminate\Http\Request; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Contracts\TagRepository; use Laravel\Horizon\Jobs\MonitorTag; use Laravel\Horizon\Jobs\StopMonitoringTag; class MonitoringController extends Controller { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * The tag repository implementation. * * @var \Laravel\Horizon\Contracts\TagRepository */ public $tags; /** * Create a new controller instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @param \Laravel\Horizon\Contracts\TagRepository $tags * @return void */ public function __construct(JobRepository $jobs, TagRepository $tags) { parent::__construct(); $this->jobs = $jobs; $this->tags = $tags; } /** * Get all of the monitored tags and their job counts. * * @return \Illuminate\Support\Collection */ public function index() { return collect($this->tags->monitoring())->map(function ($tag) { return [ 'tag' => $tag, 'count' => $this->tags->count($tag) + $this->tags->count('failed:'.$tag), ]; })->sortBy('tag')->values(); } /** * Paginate the jobs for a given tag. * * @param \Illuminate\Http\Request $request * @return array */ public function paginate(Request $request) { $tag = $request->query('tag'); $jobIds = $this->tags->paginate( $tag, $startingAt = $request->query('starting_at', 0), $request->query('limit', 25) ); return [ 'jobs' => $this->getJobs($jobIds, $startingAt), 'total' => $this->tags->count($tag), ]; } /** * Get the jobs for the given IDs. * * @param array $jobIds * @param int $startingAt * @return \Illuminate\Support\Collection */ protected function getJobs($jobIds, $startingAt = 0) { return $this->jobs->getJobs($jobIds, $startingAt)->map(function ($job) { $job->payload = json_decode($job->payload); return $job; })->values(); } /** * Start monitoring the given tag. * * @param \Illuminate\Http\Request $request * @return void */ public function store(Request $request) { dispatch(new MonitorTag($request->tag)); } /** * Stop monitoring the given tag. * * @param string $tag * @return void */ public function destroy($tag) { dispatch(new StopMonitoringTag($tag)); } } Http/Controllers/QueueMetricsController.php 0000755 00000002262 00000000000 0015127 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Laravel\Horizon\Contracts\MetricsRepository; class QueueMetricsController extends Controller { /** * The metrics repository implementation. * * @var \Laravel\Horizon\Contracts\MetricsRepository */ public $metrics; /** * Create a new controller instance. * * @param \Laravel\Horizon\Contracts\MetricsRepository $metrics * @return void */ public function __construct(MetricsRepository $metrics) { parent::__construct(); $this->metrics = $metrics; } /** * Get all of the measured queues. * * @return array */ public function index() { return $this->metrics->measuredQueues(); } /** * Get metrics for a given queue. * * @param string $id * @return \Illuminate\Support\Collection */ public function show($id) { return collect($this->metrics->snapshotsForQueue($id))->map(function ($record) { $record->runtime = round($record->runtime / 1000, 3); $record->throughput = (int) $record->throughput; return $record; }); } } Http/Controllers/PendingJobsController.php 0000755 00000002441 00000000000 0014715 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Illuminate\Http\Request; use Laravel\Horizon\Contracts\JobRepository; class PendingJobsController extends Controller { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * Create a new controller instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @return void */ public function __construct(JobRepository $jobs) { parent::__construct(); $this->jobs = $jobs; } /** * Get all of the pending jobs. * * @param \Illuminate\Http\Request $request * @return array */ public function index(Request $request) { $jobs = $this->jobs->getPending($request->query('starting_at', -1))->map(function ($job) { $job->payload = json_decode($job->payload); return $job; })->values(); return [ 'jobs' => $jobs, 'total' => $this->jobs->countPending(), ]; } /** * Decode the given job. * * @param object $job * @return object */ protected function decode($job) { $job->payload = json_decode($job->payload); return $job; } } Http/Controllers/DashboardStatsController.php 0000755 00000005045 00000000000 0015424 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\Contracts\MetricsRepository; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\WaitTimeCalculator; class DashboardStatsController extends Controller { /** * Get the key performance stats for the dashboard. * * @return array */ public function index() { return [ 'failedJobs' => app(JobRepository::class)->countRecentlyFailed(), 'jobsPerMinute' => app(MetricsRepository::class)->jobsProcessedPerMinute(), 'pausedMasters' => $this->totalPausedMasters(), 'periods' => [ 'failedJobs' => config('horizon.trim.recent_failed', config('horizon.trim.failed')), 'recentJobs' => config('horizon.trim.recent'), ], 'processes' => $this->totalProcessCount(), 'queueWithMaxRuntime' => app(MetricsRepository::class)->queueWithMaximumRuntime(), 'queueWithMaxThroughput' => app(MetricsRepository::class)->queueWithMaximumThroughput(), 'recentJobs' => app(JobRepository::class)->countRecent(), 'status' => $this->currentStatus(), 'wait' => collect(app(WaitTimeCalculator::class)->calculate())->take(1), ]; } /** * Get the total process count across all supervisors. * * @return int */ protected function totalProcessCount() { $supervisors = app(SupervisorRepository::class)->all(); return collect($supervisors)->reduce(function ($carry, $supervisor) { return $carry + collect($supervisor->processes)->sum(); }, 0); } /** * Get the current status of Horizon. * * @return string */ protected function currentStatus() { if (! $masters = app(MasterSupervisorRepository::class)->all()) { return 'inactive'; } return collect($masters)->every(function ($master) { return $master->status === 'paused'; }) ? 'paused' : 'running'; } /** * Get the number of master supervisors that are currently paused. * * @return int */ protected function totalPausedMasters() { if (! $masters = app(MasterSupervisorRepository::class)->all()) { return 0; } return collect($masters)->filter(function ($master) { return $master->status === 'paused'; })->count(); } } Http/Controllers/BatchesController.php 0000755 00000004306 00000000000 0014066 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Illuminate\Bus\BatchRepository; use Illuminate\Database\QueryException; use Illuminate\Http\Request; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Jobs\RetryFailedJob; class BatchesController extends Controller { /** * The job repository implementation. * * @var \Illuminate\Bus\BatchRepository */ public $batches; /** * Create a new controller instance. * * @param \Illuminate\Bus\BatchRepository $batches * @return void */ public function __construct(BatchRepository $batches) { parent::__construct(); $this->batches = $batches; } /** * Get all of the batches. * * @param \Illuminate\Http\Request $request * @return array */ public function index(Request $request) { try { $batches = $this->batches->get(50, $request->query('before_id') ?: null); } catch (QueryException $e) { $batches = []; } return [ 'batches' => $batches, ]; } /** * Get the details of a batch by ID. * * @param string $id * @return array */ public function show($id) { $batch = $this->batches->find($id); if ($batch) { $failedJobs = app(JobRepository::class) ->getJobs($batch->failedJobIds); } return [ 'batch' => $batch, 'failedJobs' => $failedJobs ?? null, ]; } /** * Retry the given batch. * * @param string $id * @return void */ public function retry($id) { $batch = $this->batches->find($id); if ($batch) { app(JobRepository::class) ->getJobs($batch->failedJobIds) ->reject(function ($job) { $payload = json_decode($job->payload); return isset($payload->retry_of); })->each(function ($job) { dispatch(new RetryFailedJob($job->id)); }); } } } Http/Controllers/WorkloadController.php 0000755 00000000731 00000000000 0014275 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Laravel\Horizon\Contracts\WorkloadRepository; class WorkloadController extends Controller { /** * Get the current queue workload for the application. * * @param \Laravel\Horizon\Contracts\WorkloadRepository $workload * @return array */ public function index(WorkloadRepository $workload) { return collect($workload->get())->sortBy('name')->values()->toArray(); } } Http/Controllers/JobMetricsController.php 0000755 00000002250 00000000000 0014552 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Laravel\Horizon\Contracts\MetricsRepository; class JobMetricsController extends Controller { /** * The metrics repository implementation. * * @var \Laravel\Horizon\Contracts\MetricsRepository */ public $metrics; /** * Create a new controller instance. * * @param \Laravel\Horizon\Contracts\MetricsRepository $metrics * @return void */ public function __construct(MetricsRepository $metrics) { parent::__construct(); $this->metrics = $metrics; } /** * Get all of the measured jobs. * * @return array */ public function index() { return $this->metrics->measuredJobs(); } /** * Get metrics for a given job. * * @param string $id * @return \Illuminate\Support\Collection */ public function show($id) { return collect($this->metrics->snapshotsForJob($id))->map(function ($record) { $record->runtime = round($record->runtime / 1000, 3); $record->throughput = (int) $record->throughput; return $record; }); } } Http/Controllers/JobsController.php 0000755 00000002060 00000000000 0013405 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Laravel\Horizon\Contracts\JobRepository; class JobsController extends Controller { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * Create a new controller instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @return void */ public function __construct(JobRepository $jobs) { parent::__construct(); $this->jobs = $jobs; } /** * Get the details of a recent job by ID. * * @param string $id * @return array */ public function show($id) { return (array) $this->jobs->getJobs([$id])->map(function ($job) { return $this->decode($job); })->first(); } /** * Decode the given job. * * @param object $job * @return object */ protected function decode($job) { $job->payload = json_decode($job->payload); return $job; } } Http/Controllers/MasterSupervisorController.php 0000755 00000003674 00000000000 0016061 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\ProvisioningPlan; class MasterSupervisorController extends Controller { /** * Get all of the master supervisors and their underlying supervisors. * * @param \Laravel\Horizon\Contracts\MasterSupervisorRepository $masters * @param \Laravel\Horizon\Contracts\SupervisorRepository $supervisors * @return \Illuminate\Support\Collection */ public function index(MasterSupervisorRepository $masters, SupervisorRepository $supervisors) { $masters = collect($masters->all())->keyBy('name')->sortBy('name'); $supervisors = collect($supervisors->all())->sortBy('name')->groupBy('master'); return $masters->each(function ($master, $name) use ($supervisors) { $master->supervisors = ($supervisors->get($name) ?? collect()) ->merge( collect(ProvisioningPlan::get($name)->plan[$master->environment ?? config('horizon.env') ?? config('app.env')] ?? []) ->map(function ($value, $key) use ($name) { return (object) [ 'name' => $name.':'.$key, 'master' => $name, 'status' => 'inactive', 'processes' => [], 'options' => [ 'queue' => array_key_exists('queue', $value) && is_array($value['queue']) ? implode(',', $value['queue']) : ($value['queue'] ?? ''), 'balance' => $value['balance'] ?? null, ], ]; }) ) ->unique('name') ->values(); }); } } Http/Controllers/RetryController.php 0000755 00000000502 00000000000 0013614 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Laravel\Horizon\Jobs\RetryFailedJob; class RetryController extends Controller { /** * Retry a failed job. * * @param string $id * @return void */ public function store($id) { dispatch(new RetryFailedJob($id)); } } Http/Controllers/HomeController.php 0000755 00000000664 00000000000 0013410 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Illuminate\Support\Facades\App; class HomeController extends Controller { /** * Single page application catch-all route. * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function index() { return view('horizon::layout', [ 'isDownForMaintenance' => App::isDownForMaintenance(), ]); } } Http/Controllers/Controller.php 0000755 00000000572 00000000000 0012575 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Illuminate\Routing\Controller as BaseController; use Laravel\Horizon\Http\Middleware\Authenticate; class Controller extends BaseController { /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware(Authenticate::class); } } Http/Controllers/CompletedJobsController.php 0000755 00000002451 00000000000 0015246 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Illuminate\Http\Request; use Laravel\Horizon\Contracts\JobRepository; class CompletedJobsController extends Controller { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * Create a new controller instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @return void */ public function __construct(JobRepository $jobs) { parent::__construct(); $this->jobs = $jobs; } /** * Get all of the completed jobs. * * @param \Illuminate\Http\Request $request * @return array */ public function index(Request $request) { $jobs = $this->jobs->getCompleted($request->query('starting_at', -1))->map(function ($job) { $job->payload = json_decode($job->payload); return $job; })->values(); return [ 'jobs' => $jobs, 'total' => $this->jobs->countCompleted(), ]; } /** * Decode the given job. * * @param object $job * @return object */ protected function decode($job) { $job->payload = json_decode($job->payload); return $job; } } Http/Controllers/FailedJobsController.php 0000755 00000006266 00000000000 0014526 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Illuminate\Http\Request; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Contracts\TagRepository; class FailedJobsController extends Controller { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * The tag repository implementation. * * @var \Laravel\Horizon\Contracts\TagRepository */ public $tags; /** * Create a new controller instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @param \Laravel\Horizon\Contracts\TagRepository $tags * @return void */ public function __construct(JobRepository $jobs, TagRepository $tags) { parent::__construct(); $this->jobs = $jobs; $this->tags = $tags; } /** * Get all of the failed jobs. * * @param \Illuminate\Http\Request $request * @return array */ public function index(Request $request) { $jobs = ! $request->query('tag') ? $this->paginate($request) : $this->paginateByTag($request, $request->query('tag')); $total = $request->query('tag') ? $this->tags->count('failed:'.$request->query('tag')) : $this->jobs->countFailed(); return [ 'jobs' => $jobs, 'total' => $total, ]; } /** * Paginate the failed jobs for the request. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Support\Collection */ protected function paginate(Request $request) { return $this->jobs->getFailed($request->query('starting_at') ?: -1)->map(function ($job) { return $this->decode($job); }); } /** * Paginate the failed jobs for the request and tag. * * @param \Illuminate\Http\Request $request * @param string $tag * @return \Illuminate\Support\Collection */ protected function paginateByTag(Request $request, $tag) { $jobIds = $this->tags->paginate( 'failed:'.$tag, ($request->query('starting_at') ?: -1) + 1, 50 ); $startingAt = $request->query('starting_at', 0); return $this->jobs->getJobs($jobIds, $startingAt)->map(function ($job) { return $this->decode($job); }); } /** * Get a failed job instance. * * @param string $id * @return mixed */ public function show($id) { return (array) $this->jobs->getJobs([$id])->map(function ($job) { return $this->decode($job); })->first(); } /** * Decode the given job. * * @param object $job * @return object */ protected function decode($job) { $job->payload = json_decode($job->payload); $job->exception = mb_convert_encoding($job->exception, 'UTF-8'); $job->context = json_decode($job->context ?? ''); $job->retried_by = collect(! is_null($job->retried_by) ? json_decode($job->retried_by) : []) ->sortByDesc('retried_at')->values(); return $job; } } Http/Controllers/SilencedJobsController.php 0000755 00000002445 00000000000 0015063 0 ustar 00 <?php namespace Laravel\Horizon\Http\Controllers; use Illuminate\Http\Request; use Laravel\Horizon\Contracts\JobRepository; class SilencedJobsController extends Controller { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * Create a new controller instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @return void */ public function __construct(JobRepository $jobs) { parent::__construct(); $this->jobs = $jobs; } /** * Get all of the silenced jobs. * * @param \Illuminate\Http\Request $request * @return array */ public function index(Request $request) { $jobs = $this->jobs->getSilenced($request->query('starting_at', -1))->map(function ($job) { $job->payload = json_decode($job->payload); return $job; })->values(); return [ 'jobs' => $jobs, 'total' => $this->jobs->countSilenced(), ]; } /** * Decode the given job. * * @param object $job * @return object */ protected function decode($job) { $job->payload = json_decode($job->payload); return $job; } } Http/Middleware/Authenticate.php 0000755 00000001020 00000000000 0012624 0 ustar 00 <?php namespace Laravel\Horizon\Http\Middleware; use Laravel\Horizon\Exceptions\ForbiddenException; use Laravel\Horizon\Horizon; class Authenticate { /** * Handle the incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return \Illuminate\Http\Response|null */ public function handle($request, $next) { if (! Horizon::check($request)) { throw ForbiddenException::make(); } return $next($request); } } RedisHorizonCommandQueue.php 0000755 00000004057 00000000000 0012152 0 ustar 00 <?php namespace Laravel\Horizon; use Illuminate\Contracts\Redis\Factory as RedisFactory; use Laravel\Horizon\Contracts\HorizonCommandQueue; class RedisHorizonCommandQueue implements HorizonCommandQueue { /** * The Redis connection instance. * * @var \Illuminate\Contracts\Redis\Factory */ public $redis; /** * Create a new command queue instance. * * @param \Illuminate\Contracts\Redis\Factory $redis * @return void */ public function __construct(RedisFactory $redis) { $this->redis = $redis; } /** * Push a command onto a given queue. * * @param string $name * @param string $command * @param array $options * @return void */ public function push($name, $command, array $options = []) { $this->connection()->rpush('commands:'.$name, json_encode([ 'command' => $command, 'options' => $options, ])); } /** * Get the pending commands for a given queue name. * * @param string $name * @return array */ public function pending($name) { $length = $this->connection()->llen('commands:'.$name); if ($length < 1) { return []; } $results = $this->connection()->pipeline(function ($pipe) use ($name, $length) { $pipe->lrange('commands:'.$name, 0, $length - 1); $pipe->ltrim('commands:'.$name, $length, -1); }); return collect($results[0])->map(function ($result) { return (object) json_decode($result, true); })->all(); } /** * Flush the command queue for a given queue name. * * @param string $name * @return void */ public function flush($name) { $this->connection()->del('commands:'.$name); } /** * Get the Redis connection instance. * * @return \Illuminate\Redis\Connections\Connection */ protected function connection() { return $this->redis->connection('horizon'); } } QueueCommandString.php 0000755 00000004131 00000000000 0010772 0 ustar 00 <?php namespace Laravel\Horizon; class QueueCommandString { /** * Get the additional option string for the worker command. * * @param \Laravel\Horizon\SupervisorOptions $options * @return string */ public static function toWorkerOptionsString(SupervisorOptions $options) { return sprintf('--name=%s --supervisor=%s %s', $options->workersName, $options->name, static::toOptionsString($options) ); } /** * Get the additional option string for the supervisor command. * * @param \Laravel\Horizon\SupervisorOptions $options * @return string */ public static function toSupervisorOptionsString(SupervisorOptions $options) { return sprintf('--workers-name=%s --balance=%s --max-processes=%s --min-processes=%s --nice=%s --balance-cooldown=%s --balance-max-shift=%s --parent-id=%s --auto-scaling-strategy=%s %s', $options->workersName, $options->balance, $options->maxProcesses, $options->minProcesses, $options->nice, $options->balanceCooldown, $options->balanceMaxShift, $options->parentId, $options->autoScalingStrategy, static::toOptionsString($options) ); } /** * Get the additional option string for the command. * * @param \Laravel\Horizon\SupervisorOptions $options * @param bool $paused * @return string */ public static function toOptionsString(SupervisorOptions $options, $paused = false) { $string = sprintf('--backoff=%s --max-time=%s --max-jobs=%s --memory=%s --queue="%s" --sleep=%s --timeout=%s --tries=%s --rest=%s', $options->backoff, $options->maxTime, $options->maxJobs, $options->memory, $options->queue, $options->sleep, $options->timeout, $options->maxTries, $options->rest ); if ($options->force) { $string .= ' --force'; } if ($paused) { $string .= ' --paused'; } return $string; } } Supervisor.php 0000755 00000027413 00000000000 0007411 0 ustar 00 <?php namespace Laravel\Horizon; use Carbon\CarbonImmutable; use Closure; use Exception; use Illuminate\Contracts\Cache\Factory as CacheFactory; use Illuminate\Contracts\Debug\ExceptionHandler; use Laravel\Horizon\Contracts\HorizonCommandQueue; use Laravel\Horizon\Contracts\Pausable; use Laravel\Horizon\Contracts\Restartable; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\Contracts\Terminable; use Laravel\Horizon\Events\SupervisorLooped; use Throwable; class Supervisor implements Pausable, Restartable, Terminable { use ListensForSignals; /** * The name of this supervisor instance. * * @return string */ public $name; /** * The SupervisorOptions that should be utilized. * * @var \Laravel\Horizon\SupervisorOptions */ public $options; /** * All of the process pools being managed. * * @var \Illuminate\Support\Collection */ public $processPools; /** * Indicates if the Supervisor processes are working. * * @var bool */ public $working = true; /** * The time at which auto-scaling last ran for this supervisor. * * @var \Carbon\CarbonImmutable */ public $lastAutoScaled; /** * The output handler. * * @var \Closure|null */ public $output; /** * Create a new supervisor instance. * * @param \Laravel\Horizon\SupervisorOptions $options * @return void */ public function __construct(SupervisorOptions $options) { $this->options = $options; $this->name = $options->name; $this->processPools = $this->createProcessPools(); $this->output = function () { // }; app(HorizonCommandQueue::class)->flush($this->name); } /** * Create the supervisor's process pools. * * @return \Illuminate\Support\Collection */ public function createProcessPools() { return $this->options->balancing() ? $this->createProcessPoolPerQueue() : $this->createSingleProcessPool(); } /** * Create a process pool for each queue. * * @return \Illuminate\Support\Collection */ protected function createProcessPoolPerQueue() { return collect(explode(',', $this->options->queue))->map(function ($queue) { return $this->createProcessPool($this->options->withQueue($queue)); }); } /** * Create a single process pool. * * @return \Illuminate\Support\Collection */ protected function createSingleProcessPool() { return collect([$this->createProcessPool($this->options)]); } /** * Create a new process pool with the given options. * * @param \Laravel\Horizon\SupervisorOptions $options * @return \Laravel\Horizon\ProcessPool */ protected function createProcessPool(SupervisorOptions $options) { return new ProcessPool($options, function ($type, $line) { $this->output($type, $line); }); } /** * Scale the process count. * * @param int $processes * @return void */ public function scale($processes) { $this->options->maxProcesses = max($this->options->maxProcesses, $processes, count($this->processPools) ); $this->balance($this->processPools->mapWithKeys(function ($pool) use ($processes) { return [$pool->queue() => floor($processes / count($this->processPools))]; })->all()); } /** * Balance the process pool at the given scales. * * @param array $balance * @return void */ public function balance(array $balance) { foreach ($balance as $queue => $scale) { $this->processPools->first(function ($pool) use ($queue) { return $pool->queue() === $queue; }, new class { public function __call($method, $arguments) { } })->scale($scale); } } /** * Terminate all current workers and start fresh ones. * * @return void */ public function restart() { $this->working = true; $this->processPools->each->restart(); } /** * Pause all of the worker processes. * * @return void */ public function pause() { $this->working = false; $this->processPools->each->pause(); } /** * Instruct all of the worker processes to continue working. * * @return void */ public function continue() { $this->working = true; $this->processPools->each->continue(); } /** * Terminate this supervisor process and all of its workers. * * @param int $status * @return void */ public function terminate($status = 0) { $this->working = false; // We will mark this supervisor as terminating so that any user interface can // correctly show the supervisor's status. Then, we will scale the process // pools down to zero workers to gracefully terminate them all out here. app(SupervisorRepository::class)->forget($this->name); $this->processPools->each(function ($pool) { $pool->processes()->each(function ($process) { $process->terminate(); }); }); if ($this->shouldWait()) { while ($this->processPools->map->runningProcesses()->collapse()->count()) { sleep(1); } } $this->exit($status); } /** * Check if the supervisor should wait for all its workers to terminate. * * @return bool */ protected function shouldWait() { return ! config('horizon.fast_termination') || app(CacheFactory::class)->get('horizon:terminate:wait'); } /** * Monitor the worker processes. * * @return void */ public function monitor() { $this->ensureNoDuplicateSupervisors(); $this->listenForSignals(); $this->persist(); while (true) { sleep(1); $this->loop(); } } /** * Ensure no other supervisors are running with the same name. * * @return void * * @throws \Exception */ public function ensureNoDuplicateSupervisors() { if (app(SupervisorRepository::class)->find($this->name) !== null) { throw new Exception("A supervisor with the name [{$this->name}] is already running."); } } /** * Perform a monitor loop. * * @return void */ public function loop() { try { $this->ensureParentIsRunning(); $this->processPendingSignals(); $this->processPendingCommands(); // If the supervisor is working, we will perform any needed scaling operations and // monitor all of these underlying worker processes to make sure they are still // processing queued jobs. If they have died, we will restart them each here. if ($this->working) { $this->autoScale(); $this->processPools->each->monitor(); } // Next, we'll persist the supervisor state to storage so that it can be read by a // user interface. This contains information on the specific options for it and // the current number of worker processes per queue for easy load monitoring. $this->persist(); event(new SupervisorLooped($this)); } catch (Throwable $e) { app(ExceptionHandler::class)->report($e); } } /** * Ensure the parent process is still running. * * @return void */ protected function ensureParentIsRunning() { if ($this->options->parentId > 1 && posix_getppid() <= 1) { $this->terminate(); } } /** * Handle any pending commands for the supervisor. * * @return void */ protected function processPendingCommands() { foreach (app(HorizonCommandQueue::class)->pending($this->name) as $command) { app($command->command)->process($this, $command->options); } } /** * Run the auto-scaling routine for the supervisor. * * @return void */ protected function autoScale() { $this->lastAutoScaled = $this->lastAutoScaled ?: CarbonImmutable::now()->subSeconds($this->options->balanceCooldown + 1); if (CarbonImmutable::now()->subSeconds($this->options->balanceCooldown)->gte($this->lastAutoScaled)) { $this->lastAutoScaled = CarbonImmutable::now(); app(AutoScaler::class)->scale($this); } } /** * Persist information about this supervisor instance. * * @return void */ public function persist() { app(SupervisorRepository::class)->update($this); } /** * Prune all terminating processes and return the total process count. * * @return int */ public function pruneAndGetTotalProcesses() { $this->pruneTerminatingProcesses(); return $this->totalProcessCount(); } /** * Prune any terminating processes that have finished terminating. * * @return void */ public function pruneTerminatingProcesses() { $this->processPools->each->pruneTerminatingProcesses(); } /** * Get all of the current processes as a collection. * * @return \Illuminate\Support\Collection */ public function processes() { return $this->processPools->map->processes()->collapse(); } /** * Get the processes that are still terminating. * * @return \Illuminate\Support\Collection */ public function terminatingProcesses() { return $this->processPools->map->terminatingProcesses()->collapse(); } /** * Get the total active process count, including processes pending termination. * * @return int */ public function totalProcessCount() { return $this->processPools->sum->totalProcessCount(); } /** * Get the total active process count by asking the OS. * * @return int */ public function totalSystemProcessCount() { return app(SystemProcessCounter::class)->get($this->name); } /** * Get the process ID for this supervisor. * * @return int */ public function pid() { return getmypid(); } /** * Get the current memory usage (in megabytes). * * @return float */ public function memoryUsage() { return memory_get_usage() / 1024 / 1024; } /** * Determine if the supervisor is paused. * * @return bool */ public function isPaused() { return ! $this->working; } /** * Set the output handler. * * @param \Closure $callback * @return $this */ public function handleOutputUsing(Closure $callback) { $this->output = $callback; return $this; } /** * Handle the given output. * * @param string $type * @param string $line * @return void */ public function output($type, $line) { call_user_func($this->output, $type, $line); } /** * Shutdown the supervisor. * * @param int $status * @return void */ protected function exit($status = 0) { $this->exitProcess($status); } /** * Exit the PHP process. * * @param int $status * @return void */ protected function exitProcess($status = 0) { exit((int) $status); } } SupervisorCommandString.php 0000755 00000002340 00000000000 0012067 0 ustar 00 <?php namespace Laravel\Horizon; class SupervisorCommandString { /** * The base worker command. * * @var string */ public static $command = 'exec @php artisan horizon:supervisor'; /** * Get the command-line representation of the options for a supervisor. * * @param \Laravel\Horizon\SupervisorOptions $options * @return string */ public static function fromOptions(SupervisorOptions $options) { $command = str_replace('@php', PhpBinary::path(), static::$command); return sprintf( "%s {$options->name} {$options->connection} %s", $command, static::toOptionsString($options) ); } /** * Get the additional option string for the command. * * @param \Laravel\Horizon\SupervisorOptions $options * @return string */ public static function toOptionsString(SupervisorOptions $options) { return QueueCommandString::toSupervisorOptionsString($options); } /** * Reset the base command back to its default value. * * @return void */ public static function reset() { static::$command = 'exec @php artisan horizon:supervisor'; } } ProcessInspector.php 0000755 00000003254 00000000000 0010532 0 ustar 00 <?php namespace Laravel\Horizon; use Illuminate\Support\Arr; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\Contracts\SupervisorRepository; class ProcessInspector { /** * The command executor. * * @var \Laravel\Horizon\Exec */ public $exec; /** * Create a new process inspector instance. * * @param \Laravel\Horizon\Exec $exec * @return void */ public function __construct(Exec $exec) { $this->exec = $exec; } /** * Get the IDs of all Horizon processes running on the system. * * @return array */ public function current() { return array_diff( $this->exec->run('pgrep -f [h]orizon'), $this->exec->run('pgrep -f horizon:purge') ); } /** * Get an array of running Horizon processes that can't be accounted for. * * @return array */ public function orphaned() { return array_diff($this->current(), $this->monitoring()); } /** * Get all of the process IDs Horizon is actively monitoring. * * @return array */ public function monitoring() { return collect(app(SupervisorRepository::class)->all()) ->pluck('pid') ->pipe(function ($processes) { $processes->each(function ($process) use (&$processes) { $processes = $processes->merge($this->exec->run("pgrep -P {$process}")); }); return $processes; }) ->merge( Arr::pluck(app(MasterSupervisorRepository::class)->all(), 'pid') )->all(); } } BackgroundProcess.php 0000755 00000000366 00000000000 0010644 0 ustar 00 <?php namespace Laravel\Horizon; use Symfony\Component\Process\Process; class BackgroundProcess extends Process { /** * Destruct the object. * * @return void */ public function __destruct() { // } } EventMap.php 0000755 00000003745 00000000000 0006751 0 ustar 00 <?php namespace Laravel\Horizon; trait EventMap { /** * All of the Horizon event / listener mappings. * * @var array */ protected $events = [ Events\JobPushed::class => [ Listeners\StoreJob::class, Listeners\StoreMonitoredTags::class, ], Events\JobReserved::class => [ Listeners\MarkJobAsReserved::class, Listeners\StartTimingJob::class, ], Events\JobReleased::class => [ Listeners\MarkJobAsReleased::class, ], Events\JobDeleted::class => [ Listeners\MarkJobAsComplete::class, Listeners\UpdateJobMetrics::class, ], Events\JobsMigrated::class => [ Listeners\MarkJobsAsMigrated::class, ], \Illuminate\Queue\Events\JobExceptionOccurred::class => [ Listeners\ForgetJobTimer::class, ], \Illuminate\Queue\Events\JobFailed::class => [ Listeners\ForgetJobTimer::class, Listeners\MarshalFailedEvent::class, ], Events\JobFailed::class => [ Listeners\MarkJobAsFailed::class, Listeners\StoreTagsForFailedJob::class, ], Events\MasterSupervisorLooped::class => [ Listeners\TrimRecentJobs::class, Listeners\TrimFailedJobs::class, Listeners\TrimMonitoredJobs::class, Listeners\ExpireSupervisors::class, Listeners\MonitorMasterSupervisorMemory::class, ], Events\SupervisorLooped::class => [ Listeners\PruneTerminatingProcesses::class, Listeners\MonitorSupervisorMemory::class, Listeners\MonitorWaitTimes::class, ], Events\WorkerProcessRestarting::class => [ // ], Events\SupervisorProcessRestarting::class => [ // ], Events\LongWaitDetected::class => [ Listeners\SendNotification::class, ], ]; } SupervisorProcess.php 0000755 00000007700 00000000000 0010745 0 ustar 00 <?php namespace Laravel\Horizon; use Closure; use Laravel\Horizon\Contracts\HorizonCommandQueue; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\MasterSupervisorCommands\AddSupervisor; use Laravel\Horizon\SupervisorCommands\Terminate; class SupervisorProcess extends WorkerProcess { /** * The name of the supervisor. * * @var string */ public $name; /** * The supervisor process options. * * @var \Laravel\Horizon\SupervisorOptions */ public $options; /** * Indicates if the process is "dead". * * @var bool */ public $dead = false; /** * The exit codes on which supervisor should be marked as dead. * * @var array */ public $dontRestartOn = [ 0, 2, 13, // Indicates duplicate supervisors... ]; /** * Create a new supervisor process instance. * * @param \Laravel\Horizon\SupervisorOptions $options * @param \Symfony\Component\Process\Process $process * @param \Closure|null $output * @return void */ public function __construct(SupervisorOptions $options, $process, Closure $output = null) { $this->options = $options; $this->name = $options->name; $this->output = $output ?: function () { // }; parent::__construct($process); } /** * Evaluate the current state of the process. * * @return void */ public function monitor() { if (! $this->process->isStarted()) { return $this->restart(); } // First, we will check to see if the supervisor failed as a duplicate and if // it did we will go ahead and mark it as dead. We will do this before the // other checks run because we do not care if this is cooling down here. if (! $this->process->isRunning() && $this->process->getExitCode() === 13) { return $this->markAsDead(); } // If the process is running or cooling down from a failure, we don't need to // attempt to do anything right now, so we can just bail out of the method // here and it will get checked out during the next master monitor loop. if ($this->process->isRunning() || $this->coolingDown()) { return; } // Next, we will determine if the exit code is one that means this supervisor // should be marked as dead and not be restarted. Typically, this could be // an indication that the supervisor was simply purposefully terminated. $exitCode = $this->process->getExitCode(); $this->markAsDead(); // If the supervisor exited with a status code that we do not restart on then // we will not attempt to restart it. Otherwise, we will need to provision // it back out based on the latest provisioning information we have now. if (in_array($exitCode, $this->dontRestartOn)) { return; } $this->reprovision(); } /** * Re-provision this supervisor process based on the provisioning plan. * * @return void */ protected function reprovision() { if (isset($this->name)) { app(SupervisorRepository::class)->forget($this->name); } app(HorizonCommandQueue::class)->push( MasterSupervisor::commandQueue(), AddSupervisor::class, $this->options->toArray() ); } /** * Terminate the supervisor with the given status. * * @param int $status * @return void */ public function terminateWithStatus($status) { app(HorizonCommandQueue::class)->push( $this->options->name, Terminate::class, ['status' => $status] ); } /** * Mark the process as "dead". * * @return void */ protected function markAsDead() { $this->dead = true; } } ServiceBindings.php 0000755 00000002230 00000000000 0010274 0 ustar 00 <?php namespace Laravel\Horizon; trait ServiceBindings { /** * All of the service bindings for Horizon. * * @var array */ public $serviceBindings = [ // General services... AutoScaler::class, Contracts\HorizonCommandQueue::class => RedisHorizonCommandQueue::class, Listeners\TrimRecentJobs::class, Listeners\TrimFailedJobs::class, Listeners\TrimMonitoredJobs::class, Lock::class, Stopwatch::class, // Repository services... Contracts\JobRepository::class => Repositories\RedisJobRepository::class, Contracts\MasterSupervisorRepository::class => Repositories\RedisMasterSupervisorRepository::class, Contracts\MetricsRepository::class => Repositories\RedisMetricsRepository::class, Contracts\ProcessRepository::class => Repositories\RedisProcessRepository::class, Contracts\SupervisorRepository::class => Repositories\RedisSupervisorRepository::class, Contracts\TagRepository::class => Repositories\RedisTagRepository::class, Contracts\WorkloadRepository::class => Repositories\RedisWorkloadRepository::class, ]; } MasterSupervisor.php 0000755 00000021156 00000000000 0010563 0 ustar 00 <?php namespace Laravel\Horizon; use Carbon\CarbonImmutable; use Closure; use Exception; use Illuminate\Contracts\Cache\Factory as CacheFactory; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\HorizonCommandQueue; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\Contracts\Pausable; use Laravel\Horizon\Contracts\Restartable; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\Contracts\Terminable; use Laravel\Horizon\Events\MasterSupervisorLooped; use Throwable; class MasterSupervisor implements Pausable, Restartable, Terminable { use ListensForSignals; /** * The environment that was used to provision this master supervisor. * * @var string|null */ public $environment; /** * The name of the master supervisor. * * @var string */ public $name; /** * All of the supervisors managed. * * @var \Illuminate\Support\Collection */ public $supervisors; /** * Indicates if the master supervisor process is working. * * @var bool */ public $working = true; /** * The output handler. * * @var \Closure|null */ public $output; /** * The callback to use to resolve master supervisor names. * * @var \Closure|null */ public static $nameResolver; /** * Create a new master supervisor instance. * * @param string $environment * @return void */ public function __construct(string $environment = null) { $this->environment = $environment; $this->name = static::name(); $this->supervisors = collect(); $this->output = function () { // }; app(HorizonCommandQueue::class)->flush($this->commandQueue()); } /** * Get the name for this master supervisor. * * @return string */ public static function name() { static $token; if (! $token) { $token = Str::random(4); } return static::basename().'-'.$token; } /** * Get the basename for the machine's master supervisors. * * @return string */ public static function basename() { return static::$nameResolver ? call_user_func(static::$nameResolver) : Str::slug(gethostname()); } /** * Use the given callback to resolve master supervisor names. * * @param \Closure $callback * @return void */ public static function determineNameUsing(Closure $callback) { static::$nameResolver = $callback; } /** * Terminate all current supervisors and start fresh ones. * * @return void */ public function restart() { $this->working = true; $this->supervisors->each->terminateWithStatus(1); } /** * Pause the supervisors. * * @return void */ public function pause() { $this->working = false; $this->supervisors->each->pause(); } /** * Instruct the supervisors to continue working. * * @return void */ public function continue() { $this->working = true; $this->supervisors->each->continue(); } /** * Terminate this master supervisor and all of its supervisors. * * @param int $status * @return void */ public function terminate($status = 0) { $this->working = false; // First we will terminate all child supervisors so they will gracefully scale // down to zero. We'll also grab the longest expiration times of any of the // active supervisors so we know the maximum amount of time to wait here. $longest = app(SupervisorRepository::class) ->longestActiveTimeout(); $this->supervisors->each->terminate(); // We will go ahead and remove this master supervisor's record from storage so // another master supervisor could get started in its place without waiting // for it to really finish terminating all of its underlying supervisors. app(MasterSupervisorRepository::class) ->forget($this->name); $startedTerminating = CarbonImmutable::now(); // Here we will wait until all of the child supervisors finish terminating and // then exit the process. We will keep track of a timeout value so that the // process does not get stuck in an infinite loop here waiting for these. while (count($this->supervisors->filter->isRunning())) { if (CarbonImmutable::now()->subSeconds($longest) ->gte($startedTerminating)) { break; } sleep(1); } if (config('horizon.fast_termination')) { app(CacheFactory::class)->forget('horizon:terminate:wait'); } $this->exit($status); } /** * Monitor the worker processes. * * @return void */ public function monitor() { $this->ensureNoOtherMasterSupervisors(); $this->listenForSignals(); $this->persist(); while (true) { sleep(1); $this->loop(); } } /** * Ensure that this is the only master supervisor running for this machine. * * @return void * * @throws \Exception */ public function ensureNoOtherMasterSupervisors() { if (app(MasterSupervisorRepository::class)->find($this->name) !== null) { throw new Exception('A master supervisor is already running on this machine.'); } } /** * Perform a monitor loop. * * @return void */ public function loop() { try { $this->processPendingSignals(); $this->processPendingCommands(); if ($this->working) { $this->monitorSupervisors(); } $this->persist(); event(new MasterSupervisorLooped($this)); } catch (Throwable $e) { app(ExceptionHandler::class)->report($e); } } /** * Handle any pending commands for the master supervisor. * * @return void */ protected function processPendingCommands() { foreach (app(HorizonCommandQueue::class)->pending($this->commandQueue()) as $command) { app($command->command)->process($this, $command->options); } } /** * "Monitor" all of the supervisors. * * @return void */ protected function monitorSupervisors() { $this->supervisors->each->monitor(); $this->supervisors = $this->supervisors->reject->dead; } /** * Persist information about the master supervisor instance. * * @return void */ public function persist() { app(MasterSupervisorRepository::class)->update($this); } /** * Get the process ID for this supervisor. * * @return int */ public function pid() { return getmypid(); } /** * Get the current memory usage (in megabytes). * * @return float */ public function memoryUsage() { return memory_get_usage() / 1024 / 1024; } /** * Get the name of the command queue for the master supervisor. * * @return string */ public static function commandQueue() { return 'master:'.static::name(); } /** * Get the name of the command queue for the given master supervisor. * * @param string|null $name * @return string */ public static function commandQueueFor($name = null) { return $name ? 'master:'.$name : static::commandQueue(); } /** * Set the output handler. * * @param \Closure $callback * @return $this */ public function handleOutputUsing(Closure $callback) { $this->output = $callback; return $this; } /** * Handle the given output. * * @param string $type * @param string $line * @return void */ public function output($type, $line) { call_user_func($this->output, $type, $line); } /** * Shutdown the supervisor. * * @param int $status * @return void */ protected function exit($status = 0) { $this->exitProcess($status); } /** * Exit the PHP process. * * @param int $status * @return void */ protected function exitProcess($status = 0) { exit((int) $status); } } Repositories/RedisWorkloadRepository.php 0000755 00000007241 00000000000 0014565 0 ustar 00 <?php namespace Laravel\Horizon\Repositories; use Illuminate\Contracts\Queue\Factory as QueueFactory; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\Contracts\WorkloadRepository; use Laravel\Horizon\WaitTimeCalculator; class RedisWorkloadRepository implements WorkloadRepository { /** * The queue factory implementation. * * @var \Illuminate\Contracts\Queue\Factory */ public $queue; /** * The wait time calculator instance. * * @var \Laravel\Horizon\WaitTimeCalculator */ public $waitTime; /** * The master supervisor repository implementation. * * @var \Laravel\Horizon\Contracts\MasterSupervisorRepository */ private $masters; /** * The supervisor repository implementation. * * @var \Laravel\Horizon\Contracts\SupervisorRepository */ private $supervisors; /** * Create a new repository instance. * * @param \Illuminate\Contracts\Queue\Factory $queue * @param \Laravel\Horizon\WaitTimeCalculator $waitTime * @param \Laravel\Horizon\Contracts\MasterSupervisorRepository $masters * @param \Laravel\Horizon\Contracts\SupervisorRepository $supervisors * @return void */ public function __construct(QueueFactory $queue, WaitTimeCalculator $waitTime, MasterSupervisorRepository $masters, SupervisorRepository $supervisors) { $this->queue = $queue; $this->masters = $masters; $this->waitTime = $waitTime; $this->supervisors = $supervisors; } /** * Get the current workload of each queue. * * @return array */ public function get() { $processes = $this->processes(); return collect($this->waitTime->calculate()) ->map(function ($waitTime, $queue) use ($processes) { [$connection, $queueName] = explode(':', $queue, 2); $totalProcesses = $processes[$queue] ?? 0; $length = ! Str::contains($queue, ',') ? collect([$queueName => $this->queue->connection($connection)->readyNow($queueName)]) : collect(explode(',', $queueName))->mapWithKeys(function ($queueName) use ($connection) { return [$queueName => $this->queue->connection($connection)->readyNow($queueName)]; }); $splitQueues = Str::contains($queue, ',') ? $length->map(function ($length, $queueName) use ($connection, $totalProcesses, &$wait) { return [ 'name' => $queueName, 'length' => $length, 'wait' => $wait += $this->waitTime->calculateTimeToClear($connection, $queueName, $totalProcesses), ]; }) : null; return [ 'name' => $queueName, 'length' => $length->sum(), 'wait' => $waitTime, 'processes' => $totalProcesses, 'split_queues' => $splitQueues, ]; })->values()->toArray(); } /** * Get the number of processes of each queue. * * @return array */ private function processes() { return collect($this->supervisors->all())->pluck('processes')->reduce(function ($final, $queues) { foreach ($queues as $queue => $processes) { $final[$queue] = isset($final[$queue]) ? $final[$queue] + $processes : $processes; } return $final; }, []); } } Repositories/RedisSupervisorRepository.php 0000755 00000011311 00000000000 0015155 0 ustar 00 <?php namespace Laravel\Horizon\Repositories; use Carbon\CarbonImmutable; use Illuminate\Contracts\Redis\Factory as RedisFactory; use Illuminate\Support\Arr; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\Supervisor; class RedisSupervisorRepository implements SupervisorRepository { /** * The Redis connection instance. * * @var \Illuminate\Contracts\Redis\Factory */ public $redis; /** * Create a new repository instance. * * @param \Illuminate\Contracts\Redis\Factory $redis * @return void */ public function __construct(RedisFactory $redis) { $this->redis = $redis; } /** * Get the names of all the supervisors currently running. * * @return array */ public function names() { return $this->connection()->zrevrangebyscore('supervisors', '+inf', CarbonImmutable::now()->subSeconds(29)->getTimestamp() ); } /** * Get information on all of the supervisors. * * @return array */ public function all() { return $this->get($this->names()); } /** * Get information on a supervisor by name. * * @param string $name * @return \stdClass|null */ public function find($name) { return Arr::get($this->get([$name]), 0); } /** * Get information on the given supervisors. * * @param array $names * @return array */ public function get(array $names) { $records = $this->connection()->pipeline(function ($pipe) use ($names) { foreach ($names as $name) { $pipe->hmget('supervisor:'.$name, ['name', 'master', 'pid', 'status', 'processes', 'options']); } }); return collect($records)->filter()->map(function ($record) { $record = array_values($record); return ! $record[0] ? null : (object) [ 'name' => $record[0], 'master' => $record[1], 'pid' => $record[2], 'status' => $record[3], 'processes' => json_decode($record[4], true), 'options' => json_decode($record[5], true), ]; })->filter()->all(); } /** * Get the longest active timeout setting for a supervisor. * * @return int */ public function longestActiveTimeout() { return collect($this->all())->max(function ($supervisor) { return $supervisor->options['timeout']; }) ?: 0; } /** * Update the information about the given supervisor process. * * @param \Laravel\Horizon\Supervisor $supervisor * @return void */ public function update(Supervisor $supervisor) { $processes = $supervisor->processPools->mapWithKeys(function ($pool) use ($supervisor) { return [$supervisor->options->connection.':'.$pool->queue() => count($pool->processes())]; })->toJson(); $this->connection()->pipeline(function ($pipe) use ($supervisor, $processes) { $pipe->hmset( 'supervisor:'.$supervisor->name, [ 'name' => $supervisor->name, 'master' => implode(':', explode(':', $supervisor->name, -1)), 'pid' => $supervisor->pid(), 'status' => $supervisor->working ? 'running' : 'paused', 'processes' => $processes, 'options' => $supervisor->options->toJson(), ] ); $pipe->zadd('supervisors', CarbonImmutable::now()->getTimestamp(), $supervisor->name ); $pipe->expire('supervisor:'.$supervisor->name, 30); }); } /** * Remove the supervisor information from storage. * * @param array|string $names * @return void */ public function forget($names) { $names = (array) $names; if (empty($names)) { return; } $this->connection()->del(...collect($names)->map(function ($name) { return 'supervisor:'.$name; })->all()); $this->connection()->zrem('supervisors', ...$names); } /** * Remove expired supervisors from storage. * * @return void */ public function flushExpired() { $this->connection()->zremrangebyscore('supervisors', '-inf', CarbonImmutable::now()->subSeconds(14)->getTimestamp() ); } /** * Get the Redis connection instance. * * @return \Illuminate\Redis\Connections\Connection */ protected function connection() { return $this->redis->connection('horizon'); } } Repositories/RedisMasterSupervisorRepository.php 0000755 00000010236 00000000000 0016336 0 ustar 00 <?php namespace Laravel\Horizon\Repositories; use Carbon\CarbonImmutable; use Illuminate\Contracts\Redis\Factory as RedisFactory; use Illuminate\Support\Arr; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\MasterSupervisor; class RedisMasterSupervisorRepository implements MasterSupervisorRepository { /** * The Redis connection instance. * * @var \Illuminate\Contracts\Redis\Factory */ public $redis; /** * Create a new repository instance. * * @param \Illuminate\Contracts\Redis\Factory $redis * @return void */ public function __construct(RedisFactory $redis) { $this->redis = $redis; } /** * Get the names of all the master supervisors currently running. * * @return array */ public function names() { return $this->connection()->zrevrangebyscore('masters', '+inf', CarbonImmutable::now()->subSeconds(14)->getTimestamp() ); } /** * Get information on all of the supervisors. * * @return array */ public function all() { return $this->get($this->names()); } /** * Get information on a master supervisor by name. * * @param string $name * @return \stdClass|null */ public function find($name) { return Arr::get($this->get([$name]), 0); } /** * Get information on the given master supervisors. * * @param array $names * @return array */ public function get(array $names) { $records = $this->connection()->pipeline(function ($pipe) use ($names) { foreach ($names as $name) { $pipe->hmget('master:'.$name, ['name', 'pid', 'status', 'supervisors', 'environment']); } }); return collect($records)->map(function ($record) { $record = array_values($record); return ! $record[0] ? null : (object) [ 'name' => $record[0], 'environment' => $record[4], 'pid' => $record[1], 'status' => $record[2], 'supervisors' => json_decode($record[3], true), ]; })->filter()->all(); } /** * Update the information about the given master supervisor. * * @param \Laravel\Horizon\MasterSupervisor $master * @return void */ public function update(MasterSupervisor $master) { $supervisors = $master->supervisors->map->name->all(); $this->connection()->pipeline(function ($pipe) use ($master, $supervisors) { $pipe->hmset( 'master:'.$master->name, [ 'name' => $master->name, 'environment' => $master->environment, 'pid' => $master->pid(), 'status' => $master->working ? 'running' : 'paused', 'supervisors' => json_encode($supervisors), ] ); $pipe->zadd('masters', CarbonImmutable::now()->getTimestamp(), $master->name ); $pipe->expire('master:'.$master->name, 15); }); } /** * Remove the master supervisor information from storage. * * @param string $name * @return void */ public function forget($name) { if (! $master = $this->find($name)) { return; } app(SupervisorRepository::class)->forget( $master->supervisors ); $this->connection()->del('master:'.$name); $this->connection()->zrem('masters', $name); } /** * Remove expired master supervisors from storage. * * @return void */ public function flushExpired() { $this->connection()->zremrangebyscore('masters', '-inf', CarbonImmutable::now()->subSeconds(14)->getTimestamp() ); } /** * Get the Redis connection instance. * * @return \Illuminate\Redis\Connections\Connection */ protected function connection() { return $this->redis->connection('horizon'); } } Repositories/RedisProcessRepository.php 0000755 00000005214 00000000000 0014417 0 ustar 00 <?php namespace Laravel\Horizon\Repositories; use Carbon\CarbonImmutable; use Illuminate\Contracts\Redis\Factory as RedisFactory; use Laravel\Horizon\Contracts\ProcessRepository; class RedisProcessRepository implements ProcessRepository { /** * The Redis connection instance. * * @var \Illuminate\Contracts\Redis\Factory */ public $redis; /** * Create a new repository instance. * * @param \Illuminate\Contracts\Redis\Factory $redis * @return void */ public function __construct(RedisFactory $redis) { $this->redis = $redis; } /** * Get all of the orphan process IDs and the times they were observed. * * @param string $master * @return array */ public function allOrphans($master) { return $this->connection()->hgetall( "{$master}:orphans" ); } /** * Record the given process IDs as orphaned. * * @param string $master * @param array $processIds * @return void */ public function orphaned($master, array $processIds) { $time = CarbonImmutable::now()->getTimestamp(); $shouldRemove = array_diff($this->connection()->hkeys( $key = "{$master}:orphans" ), $processIds); if (! empty($shouldRemove)) { $this->connection()->hdel($key, ...$shouldRemove); } $this->connection()->pipeline(function ($pipe) use ($key, $time, $processIds) { foreach ($processIds as $processId) { $pipe->hsetnx($key, $processId, $time); } }); } /** * Get the process IDs orphaned for at least the given number of seconds. * * @param string $master * @param int $seconds * @return array */ public function orphanedFor($master, $seconds) { $expiresAt = CarbonImmutable::now()->getTimestamp() - $seconds; return collect($this->allOrphans($master))->filter(function ($recordedAt, $_) use ($expiresAt) { return $expiresAt > $recordedAt; })->keys()->all(); } /** * Remove the given process IDs from the orphan list. * * @param string $master * @param array $processIds * @return void */ public function forgetOrphans($master, array $processIds) { $this->connection()->hdel( "{$master}:orphans", ...$processIds ); } /** * Get the Redis connection instance. * * @return \Illuminate\Redis\Connections\Connection */ protected function connection() { return $this->redis->connection('horizon'); } } Repositories/RedisJobRepository.php 0000755 00000050566 00000000000 0013525 0 ustar 00 <?php namespace Laravel\Horizon\Repositories; use Carbon\CarbonImmutable; use Illuminate\Contracts\Redis\Factory as RedisFactory; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\JobPayload; use Laravel\Horizon\LuaScripts; class RedisJobRepository implements JobRepository { /** * The Redis connection instance. * * @var \Illuminate\Contracts\Redis\Factory */ public $redis; /** * The keys stored on the job hashes. * * @var array */ public $keys = [ 'id', 'connection', 'queue', 'name', 'status', 'payload', 'exception', 'context', 'failed_at', 'completed_at', 'retried_by', 'reserved_at', ]; /** * The number of minutes until recently failed jobs should be purged. * * @var int */ public $recentFailedJobExpires; /** * The number of minutes until recent jobs should be purged. * * @var int */ public $recentJobExpires; /** * The number of minutes until pending jobs should be purged. * * @var int */ public $pendingJobExpires; /** * The number of minutes until completed and silenced jobs should be purged. * * @var int */ public $completedJobExpires; /** * The number of minutes until failed jobs should be purged. * * @var int */ public $failedJobExpires; /** * The number of minutes until monitored jobs should be purged. * * @var int */ public $monitoredJobExpires; /** * Create a new repository instance. * * @param \Illuminate\Contracts\Redis\Factory $redis * @return void */ public function __construct(RedisFactory $redis) { $this->redis = $redis; $this->recentJobExpires = config('horizon.trim.recent', 60); $this->pendingJobExpires = config('horizon.trim.pending', 60); $this->completedJobExpires = config('horizon.trim.completed', 60); $this->failedJobExpires = config('horizon.trim.failed', 10080); $this->recentFailedJobExpires = config('horizon.trim.recent_failed', $this->failedJobExpires); $this->monitoredJobExpires = config('horizon.trim.monitored', 10080); } /** * Get the next job ID that should be assigned. * * @return string */ public function nextJobId() { return (string) $this->connection()->incr('job_id'); } /** * Get the total count of recent jobs. * * @return int */ public function totalRecent() { return $this->connection()->zcard('recent_jobs'); } /** * Get the total count of failed jobs. * * @return int */ public function totalFailed() { return $this->connection()->zcard('failed_jobs'); } /** * Get a chunk of recent jobs. * * @param string|null $afterIndex * @return \Illuminate\Support\Collection */ public function getRecent($afterIndex = null) { return $this->getJobsByType('recent_jobs', $afterIndex); } /** * Get a chunk of failed jobs. * * @param string|null $afterIndex * @return \Illuminate\Support\Collection */ public function getFailed($afterIndex = null) { return $this->getJobsByType('failed_jobs', $afterIndex); } /** * Get a chunk of pending jobs. * * @param string|null $afterIndex * @return \Illuminate\Support\Collection */ public function getPending($afterIndex = null) { return $this->getJobsByType('pending_jobs', $afterIndex); } /** * Get a chunk of completed jobs. * * @param string|null $afterIndex * @return \Illuminate\Support\Collection */ public function getCompleted($afterIndex = null) { return $this->getJobsByType('completed_jobs', $afterIndex); } /** * Get a chunk of silenced jobs. * * @param string|null $afterIndex * @return \Illuminate\Support\Collection */ public function getSilenced($afterIndex = null) { return $this->getJobsByType('silenced_jobs', $afterIndex); } /** * Get the count of recent jobs. * * @return int */ public function countRecent() { return $this->countJobsByType('recent_jobs'); } /** * Get the count of failed jobs. * * @return int */ public function countFailed() { return $this->countJobsByType('failed_jobs'); } /** * Get the count of pending jobs. * * @return int */ public function countPending() { return $this->countJobsByType('pending_jobs'); } /** * Get the count of completed jobs. * * @return int */ public function countCompleted() { return $this->countJobsByType('completed_jobs'); } /** * Get the count of silenced jobs. * * @return int */ public function countSilenced() { return $this->countJobsByType('silenced_jobs'); } /** * Get the count of the recently failed jobs. * * @return int */ public function countRecentlyFailed() { return $this->countJobsByType('recent_failed_jobs'); } /** * Get a chunk of jobs from the given type set. * * @param string $type * @param string $afterIndex * @return \Illuminate\Support\Collection */ protected function getJobsByType($type, $afterIndex) { $afterIndex = $afterIndex === null ? -1 : $afterIndex; return $this->getJobs($this->connection()->zrange( $type, $afterIndex + 1, $afterIndex + 50 ), $afterIndex + 1); } /** * Get the number of jobs in a given type set. * * @param string $type * @return int */ protected function countJobsByType($type) { $minutes = $this->minutesForType($type); return $this->connection()->zcount( $type, '-inf', CarbonImmutable::now()->subMinutes($minutes)->getTimestamp() * -1 ); } /** * Get the number of minutes to count for a given type set. * * @param string $type * @return int */ protected function minutesForType($type) { return match ($type) { 'failed_jobs' => $this->failedJobExpires, 'recent_failed_jobs' => $this->recentFailedJobExpires, 'pending_jobs' => $this->pendingJobExpires, 'completed_jobs' => $this->completedJobExpires, 'silenced_jobs' => $this->completedJobExpires, default => $this->recentJobExpires, }; } /** * Retrieve the jobs with the given IDs. * * @param array $ids * @param mixed $indexFrom * @return \Illuminate\Support\Collection */ public function getJobs(array $ids, $indexFrom = 0) { $jobs = $this->connection()->pipeline(function ($pipe) use ($ids) { foreach ($ids as $id) { $pipe->hmget($id, $this->keys); } }); return $this->indexJobs(collect($jobs)->filter(function ($job) { $job = is_array($job) ? array_values($job) : null; return is_array($job) && $job[0] !== null && $job[0] !== false; })->values(), $indexFrom); } /** * Index the given jobs from the given index. * * @param \Illuminate\Support\Collection $jobs * @param int $indexFrom * @return \Illuminate\Support\Collection */ protected function indexJobs($jobs, $indexFrom) { return $jobs->map(function ($job) use (&$indexFrom) { $job = (object) array_combine($this->keys, $job); $job->index = $indexFrom; $indexFrom++; return $job; }); } /** * Insert the job into storage. * * @param string $connection * @param string $queue * @param \Laravel\Horizon\JobPayload $payload * @return void */ public function pushed($connection, $queue, JobPayload $payload) { $this->connection()->pipeline(function ($pipe) use ($connection, $queue, $payload) { $this->storeJobReference($pipe, 'recent_jobs', $payload); $this->storeJobReference($pipe, 'pending_jobs', $payload); $time = str_replace(',', '.', microtime(true)); $pipe->hmset($payload->id(), [ 'id' => $payload->id(), 'connection' => $connection, 'queue' => $queue, 'name' => $payload->decoded['displayName'], 'status' => 'pending', 'payload' => $payload->value, 'created_at' => $time, 'updated_at' => $time, ]); $pipe->expireat( $payload->id(), CarbonImmutable::now()->addMinutes($this->pendingJobExpires)->getTimestamp() ); }); } /** * Mark the job as reserved. * * @param string $connection * @param string $queue * @param \Laravel\Horizon\JobPayload $payload * @return void */ public function reserved($connection, $queue, JobPayload $payload) { $time = str_replace(',', '.', microtime(true)); $this->connection()->hmset( $payload->id(), [ 'status' => 'reserved', 'payload' => $payload->value, 'updated_at' => $time, 'reserved_at' => $time, ] ); } /** * Mark the job as released / pending. * * @param string $connection * @param string $queue * @param \Laravel\Horizon\JobPayload $payload * @return void */ public function released($connection, $queue, JobPayload $payload) { $this->connection()->hmset( $payload->id(), [ 'status' => 'pending', 'payload' => $payload->value, 'updated_at' => str_replace(',', '.', microtime(true)), ] ); } /** * Mark the job as completed and monitored. * * @param string $connection * @param string $queue * @param \Laravel\Horizon\JobPayload $payload * @return void */ public function remember($connection, $queue, JobPayload $payload) { $this->connection()->pipeline(function ($pipe) use ($connection, $queue, $payload) { $this->storeJobReference($pipe, 'monitored_jobs', $payload); $pipe->hmset( $payload->id(), [ 'id' => $payload->id(), 'connection' => $connection, 'queue' => $queue, 'name' => $payload->decoded['displayName'], 'status' => 'completed', 'payload' => $payload->value, 'completed_at' => str_replace(',', '.', microtime(true)), ] ); $pipe->expireat( $payload->id(), CarbonImmutable::now()->addMinutes($this->monitoredJobExpires)->getTimestamp() ); }); } /** * Mark the given jobs as released / pending. * * @param string $connection * @param string $queue * @param \Illuminate\Support\Collection $payloads * @return void */ public function migrated($connection, $queue, Collection $payloads) { $this->connection()->pipeline(function ($pipe) use ($payloads) { foreach ($payloads as $payload) { $pipe->hmset( $payload->id(), [ 'status' => 'pending', 'payload' => $payload->value, 'updated_at' => str_replace(',', '.', microtime(true)), ] ); } }); } /** * Handle the storage of a completed job. * * @param \Laravel\Horizon\JobPayload $payload * @param bool $failed * @param bool $silenced * @return void */ public function completed(JobPayload $payload, $failed = false, $silenced = false) { if ($payload->isRetry()) { $this->updateRetryInformationOnParent($payload, $failed); } $this->connection()->pipeline(function ($pipe) use ($payload, $silenced) { $this->storeJobReference($pipe, $silenced ? 'silenced_jobs' : 'completed_jobs', $payload); $this->removeJobReference($pipe, 'pending_jobs', $payload); $pipe->hmset( $payload->id(), [ 'status' => 'completed', 'completed_at' => str_replace(',', '.', microtime(true)), ] ); $pipe->expireat($payload->id(), CarbonImmutable::now()->addMinutes($this->completedJobExpires)->getTimestamp()); }); } /** * Update the retry status of a job's parent. * * @param \Laravel\Horizon\JobPayload $payload * @param bool $failed * @return void */ protected function updateRetryInformationOnParent(JobPayload $payload, $failed) { if ($retries = $this->connection()->hget($payload->retryOf(), 'retried_by')) { $retries = $this->updateRetryStatus( $payload, json_decode($retries, true), $failed ); $this->connection()->hset( $payload->retryOf(), 'retried_by', json_encode($retries) ); } } /** * Update the retry status of a job in a retry array. * * @param \Laravel\Horizon\JobPayload $payload * @param array $retries * @param bool $failed * @return array */ protected function updateRetryStatus(JobPayload $payload, $retries, $failed) { return collect($retries)->map(function ($retry) use ($payload, $failed) { return $retry['id'] === $payload->id() ? Arr::set($retry, 'status', $failed ? 'failed' : 'completed') : $retry; })->all(); } /** * Delete the given monitored jobs by IDs. * * @param array $ids * @return void */ public function deleteMonitored(array $ids) { $this->connection()->pipeline(function ($pipe) use ($ids) { foreach ($ids as $id) { $pipe->expireat($id, CarbonImmutable::now()->addDays(7)->getTimestamp()); } }); } /** * Trim the recent job list. * * @return void */ public function trimRecentJobs() { $this->connection()->pipeline(function ($pipe) { $pipe->zremrangebyscore( 'recent_jobs', CarbonImmutable::now()->subMinutes($this->recentJobExpires)->getTimestamp() * -1, '+inf' ); $pipe->zremrangebyscore( 'recent_failed_jobs', CarbonImmutable::now()->subMinutes($this->recentFailedJobExpires)->getTimestamp() * -1, '+inf' ); $pipe->zremrangebyscore( 'pending_jobs', CarbonImmutable::now()->subMinutes($this->pendingJobExpires)->getTimestamp() * -1, '+inf' ); $pipe->zremrangebyscore( 'completed_jobs', CarbonImmutable::now()->subMinutes($this->completedJobExpires)->getTimestamp() * -1, '+inf' ); $pipe->zremrangebyscore( 'silenced_jobs', CarbonImmutable::now()->subMinutes($this->completedJobExpires)->getTimestamp() * -1, '+inf' ); }); } /** * Trim the failed job list. * * @return void */ public function trimFailedJobs() { $this->connection()->zremrangebyscore( 'failed_jobs', CarbonImmutable::now()->subMinutes($this->failedJobExpires)->getTimestamp() * -1, '+inf' ); } /** * Trim the monitored job list. * * @return void */ public function trimMonitoredJobs() { $this->connection()->zremrangebyscore( 'monitored_jobs', CarbonImmutable::now()->subMinutes($this->monitoredJobExpires)->getTimestamp() * -1, '+inf' ); } /** * Find a failed job by ID. * * @param string $id * @return \stdClass|null */ public function findFailed($id) { $attributes = $this->connection()->hmget( $id, $this->keys ); $job = is_array($attributes) && $attributes[0] !== null ? (object) array_combine($this->keys, $attributes) : null; if ($job && $job->status !== 'failed') { return; } return $job; } /** * Mark the job as failed. * * @param \Exception $exception * @param string $connection * @param string $queue * @param \Laravel\Horizon\JobPayload $payload * @return void */ public function failed($exception, $connection, $queue, JobPayload $payload) { $this->connection()->pipeline(function ($pipe) use ($exception, $connection, $queue, $payload) { $this->storeJobReference($pipe, 'failed_jobs', $payload); $this->storeJobReference($pipe, 'recent_failed_jobs', $payload); $this->removeJobReference($pipe, 'pending_jobs', $payload); $this->removeJobReference($pipe, 'completed_jobs', $payload); $this->removeJobReference($pipe, 'silenced_jobs', $payload); $pipe->hmset( $payload->id(), [ 'id' => $payload->id(), 'connection' => $connection, 'queue' => $queue, 'name' => $payload->decoded['displayName'], 'status' => 'failed', 'payload' => $payload->value, 'exception' => (string) $exception, 'context' => method_exists($exception, 'context') ? json_encode($exception->context()) : null, 'failed_at' => str_replace(',', '.', microtime(true)), ] ); $pipe->expireat( $payload->id(), CarbonImmutable::now()->addMinutes($this->failedJobExpires)->getTimestamp() ); }); } /** * Store the look-up references for a job. * * @param mixed $pipe * @param string $key * @param \Laravel\Horizon\JobPayload $payload * @return void */ protected function storeJobReference($pipe, $key, JobPayload $payload) { $pipe->zadd($key, str_replace(',', '.', microtime(true) * -1), $payload->id()); } /** * Remove the look-up references for a job. * * @param mixed $pipe * @param string $key * @param \Laravel\Horizon\JobPayload $payload * @return void */ protected function removeJobReference($pipe, $key, JobPayload $payload) { $pipe->zrem($key, $payload->id()); } /** * Store the retry job ID on the original job record. * * @param string $id * @param string $retryId * @return void */ public function storeRetryReference($id, $retryId) { $retries = json_decode($this->connection()->hget($id, 'retried_by') ?: '[]'); $retries[] = [ 'id' => $retryId, 'status' => 'pending', 'retried_at' => CarbonImmutable::now()->getTimestamp(), ]; $this->connection()->hmset($id, ['retried_by' => json_encode($retries)]); } /** * Delete a failed job by ID. * * @param string $id * @return int */ public function deleteFailed($id) { return $this->connection()->zrem('failed_jobs', $id) != 1 ? 0 : $this->connection()->del($id); } /** * Delete pending and reserved jobs for a queue. * * @param string $queue * @return int */ public function purge($queue) { return $this->connection()->eval( LuaScripts::purge(), 2, 'recent_jobs', 'pending_jobs', config('horizon.prefix'), $queue ); } /** * Get the Redis connection instance. * * @return \Illuminate\Redis\Connections\Connection */ protected function connection() { return $this->redis->connection('horizon'); } } Repositories/RedisTagRepository.php 0000755 00000010262 00000000000 0013513 0 ustar 00 <?php namespace Laravel\Horizon\Repositories; use Illuminate\Contracts\Redis\Factory as RedisFactory; use Laravel\Horizon\Contracts\TagRepository; class RedisTagRepository implements TagRepository { /** * The Redis connection instance. * * @var \Illuminate\Contracts\Redis\Factory */ public $redis; /** * Create a new repository instance. * * @param \Illuminate\Contracts\Redis\Factory $redis * @return void */ public function __construct(RedisFactory $redis) { $this->redis = $redis; } /** * Get the currently monitored tags. * * @return array */ public function monitoring() { return (array) $this->connection()->smembers('monitoring'); } /** * Return the tags which are being monitored. * * @param array $tags * @return array */ public function monitored(array $tags) { return array_intersect($tags, $this->monitoring()); } /** * Start monitoring the given tag. * * @param string $tag * @return void */ public function monitor($tag) { $this->connection()->sadd('monitoring', $tag); } /** * Stop monitoring the given tag. * * @param string $tag * @return void */ public function stopMonitoring($tag) { $this->connection()->srem('monitoring', $tag); } /** * Store the tags for the given job. * * @param string $id * @param array $tags * @return void */ public function add($id, array $tags) { $this->connection()->pipeline(function ($pipe) use ($id, $tags) { foreach ($tags as $tag) { $pipe->zadd($tag, str_replace(',', '.', microtime(true)), $id); } }); } /** * Store the tags for the given job temporarily. * * @param int $minutes * @param string $id * @param array $tags * @return void */ public function addTemporary($minutes, $id, array $tags) { $this->connection()->pipeline(function ($pipe) use ($minutes, $id, $tags) { foreach ($tags as $tag) { $pipe->zadd($tag, str_replace(',', '.', microtime(true)), $id); $pipe->expire($tag, $minutes * 60); } }); } /** * Get the number of jobs matching a given tag. * * @param string $tag * @return int */ public function count($tag) { return $this->connection()->zcard($tag); } /** * Get all of the job IDs for a given tag. * * @param string $tag * @return array */ public function jobs($tag) { return (array) $this->connection()->zrange($tag, 0, -1); } /** * Paginate the job IDs for a given tag. * * @param string $tag * @param int $startingAt * @param int $limit * @return array */ public function paginate($tag, $startingAt = 0, $limit = 25) { $tags = (array) $this->connection()->zrevrange( $tag, $startingAt, $startingAt + $limit - 1 ); return collect($tags)->values()->mapWithKeys(function ($tag, $index) use ($startingAt) { return [$index + $startingAt => $tag]; })->all(); } /** * Remove the given job IDs from the given tag. * * @param array|string $tags * @param array|string $ids * @return void */ public function forgetJobs($tags, $ids) { $this->connection()->pipeline(function ($pipe) use ($tags, $ids) { foreach ((array) $tags as $tag) { foreach ((array) $ids as $id) { $pipe->zrem($tag, $id); } } }); } /** * Delete the given tag from storage. * * @param string $tag * @return void */ public function forget($tag) { $this->connection()->del($tag); } /** * Get the Redis connection instance. * * @return \Illuminate\Redis\Connections\Connection */ protected function connection() { return $this->redis->connection('horizon'); } } Repositories/RedisMetricsRepository.php 0000755 00000024616 00000000000 0014416 0 ustar 00 <?php namespace Laravel\Horizon\Repositories; use Carbon\CarbonImmutable; use Illuminate\Contracts\Redis\Factory as RedisFactory; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\MetricsRepository; use Laravel\Horizon\Lock; use Laravel\Horizon\LuaScripts; use Laravel\Horizon\WaitTimeCalculator; class RedisMetricsRepository implements MetricsRepository { /** * The Redis connection instance. * * @var \Illuminate\Contracts\Redis\Factory */ public $redis; /** * Create a new repository instance. * * @param \Illuminate\Contracts\Redis\Factory $redis * @return void */ public function __construct(RedisFactory $redis) { $this->redis = $redis; } /** * Get all of the class names that have metrics measurements. * * @return array */ public function measuredJobs() { $classes = (array) $this->connection()->smembers('measured_jobs'); return collect($classes)->map(function ($class) { return preg_match('/job:(.*)$/', $class, $matches) ? $matches[1] : $class; })->sort()->values()->all(); } /** * Get all of the queues that have metrics measurements. * * @return array */ public function measuredQueues() { $queues = (array) $this->connection()->smembers('measured_queues'); return collect($queues)->map(function ($class) { return preg_match('/queue:(.*)$/', $class, $matches) ? $matches[1] : $class; })->sort()->values()->all(); } /** * Get the jobs processed per minute since the last snapshot. * * @return float */ public function jobsProcessedPerMinute() { return round($this->throughput() / $this->minutesSinceLastSnapshot()); } /** * Get the application's total throughput since the last snapshot. * * @return int */ public function throughput() { return collect($this->measuredQueues())->reduce(function ($carry, $queue) { return $carry + $this->connection()->hget('queue:'.$queue, 'throughput'); }, 0); } /** * Get the throughput for a given job. * * @param string $job * @return int */ public function throughputForJob($job) { return $this->throughputFor('job:'.$job); } /** * Get the throughput for a given queue. * * @param string $queue * @return int */ public function throughputForQueue($queue) { return $this->throughputFor('queue:'.$queue); } /** * Get the throughput for a given key. * * @param string $key * @return int */ protected function throughputFor($key) { return (int) $this->connection()->hget($key, 'throughput'); } /** * Get the average runtime for a given job in milliseconds. * * @param string $job * @return float */ public function runtimeForJob($job) { return $this->runtimeFor('job:'.$job); } /** * Get the average runtime for a given queue in milliseconds. * * @param string $queue * @return float */ public function runtimeForQueue($queue) { return $this->runtimeFor('queue:'.$queue); } /** * Get the average runtime for a given key in milliseconds. * * @param string $key * @return float */ protected function runtimeFor($key) { return (float) $this->connection()->hget($key, 'runtime'); } /** * Get the queue that has the longest runtime. * * @return int */ public function queueWithMaximumRuntime() { return collect($this->measuredQueues())->sortBy(function ($queue) { if ($snapshots = $this->connection()->zrange('snapshot:queue:'.$queue, -1, 1)) { return json_decode($snapshots[0])->runtime; } })->last(); } /** * Get the queue that has the most throughput. * * @return int */ public function queueWithMaximumThroughput() { return collect($this->measuredQueues())->sortBy(function ($queue) { if ($snapshots = $this->connection()->zrange('snapshot:queue:'.$queue, -1, 1)) { return json_decode($snapshots[0])->throughput; } })->last(); } /** * Increment the metrics information for a job. * * @param string $job * @param float|null $runtime * @return void */ public function incrementJob($job, $runtime) { $this->connection()->eval(LuaScripts::updateMetrics(), 2, 'job:'.$job, 'measured_jobs', str_replace(',', '.', (string) $runtime) ); } /** * Increment the metrics information for a queue. * * @param string $queue * @param float|null $runtime * @return void */ public function incrementQueue($queue, $runtime) { $this->connection()->eval(LuaScripts::updateMetrics(), 2, 'queue:'.$queue, 'measured_queues', str_replace(',', '.', (string) $runtime) ); } /** * Get all of the snapshots for the given job. * * @param string $job * @return array */ public function snapshotsForJob($job) { return $this->snapshotsFor('job:'.$job); } /** * Get all of the snapshots for the given queue. * * @param string $queue * @return array */ public function snapshotsForQueue($queue) { return $this->snapshotsFor('queue:'.$queue); } /** * Get all of the snapshots for the given key. * * @param string $key * @return array */ protected function snapshotsFor($key) { return collect($this->connection()->zrange('snapshot:'.$key, 0, -1)) ->map(function ($snapshot) { return (object) json_decode($snapshot, true); })->values()->all(); } /** * Store a snapshot of the metrics information. * * @return void */ public function snapshot() { collect($this->measuredJobs())->each(function ($job) { $this->storeSnapshotForJob($job); }); collect($this->measuredQueues())->each(function ($queue) { $this->storeSnapshotForQueue($queue); }); $this->storeSnapshotTimestamp(); } /** * Store a snapshot for the given job. * * @param string $job * @return void */ protected function storeSnapshotForJob($job) { $data = $this->baseSnapshotData($key = 'job:'.$job); $this->connection()->zadd( 'snapshot:'.$key, $time = CarbonImmutable::now()->getTimestamp(), json_encode([ 'throughput' => $data['throughput'], 'runtime' => $data['runtime'], 'time' => $time, ]) ); $this->connection()->zremrangebyrank( 'snapshot:'.$key, 0, -abs(1 + config('horizon.metrics.trim_snapshots.job', 24)) ); } /** * Store a snapshot for the given queue. * * @param string $queue * @return void */ protected function storeSnapshotForQueue($queue) { $data = $this->baseSnapshotData($key = 'queue:'.$queue); $this->connection()->zadd( 'snapshot:'.$key, $time = CarbonImmutable::now()->getTimestamp(), json_encode([ 'throughput' => $data['throughput'], 'runtime' => $data['runtime'], 'wait' => app(WaitTimeCalculator::class)->calculateFor($queue), 'time' => $time, ]) ); $this->connection()->zremrangebyrank( 'snapshot:'.$key, 0, -abs(1 + config('horizon.metrics.trim_snapshots.queue', 24)) ); } /** * Get the base snapshot data for a given key. * * @param string $key * @return array */ protected function baseSnapshotData($key) { $responses = $this->connection()->transaction(function ($trans) use ($key) { $trans->hmget($key, ['throughput', 'runtime']); $trans->del($key); }); $snapshot = array_values($responses[0]); return [ 'throughput' => $snapshot[0], 'runtime' => $snapshot[1], ]; } /** * Get the number of minutes passed since the last snapshot. * * @return float */ protected function minutesSinceLastSnapshot() { $lastSnapshotAt = (int) ($this->connection()->get('last_snapshot_at') ?: $this->storeSnapshotTimestamp()); return max( (CarbonImmutable::now()->getTimestamp() - $lastSnapshotAt) / 60, 1 ); } /** * Store the current timestamp as the "last snapshot timestamp". * * @return int */ protected function storeSnapshotTimestamp() { return tap(CarbonImmutable::now()->getTimestamp(), function ($timestamp) { $this->connection()->set('last_snapshot_at', $timestamp); }); } /** * Attempt to acquire a lock to monitor the queue wait times. * * @return bool */ public function acquireWaitTimeMonitorLock() { return app(Lock::class)->get('monitor:time-to-clear'); } /** * Clear the metrics for a key. * * @param string $key * @return void */ public function forget($key) { $this->connection()->del($key); } /** * Delete all stored metrics information. * * @return void */ public function clear() { $this->forget('last_snapshot_at'); $this->forget('measured_jobs'); $this->forget('measured_queues'); $this->forget('metrics:snapshot'); foreach (['queue:*', 'job:*', 'snapshot:*'] as $pattern) { $cursor = null; do { [$cursor, $keys] = $this->connection()->scan( $cursor, ['match' => config('horizon.prefix').$pattern] ); foreach ($keys ?? [] as $key) { $this->forget(Str::after($key, config('horizon.prefix'))); } } while ($cursor > 0); } } /** * Get the Redis connection instance. * * @return \Illuminate\Redis\Connections\Connection */ public function connection() { return $this->redis->connection('horizon'); } } HorizonServiceProvider.php 0000755 00000012121 00000000000 0011702 0 ustar 00 <?php namespace Laravel\Horizon; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Foundation\CachesRoutes; use Illuminate\Queue\QueueManager; use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; use Laravel\Horizon\Connectors\RedisConnector; class HorizonServiceProvider extends ServiceProvider { use EventMap, ServiceBindings; /** * Bootstrap any application services. * * @return void */ public function boot() { $this->registerEvents(); $this->registerRoutes(); $this->registerResources(); $this->offerPublishing(); $this->registerCommands(); } /** * Register the Horizon job events. * * @return void */ protected function registerEvents() { $events = $this->app->make(Dispatcher::class); foreach ($this->events as $event => $listeners) { foreach ($listeners as $listener) { $events->listen($event, $listener); } } } /** * Register the Horizon routes. * * @return void */ protected function registerRoutes() { if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) { return; } Route::group([ 'domain' => config('horizon.domain', null), 'prefix' => config('horizon.path'), 'namespace' => 'Laravel\Horizon\Http\Controllers', 'middleware' => config('horizon.middleware', 'web'), ], function () { $this->loadRoutesFrom(__DIR__.'/../routes/web.php'); }); } /** * Register the Horizon resources. * * @return void */ protected function registerResources() { $this->loadViewsFrom(__DIR__.'/../resources/views', 'horizon'); } /** * Setup the resource publishing groups for Horizon. * * @return void */ protected function offerPublishing() { if ($this->app->runningInConsole()) { $this->publishes([ __DIR__.'/../stubs/HorizonServiceProvider.stub' => app_path('Providers/HorizonServiceProvider.php'), ], 'horizon-provider'); $this->publishes([ __DIR__.'/../config/horizon.php' => config_path('horizon.php'), ], 'horizon-config'); } } /** * Register the Horizon Artisan commands. * * @return void */ protected function registerCommands() { if ($this->app->runningInConsole()) { $this->commands([ Console\ClearCommand::class, Console\ClearMetricsCommand::class, Console\ContinueCommand::class, Console\ContinueSupervisorCommand::class, Console\ForgetFailedCommand::class, Console\HorizonCommand::class, Console\InstallCommand::class, Console\ListCommand::class, Console\PauseCommand::class, Console\PauseSupervisorCommand::class, Console\PublishCommand::class, Console\PurgeCommand::class, Console\StatusCommand::class, Console\SupervisorCommand::class, Console\SupervisorStatusCommand::class, Console\SupervisorsCommand::class, Console\TerminateCommand::class, Console\TimeoutCommand::class, Console\WorkCommand::class, ]); } $this->commands([Console\SnapshotCommand::class]); } /** * Register any application services. * * @return void */ public function register() { if (! defined('HORIZON_PATH')) { define('HORIZON_PATH', realpath(__DIR__.'/../')); } $this->app->bind(Console\WorkCommand::class, function ($app) { return new Console\WorkCommand($app['queue.worker'], $app['cache.store']); }); $this->configure(); $this->registerServices(); $this->registerQueueConnectors(); } /** * Setup the configuration for Horizon. * * @return void */ protected function configure() { $this->mergeConfigFrom( __DIR__.'/../config/horizon.php', 'horizon' ); Horizon::use(config('horizon.use', 'default')); } /** * Register Horizon's services in the container. * * @return void */ protected function registerServices() { foreach ($this->serviceBindings as $key => $value) { is_numeric($key) ? $this->app->singleton($value) : $this->app->singleton($key, $value); } } /** * Register the custom queue connectors for Horizon. * * @return void */ protected function registerQueueConnectors() { $this->callAfterResolving(QueueManager::class, function ($manager) { $manager->addConnector('redis', function () { return new RedisConnector($this->app['redis']); }); }); } } Listeners/MonitorMasterSupervisorMemory.php 0000755 00000001467 00000000000 0015277 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Events\MasterSupervisorLooped; use Laravel\Horizon\Events\MasterSupervisorOutOfMemory; class MonitorMasterSupervisorMemory { /** * Handle the event. * * @param \Laravel\Horizon\Events\MasterSupervisorLooped $event * @return void */ public function handle(MasterSupervisorLooped $event) { $master = $event->master; $memoryLimit = config('horizon.memory_limit', 64); if ($master->memoryUsage() > $memoryLimit) { event(new MasterSupervisorOutOfMemory($master)); $master->output('error', 'Memory limit exceeded: Using '.ceil($master->memoryUsage()).'/'.$memoryLimit.'MB. Consider increasing horizon.memory_limit.'); $master->terminate(12); } } } Listeners/ExpireSupervisors.php 0000755 00000001066 00000000000 0012715 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Contracts\MasterSupervisorRepository; use Laravel\Horizon\Contracts\SupervisorRepository; use Laravel\Horizon\Events\MasterSupervisorLooped; class ExpireSupervisors { /** * Handle the event. * * @param \Laravel\Horizon\Events\MasterSupervisorLooped $event * @return void */ public function handle(MasterSupervisorLooped $event) { app(MasterSupervisorRepository::class)->flushExpired(); app(SupervisorRepository::class)->flushExpired(); } } Listeners/PruneTerminatingProcesses.php 0000755 00000000574 00000000000 0014361 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Events\SupervisorLooped; class PruneTerminatingProcesses { /** * Handle the event. * * @param \Laravel\Horizon\Events\SupervisorLooped $event * @return void */ public function handle(SupervisorLooped $event) { $event->supervisor->pruneTerminatingProcesses(); } } Listeners/MarkJobAsReserved.php 0000755 00000001451 00000000000 0012503 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Events\JobReserved; class MarkJobAsReserved { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * Create a new listener instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @return void */ public function __construct(JobRepository $jobs) { $this->jobs = $jobs; } /** * Handle the event. * * @param \Laravel\Horizon\Events\JobReserved $event * @return void */ public function handle(JobReserved $event) { $this->jobs->reserved($event->connectionName, $event->queue, $event->payload); } } Listeners/MarkJobAsFailed.php 0000755 00000001524 00000000000 0012111 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Events\JobFailed; class MarkJobAsFailed { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * Create a new listener instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @return void */ public function __construct(JobRepository $jobs) { $this->jobs = $jobs; } /** * Handle the event. * * @param \Laravel\Horizon\Events\JobFailed $event * @return void */ public function handle(JobFailed $event) { $this->jobs->failed( $event->exception, $event->connectionName, $event->queue, $event->payload ); } } Listeners/MonitorWaitTimes.php 0000755 00000006165 00000000000 0012457 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Carbon\CarbonImmutable; use Laravel\Horizon\Contracts\MetricsRepository; use Laravel\Horizon\Events\LongWaitDetected; use Laravel\Horizon\WaitTimeCalculator; class MonitorWaitTimes { /** * The metrics repository implementation. * * @var \Laravel\Horizon\Contracts\MetricsRepository */ public $metrics; /** * The time at which we last checked if monitoring was due. * * @var \Carbon\CarbonImmutable */ public $lastMonitored; /** * Create a new listener instance. * * @param \Laravel\Horizon\Contracts\MetricsRepository $metrics * @return void */ public function __construct(MetricsRepository $metrics) { $this->metrics = $metrics; } /** * Handle the event. * * @param \Laravel\Horizon\Events\SupervisorLooped $event * @return void */ public function handle() { if (! $this->dueToMonitor()) { return; } // Here we will calculate the wait time in seconds for each of the queues that // the application is working. Then, we will filter the results to find the // queues with the longest wait times and raise events for each of these. $results = app(WaitTimeCalculator::class)->calculate(); $long = collect($results)->filter(function ($wait, $queue) { return config("horizon.waits.{$queue}") !== 0 && $wait > (config("horizon.waits.{$queue}") ?? 60); }); // Once we have determined which queues have long wait times we will raise the // events for each of the queues. We'll need to separate the connection and // queue names into their own strings before we will fire off the events. $long->each(function ($wait, $queue) { [$connection, $queue] = explode(':', $queue, 2); event(new LongWaitDetected($connection, $queue, $wait)); }); } /** * Determine if monitoring is due. * * @return bool */ protected function dueToMonitor() { // We will keep track of the amount of time between attempting to acquire the // lock to monitor the wait times. We only want a single supervisor to run // the checks on a given interval so that we don't fire too many events. if (! $this->lastMonitored) { $this->lastMonitored = CarbonImmutable::now(); } if (! $this->timeToMonitor()) { return false; } // Next we will update the monitor timestamp and attempt to acquire a lock to // check the wait times. We use Redis to do it in order to have the atomic // operation required. This will avoid any deadlocks or race conditions. $this->lastMonitored = CarbonImmutable::now(); return $this->metrics->acquireWaitTimeMonitorLock(); } /** * Determine if enough time has elapsed to attempt to monitor. * * @return bool */ protected function timeToMonitor() { return CarbonImmutable::now()->subMinutes(1)->lte($this->lastMonitored); } } Listeners/TrimMonitoredJobs.php 0000755 00000002206 00000000000 0012603 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Carbon\CarbonImmutable; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Events\MasterSupervisorLooped; class TrimMonitoredJobs { /** * The last time the monitored jobs were trimmed. * * @var \Carbon\CarbonImmutable */ public $lastTrimmed; /** * How many minutes to wait in between each trim. * * @var int */ public $frequency = 1440; /** * Handle the event. * * @param \Laravel\Horizon\Events\MasterSupervisorLooped $event * @return void */ public function handle(MasterSupervisorLooped $event) { if (! isset($this->lastTrimmed)) { $this->frequency = max(1, intdiv( config('horizon.trim.monitored', 10080), 12 )); $this->lastTrimmed = CarbonImmutable::now()->subMinutes($this->frequency + 1); } if ($this->lastTrimmed->lte(CarbonImmutable::now()->subMinutes($this->frequency))) { app(JobRepository::class)->trimMonitoredJobs(); $this->lastTrimmed = CarbonImmutable::now(); } } } Listeners/TrimRecentJobs.php 0000755 00000001777 00000000000 0012077 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Carbon\CarbonImmutable; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Events\MasterSupervisorLooped; class TrimRecentJobs { /** * The last time the recent jobs were trimmed. * * @var \Carbon\CarbonImmutable */ public $lastTrimmed; /** * How many minutes to wait in between each trim. * * @var int */ public $frequency = 1; /** * Handle the event. * * @param \Laravel\Horizon\Events\MasterSupervisorLooped $event * @return void */ public function handle(MasterSupervisorLooped $event) { if (! isset($this->lastTrimmed)) { $this->lastTrimmed = CarbonImmutable::now()->subMinutes($this->frequency + 1); } if ($this->lastTrimmed->lte(CarbonImmutable::now()->subMinutes($this->frequency))) { app(JobRepository::class)->trimRecentJobs(); $this->lastTrimmed = CarbonImmutable::now(); } } } Listeners/MarshalFailedEvent.php 0000755 00000002125 00000000000 0012667 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Queue\Events\JobFailed as LaravelJobFailed; use Illuminate\Queue\Jobs\RedisJob; use Laravel\Horizon\Events\JobFailed; class MarshalFailedEvent { /** * The event dispatcher implementation. * * @var \Illuminate\Contracts\Events\Dispatcher */ public $events; /** * Create a new listener instance. * * @param \Illuminate\Contracts\Events\Dispatcher $events * @return void */ public function __construct(Dispatcher $events) { $this->events = $events; } /** * Handle the event. * * @param \Illuminate\Queue\Events\JobFailed $event * @return void */ public function handle(LaravelJobFailed $event) { if (! $event->job instanceof RedisJob) { return; } $this->events->dispatch((new JobFailed( $event->exception, $event->job, $event->job->getReservedJob() ))->connection($event->connectionName)->queue($event->job->getQueue())); } } Listeners/StartTimingJob.php 0000755 00000001317 00000000000 0012073 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Events\JobReserved; use Laravel\Horizon\Stopwatch; class StartTimingJob { /** * The stopwatch instance. * * @var \Laravel\Horizon\Stopwatch */ public $watch; /** * Create a new listener instance. * * @param \Laravel\Horizon\Stopwatch $watch * @return void */ public function __construct(Stopwatch $watch) { $this->watch = $watch; } /** * Handle the event. * * @param \Laravel\Horizon\Events\JobReserved $event * @return void */ public function handle(JobReserved $event) { $this->watch->start($event->payload->id()); } } Listeners/StoreMonitoredTags.php 0000755 00000001605 00000000000 0012767 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Contracts\TagRepository; use Laravel\Horizon\Events\JobPushed; class StoreMonitoredTags { /** * The tag repository implementation. * * @var \Laravel\Horizon\Contracts\TagRepository */ public $tags; /** * Create a new listener instance. * * @param \Laravel\Horizon\Contracts\TagRepository $tags * @return void */ public function __construct(TagRepository $tags) { $this->tags = $tags; } /** * Handle the event. * * @param \Laravel\Horizon\Events\JobPushed $event * @return void */ public function handle(JobPushed $event) { $monitoring = $this->tags->monitored($event->payload->tags()); if (! empty($monitoring)) { $this->tags->add($event->payload->id(), $monitoring); } } } Listeners/MarkJobAsReleased.php 0000755 00000001451 00000000000 0012450 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Events\JobReleased; class MarkJobAsReleased { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * Create a new listener instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @return void */ public function __construct(JobRepository $jobs) { $this->jobs = $jobs; } /** * Handle the event. * * @param \Laravel\Horizon\Events\JobReleased $event * @return void */ public function handle(JobReleased $event) { $this->jobs->released($event->connectionName, $event->queue, $event->payload); } } Listeners/MonitorSupervisorMemory.php 0000755 00000001125 00000000000 0014112 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Events\SupervisorLooped; use Laravel\Horizon\Events\SupervisorOutOfMemory; class MonitorSupervisorMemory { /** * Handle the event. * * @param \Laravel\Horizon\Events\SupervisorLooped $event * @return void */ public function handle(SupervisorLooped $event) { $supervisor = $event->supervisor; if ($supervisor->memoryUsage() > $supervisor->options->memory) { event(new SupervisorOutOfMemory($supervisor)); $supervisor->terminate(12); } } } Listeners/ForgetJobTimer.php 0000755 00000001313 00000000000 0012051 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Stopwatch; class ForgetJobTimer { /** * The stopwatch instance. * * @var \Laravel\Horizon\Stopwatch */ public $watch; /** * Create a new listener instance. * * @param \Laravel\Horizon\Stopwatch $watch * @return void */ public function __construct(Stopwatch $watch) { $this->watch = $watch; } /** * Handle the event. * * @param \Illuminate\Queue\Events\JobExceptionOccurred|\Illuminate\Queue\Events\JobFailed $event * @return void */ public function handle($event) { $this->watch->forget($event->job->getJobId()); } } Listeners/UpdateJobMetrics.php 0000755 00000002551 00000000000 0012400 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Contracts\MetricsRepository; use Laravel\Horizon\Events\JobDeleted; use Laravel\Horizon\Stopwatch; class UpdateJobMetrics { /** * The metrics repository implementation. * * @var \Laravel\Horizon\Contracts\MetricsRepository */ public $metrics; /** * The stopwatch instance. * * @var \Laravel\Horizon\Stopwatch */ public $watch; /** * Create a new listener instance. * * @param \Laravel\Horizon\Contracts\MetricsRepository $metrics * @param \Laravel\Horizon\Stopwatch $watch * @return void */ public function __construct(MetricsRepository $metrics, Stopwatch $watch) { $this->watch = $watch; $this->metrics = $metrics; } /** * Stop gathering metrics for a job. * * @param \Laravel\Horizon\Events\JobDeleted $event * @return void */ public function handle(JobDeleted $event) { if ($event->job->hasFailed()) { return; } $time = $this->watch->check($id = $event->payload->id()) ?: 0; $this->metrics->incrementQueue( $event->job->getQueue(), $time ); $this->metrics->incrementJob( $event->payload->displayName(), $time ); $this->watch->forget($id); } } Listeners/SendNotification.php 0000755 00000001303 00000000000 0012426 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Illuminate\Support\Facades\Notification; use Laravel\Horizon\Horizon; use Laravel\Horizon\Lock; class SendNotification { /** * Handle the event. * * @param mixed $event * @return void */ public function handle($event) { $notification = $event->toNotification(); if (! app(Lock::class)->get('notification:'.$notification->signature(), 300)) { return; } Notification::route('slack', Horizon::$slackWebhookUrl) ->route('nexmo', Horizon::$smsNumber) ->route('mail', Horizon::$email) ->notify($notification); } } Listeners/StoreJob.php 0000755 00000001456 00000000000 0010726 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Events\JobPushed; class StoreJob { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * Create a new listener instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @return void */ public function __construct(JobRepository $jobs) { $this->jobs = $jobs; } /** * Handle the event. * * @param \Laravel\Horizon\Events\JobPushed $event * @return void */ public function handle(JobPushed $event) { $this->jobs->pushed( $event->connectionName, $event->queue, $event->payload ); } } Listeners/MarkJobAsComplete.php 0000755 00000002456 00000000000 0012502 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Contracts\TagRepository; use Laravel\Horizon\Events\JobDeleted; class MarkJobAsComplete { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * The tag repository implementation. * * @var \Laravel\Horizon\Contracts\TagRepository */ public $tags; /** * Create a new listener instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @param \Laravel\Horizon\Contracts\TagRepository $tags * @return void */ public function __construct(JobRepository $jobs, TagRepository $tags) { $this->jobs = $jobs; $this->tags = $tags; } /** * Handle the event. * * @param \Laravel\Horizon\Events\JobDeleted $event * @return void */ public function handle(JobDeleted $event) { $this->jobs->completed($event->payload, $event->job->hasFailed(), $event->payload->isSilenced()); if (! $event->job->hasFailed() && count($this->tags->monitored($event->payload->tags())) > 0) { $this->jobs->remember($event->connectionName, $event->queue, $event->payload); } } } Listeners/MarkJobsAsMigrated.php 0000755 00000001456 00000000000 0012650 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Events\JobsMigrated; class MarkJobsAsMigrated { /** * The job repository implementation. * * @var \Laravel\Horizon\Contracts\JobRepository */ public $jobs; /** * Create a new listener instance. * * @param \Laravel\Horizon\Contracts\JobRepository $jobs * @return void */ public function __construct(JobRepository $jobs) { $this->jobs = $jobs; } /** * Handle the event. * * @param \Laravel\Horizon\Events\JobsMigrated $event * @return void */ public function handle(JobsMigrated $event) { $this->jobs->migrated($event->connectionName, $event->queue, $event->payloads); } } Listeners/TrimFailedJobs.php 0000755 00000002172 00000000000 0012031 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Carbon\CarbonImmutable; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Events\MasterSupervisorLooped; class TrimFailedJobs { /** * The last time the recent jobs were trimmed. * * @var \Carbon\CarbonImmutable */ public $lastTrimmed; /** * How many minutes to wait in between each trim. * * @var int */ public $frequency = 5040; /** * Handle the event. * * @param \Laravel\Horizon\Events\MasterSupervisorLooped $event * @return void */ public function handle(MasterSupervisorLooped $event) { if (! isset($this->lastTrimmed)) { $this->frequency = max(1, intdiv( config('horizon.trim.failed', 10080), 12 )); $this->lastTrimmed = CarbonImmutable::now()->subMinutes($this->frequency + 1); } if ($this->lastTrimmed->lte(CarbonImmutable::now()->subMinutes($this->frequency))) { app(JobRepository::class)->trimFailedJobs(); $this->lastTrimmed = CarbonImmutable::now(); } } } Listeners/StoreTagsForFailedJob.php 0000755 00000001713 00000000000 0013315 0 ustar 00 <?php namespace Laravel\Horizon\Listeners; use Laravel\Horizon\Contracts\TagRepository; use Laravel\Horizon\Events\JobFailed; class StoreTagsForFailedJob { /** * The tag repository implementation. * * @var \Laravel\Horizon\Contracts\TagRepository */ public $tags; /** * Create a new listener instance. * * @param \Laravel\Horizon\Contracts\TagRepository $tags * @return void */ public function __construct(TagRepository $tags) { $this->tags = $tags; } /** * Handle the event. * * @param \Laravel\Horizon\Events\JobFailed $event * @return void */ public function handle(JobFailed $event) { $tags = collect($event->payload->tags())->map(function ($tag) { return 'failed:'.$tag; })->all(); $this->tags->addTemporary( config('horizon.trim.failed', 2880), $event->payload->id(), $tags ); } } Stopwatch.php 0000755 00000001516 00000000000 0007200 0 ustar 00 <?php namespace Laravel\Horizon; class Stopwatch { /** * All of the current timers. * * @var array */ public $timers = []; /** * Start a new timer. * * @param string $key * @return void */ public function start($key) { $this->timers[$key] = microtime(true); } /** * Check a given timer and get the elapsed time in milliseconds. * * @param string $key * @return float|null */ public function check($key) { if (isset($this->timers[$key])) { return round((microtime(true) - $this->timers[$key]) * 1000, 2); } } /** * Forget a given timer. * * @param string $key * @return void */ public function forget($key) { unset($this->timers[$key]); } } WorkerProcess.php 0000755 00000010145 00000000000 0010032 0 ustar 00 <?php namespace Laravel\Horizon; use Carbon\CarbonImmutable; use Closure; use Laravel\Horizon\Events\UnableToLaunchProcess; use Laravel\Horizon\Events\WorkerProcessRestarting; use Symfony\Component\Process\Exception\ExceptionInterface; class WorkerProcess { /** * The underlying Symfony process. * * @var \Symfony\Component\Process\Process */ public $process; /** * The output handler callback. * * @var \Closure */ public $output; /** * The time at which the cooldown period will be over. * * @var \Carbon\CarbonImmutable */ public $restartAgainAt; /** * Create a new worker process instance. * * @param \Symfony\Component\Process\Process $process * @return void */ public function __construct($process) { $this->process = $process; } /** * Start the process. * * @param \Closure $callback * @return $this */ public function start(Closure $callback) { $this->output = $callback; $this->cooldown(); $this->process->start($callback); return $this; } /** * Pause the worker process. * * @return void */ public function pause() { $this->sendSignal(SIGUSR2); } /** * Instruct the worker process to continue working. * * @return void */ public function continue() { $this->sendSignal(SIGCONT); } /** * Evaluate the current state of the process. * * @return void */ public function monitor() { if ($this->process->isRunning() || $this->coolingDown()) { return; } $this->restart(); } /** * Restart the process. * * @return void */ protected function restart() { if ($this->process->isStarted()) { event(new WorkerProcessRestarting($this)); } $this->start($this->output); } /** * Terminate the underlying process. * * @return void */ public function terminate() { $this->sendSignal(SIGTERM); } /** * Stop the underlying process. * * @return void */ public function stop() { if ($this->process->isRunning()) { $this->process->stop(); } } /** * Send a POSIX signal to the process. * * @param int $signal * @return void */ protected function sendSignal($signal) { try { $this->process->signal($signal); } catch (ExceptionInterface $e) { if ($this->process->isRunning()) { throw $e; } } } /** * Begin the cool-down period for the process. * * @return void */ protected function cooldown() { if ($this->coolingDown()) { return; } if ($this->restartAgainAt) { $this->restartAgainAt = ! $this->process->isRunning() ? CarbonImmutable::now()->addMinute() : null; if (! $this->process->isRunning()) { event(new UnableToLaunchProcess($this)); } } else { $this->restartAgainAt = CarbonImmutable::now()->addSecond(); } } /** * Determine if the process is cooling down from a failed restart. * * @return bool */ public function coolingDown() { return isset($this->restartAgainAt) && CarbonImmutable::now()->lt($this->restartAgainAt); } /** * Set the output handler. * * @param \Closure $callback * @return $this */ public function handleOutputUsing(Closure $callback) { $this->output = $callback; return $this; } /** * Pass on method calls to the underlying process. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { return $this->process->{$method}(...$parameters); } } SupervisorOptions.php 0000755 00000020756 00000000000 0010770 0 ustar 00 <?php namespace Laravel\Horizon; class SupervisorOptions { /** * The name of the supervisor. * * @var string */ public $name; /** * The name of the workers. * * @var string */ public $workersName; /** * The queue connection that should be utilized. * * @var string */ public $connection; /** * The queue that should be utilized. * * @var string */ public $queue; /** * Indicates the balancing strategy the supervisor should use. * * @var bool */ public $balance = 'off'; /** * Indicates whether auto-scaling strategy should use "time" (time-to-complete) or "size" (total count of jobs) strategies. * * @var string|null */ public $autoScalingStrategy = null; /** * Indicates if the supervisor should auto-scale. * * @var bool */ public $autoScale; /** * The maximum number of total processes to start when auto-scaling. * * @var int */ public $maxProcesses = 1; /** * The minimum number of processes to assign per working when auto-scaling. * * @var int */ public $minProcesses = 1; /** * The parent process identifier. * * @var int */ public $parentId = 0; /** * The process priority. * * @var int */ public $nice = 0; /** * The working directories that new workers should be started from. * * @var string */ public $directory; /** * The number of seconds to wait in between auto-scaling attempts. * * @var int */ public $balanceCooldown = 3; /** * The maximum number of processes to increase or decrease per one scaling. * * @var int */ public $balanceMaxShift = 1; /** * The number of seconds to wait before retrying a job that encountered an uncaught exception. * * @var int */ public $backoff; /** * The maximum number of jobs to run. * * @var int */ public $maxJobs; /** * The maximum number of seconds a worker may live. * * @var int */ public $maxTime; /** * The maximum amount of RAM the worker may consume. * * @var int */ public $memory; /** * The maximum number of seconds a child worker may run. * * @var int */ public $timeout; /** * The number of seconds to wait in between polling the queue. * * @var int */ public $sleep; /** * The maximum amount of times a job may be attempted. * * @var int */ public $maxTries; /** * Indicates if the worker should run in maintenance mode. * * @var bool */ public $force; /** * The number of seconds to rest between jobs. * * @var int */ public $rest; /** * Create a new worker options instance. * * @param string $name * @param string $connection * @param string $queue * @param string $workersName * @param string $balance * @param int $backoff * @param int $maxTime * @param int $maxJobs * @param int $maxProcesses * @param int $minProcesses * @param int $memory * @param int $timeout * @param int $sleep * @param int $maxTries * @param bool $force * @param int $nice * @param int $balanceCooldown * @param int $balanceMaxShift * @param int $parentId * @param int $rest * @param string|null $autoScalingStrategy */ public function __construct($name, $connection, $queue = null, $workersName = 'default', $balance = 'off', $backoff = 0, $maxTime = 0, $maxJobs = 0, $maxProcesses = 1, $minProcesses = 1, $memory = 128, $timeout = 60, $sleep = 3, $maxTries = 0, $force = false, $nice = 0, $balanceCooldown = 3, $balanceMaxShift = 1, $parentId = 0, $rest = 0, $autoScalingStrategy = 'time' ) { $this->name = $name; $this->connection = $connection; $this->queue = $queue ?: config('queue.connections.'.$connection.'.queue'); $this->workersName = $workersName; $this->balance = $balance; $this->backoff = $backoff; $this->maxTime = $maxTime; $this->maxJobs = $maxJobs; $this->maxProcesses = $maxProcesses; $this->minProcesses = $minProcesses; $this->memory = $memory; $this->timeout = $timeout; $this->sleep = $sleep; $this->maxTries = $maxTries; $this->force = $force; $this->nice = $nice; $this->balanceCooldown = $balanceCooldown; $this->balanceMaxShift = $balanceMaxShift; $this->parentId = $parentId; $this->rest = $rest; $this->autoScalingStrategy = $autoScalingStrategy; } /** * Create a fresh options instance with the given queue. * * @param string $queue * @return static */ public function withQueue($queue) { return tap(clone $this, function ($options) use ($queue) { $options->queue = $queue; }); } /** * Determine if a balancing strategy should be used. * * @return bool */ public function balancing() { return in_array($this->balance, ['simple', 'auto']); } /** * Determine if auto-scaling should be applied. * * @return bool */ public function autoScaling() { return $this->balance !== 'simple'; } /** * Determine if auto-scaling should be based on the number of jobs on the queue instead of time-to-clear. * * @return bool */ public function autoScaleByNumberOfJobs() { return $this->autoScalingStrategy === 'size'; } /** * Get the command-line representation of the options for a supervisor. * * @return string */ public function toSupervisorCommand() { return SupervisorCommandString::fromOptions($this); } /** * Get the command-line representation of the options for a worker. * * @return string */ public function toWorkerCommand() { return WorkerCommandString::fromOptions($this); } /** * Convert the options to a JSON string. * * @return string */ public function toJson() { return json_encode($this->toArray()); } /** * Convert the options to a raw array. * * @return array */ public function toArray() { return [ 'balance' => $this->balance, 'connection' => $this->connection, 'queue' => $this->queue, 'backoff' => $this->backoff, 'force' => $this->force, 'maxProcesses' => $this->maxProcesses, 'minProcesses' => $this->minProcesses, 'maxTries' => $this->maxTries, 'maxTime' => $this->maxTime, 'maxJobs' => $this->maxJobs, 'memory' => $this->memory, 'nice' => $this->nice, 'name' => $this->name, 'workersName' => $this->workersName, 'sleep' => $this->sleep, 'timeout' => $this->timeout, 'balanceCooldown' => $this->balanceCooldown, 'balanceMaxShift' => $this->balanceMaxShift, 'parentId' => $this->parentId, 'rest' => $this->rest, 'autoScalingStrategy' => $this->autoScalingStrategy, ]; } /** * Create a new options instance from the given array. * * @param array $array * @return static */ public static function fromArray(array $array) { return tap(new static($array['name'], $array['connection']), function ($options) use ($array) { foreach ($array as $key => $value) { $options->{$key} = $value; } }); } } MasterSupervisorCommands/AddSupervisor.php 0000755 00000002466 00000000000 0015042 0 ustar 00 <?php namespace Laravel\Horizon\MasterSupervisorCommands; use Laravel\Horizon\MasterSupervisor; use Laravel\Horizon\SupervisorOptions; use Laravel\Horizon\SupervisorProcess; use Symfony\Component\Process\Process; class AddSupervisor { /** * Process the command. * * @param \Laravel\Horizon\MasterSupervisor $master * @param array $options * @return void */ public function process(MasterSupervisor $master, array $options) { $options = SupervisorOptions::fromArray($options); $master->supervisors[] = new SupervisorProcess( $options, $this->createProcess($master, $options), function ($type, $line) use ($master) { $master->output($type, $line); } ); } /** * Create the Symfony process instance. * * @param \Laravel\Horizon\MasterSupervisor $master * @param \Laravel\Horizon\SupervisorOptions $options * @return \Symfony\Component\Process\Process */ protected function createProcess(MasterSupervisor $master, SupervisorOptions $options) { $command = $options->toSupervisorCommand(); return Process::fromShellCommandline($command, $options->directory ?? base_path()) ->setTimeout(null) ->disableOutput(); } } Exec.php 0000755 00000000404 00000000000 0006103 0 ustar 00 <?php namespace Laravel\Horizon; class Exec { /** * Run the given command. * * @param string $command * @return array */ public function run($command) { exec($command, $output); return $output; } } SupervisorFactory.php 0000755 00000000520 00000000000 0010727 0 ustar 00 <?php namespace Laravel\Horizon; class SupervisorFactory { /** * Create a new supervisor instance. * * @param \Laravel\Horizon\SupervisorOptions $options * @return \Laravel\Horizon\Supervisor */ public function make(SupervisorOptions $options) { return new Supervisor($options); } } LuaScripts.php 0000755 00000004633 00000000000 0007320 0 ustar 00 <?php namespace Laravel\Horizon; class LuaScripts { /** * Update the metrics for a job. * * KEYS[1] - The name of the key being updated * KEYS[2] - The name of the key of the metrics group * ARGV[1] - The runtime in milliseconds of the current job * * @return string */ public static function updateMetrics() { return <<<'LUA' redis.call('hsetnx', KEYS[1], 'throughput', 0) redis.call('sadd', KEYS[2], KEYS[1]) local hash = redis.call('hmget', KEYS[1], 'throughput', 'runtime') local throughput = hash[1] + 1 local runtime = 0 if hash[2] then runtime = ((hash[1] * tonumber(hash[2])) + tonumber(ARGV[1])) / throughput else runtime = tonumber(ARGV[1]) end redis.call('hmset', KEYS[1], 'throughput', throughput, 'runtime', runtime) LUA; } /** * Get the Lua script for purging recent and pending jobs off of the queue. * * KEYS[1] - The name of the recent jobs sorted set * KEYS[2] - The name of the pending jobs sorted set * ARGV[1] - The prefix of the Horizon keys * ARGV[2] - The name of the queue to purge * * @return string */ public static function purge() { return <<<'LUA' local count = 0 local cursor = 0 repeat -- Iterate over the recent jobs sorted set local scanner = redis.call('zscan', KEYS[1], cursor) cursor = scanner[1] for i = 1, #scanner[2], 2 do local jobid = scanner[2][i] local hashkey = ARGV[1] .. jobid local job = redis.call('hmget', hashkey, 'status', 'queue') -- Delete the pending/reserved jobs, that match the queue -- name, from the sorted sets as well as the job hash if((job[1] == 'reserved' or job[1] == 'pending') and job[2] == ARGV[2]) then redis.call('zrem', KEYS[1], jobid) redis.call('zrem', KEYS[2], jobid) redis.call('del', hashkey) count = count + 1 end end until cursor == '0' return count LUA; } } Lock.php 0000755 00000003655 00000000000 0006122 0 ustar 00 <?php namespace Laravel\Horizon; use Illuminate\Contracts\Redis\Factory as RedisFactory; class Lock { /** * The Redis factory implementation. * * @var \Illuminate\Contracts\Redis\Factory */ public $redis; /** * Create a Horizon lock manager. * * @param \Illuminate\Contracts\Redis\Factory $redis * @return void */ public function __construct(RedisFactory $redis) { $this->redis = $redis; } /** * Execute the given callback if a lock can be acquired. * * @param string $key * @param \Closure $callback * @param int $seconds * @return void */ public function with($key, $callback, $seconds = 60) { if ($this->get($key, $seconds)) { try { call_user_func($callback); } finally { $this->release($key); } } } /** * Determine if a lock exists for the given key. * * @param string $key * @return bool */ public function exists($key) { return $this->connection()->exists($key) === 1; } /** * Attempt to get a lock for the given key. * * @param string $key * @param int $seconds * @return bool */ public function get($key, $seconds = 60) { $result = $this->connection()->setnx($key, 1); if ($result === 1) { $this->connection()->expire($key, $seconds); } return $result === 1; } /** * Release the lock for the given key. * * @param string $key * @return void */ public function release($key) { $this->connection()->del($key); } /** * Get the Redis connection instance. * * @return \Illuminate\Redis\Connections\Connection */ public function connection() { return $this->redis->connection('horizon'); } } Contracts/JobRepository.php 0000755 00000012512 00000000000 0011774 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; use Illuminate\Support\Collection; use Laravel\Horizon\JobPayload; interface JobRepository { /** * Get the next job ID that should be assigned. * * @return string */ public function nextJobId(); /** * Get the total count of recent jobs. * * @return int */ public function totalRecent(); /** * Get the total count of failed jobs. * * @return int */ public function totalFailed(); /** * Get a chunk of recent jobs. * * @param string $afterIndex * @return \Illuminate\Support\Collection */ public function getRecent($afterIndex = null); /** * Get a chunk of failed jobs. * * @param string $afterIndex * @return \Illuminate\Support\Collection */ public function getFailed($afterIndex = null); /** * Get a chunk of pending jobs. * * @param string $afterIndex * @return \Illuminate\Support\Collection */ public function getPending($afterIndex = null); /** * Get a chunk of completed jobs. * * @param string $afterIndex * @return \Illuminate\Support\Collection */ public function getCompleted($afterIndex = null); /** * Get a chunk of silenced jobs. * * @param string $afterIndex * @return \Illuminate\Support\Collection */ public function getSilenced($afterIndex = null); /** * Get the count of recent jobs. * * @return int */ public function countRecent(); /** * Get the count of failed jobs. * * @return int */ public function countFailed(); /** * Get the count of pending jobs. * * @return int */ public function countPending(); /** * Get the count of completed jobs. * * @return int */ public function countCompleted(); /** * Get the count of silenced jobs. * * @return int */ public function countSilenced(); /** * Get the count of the recently failed jobs. * * @return int */ public function countRecentlyFailed(); /** * Retrieve the jobs with the given IDs. * * @param array $ids * @param mixed $indexFrom * @return \Illuminate\Support\Collection */ public function getJobs(array $ids, $indexFrom = 0); /** * Insert the job into storage. * * @param string $connection * @param string $queue * @param \Laravel\Horizon\JobPayload $payload * @return void */ public function pushed($connection, $queue, JobPayload $payload); /** * Mark the job as reserved. * * @param string $connection * @param string $queue * @param \Laravel\Horizon\JobPayload $payload * @return void */ public function reserved($connection, $queue, JobPayload $payload); /** * Mark the job as released / pending. * * @param string $connection * @param string $queue * @param \Laravel\Horizon\JobPayload $payload * @return void */ public function released($connection, $queue, JobPayload $payload); /** * Mark the job as completed and monitored. * * @param string $connection * @param string $queue * @param \Laravel\Horizon\JobPayload $payload * @return void */ public function remember($connection, $queue, JobPayload $payload); /** * Mark the given jobs as released / pending. * * @param string $connection * @param string $queue * @param \Illuminate\Support\Collection $payloads * @return void */ public function migrated($connection, $queue, Collection $payloads); /** * Handle the storage of a completed job. * * @param \Laravel\Horizon\JobPayload $payload * @param bool $failed * @param bool $silenced * @return void */ public function completed(JobPayload $payload, $failed = false, $silenced = false); /** * Delete the given monitored jobs by IDs. * * @param array $ids * @return void */ public function deleteMonitored(array $ids); /** * Trim the recent job list. * * @return void */ public function trimRecentJobs(); /** * Trim the failed job list. * * @return void */ public function trimFailedJobs(); /** * Trim the monitored job list. * * @return void */ public function trimMonitoredJobs(); /** * Find a failed job by ID. * * @param string $id * @return \stdClass|null */ public function findFailed($id); /** * Mark the job as failed. * * @param \Exception $exception * @param string $connection * @param string $queue * @param \Laravel\Horizon\JobPayload $payload * @return void */ public function failed($exception, $connection, $queue, JobPayload $payload); /** * Store the retry job ID on the original job record. * * @param string $id * @param string $retryId * @return void */ public function storeRetryReference($id, $retryId); /** * Delete a failed job by ID. * * @param string $id * @return int */ public function deleteFailed($id); } Contracts/WorkloadRepository.php 0000755 00000000304 00000000000 0013040 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; interface WorkloadRepository { /** * Get the current workload of each queue. * * @return array */ public function get(); } Contracts/Restartable.php 0000755 00000000255 00000000000 0011433 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; interface Restartable { /** * Restart the process. * * @return void */ public function restart(); } Contracts/Terminable.php 0000755 00000000327 00000000000 0011245 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; interface Terminable { /** * Terminate the process. * * @param int $status * @return void */ public function terminate($status = 0); } Contracts/Silenced.php 0000755 00000000104 00000000000 0010702 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; interface Silenced { } Contracts/MetricsRepository.php 0000755 00000005622 00000000000 0012674 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; interface MetricsRepository { /** * Get all of the class names that have metrics measurements. * * @return array */ public function measuredJobs(); /** * Get all of the queues that have metrics measurements. * * @return array */ public function measuredQueues(); /** * Get the jobs processed per minute since the last snapshot. * * @return int */ public function jobsProcessedPerMinute(); /** * Get the application's total throughput since the last snapshot. * * @return int */ public function throughput(); /** * Get the throughput for a given job. * * @param string $job * @return int */ public function throughputForJob($job); /** * Get the throughput for a given queue. * * @param string $queue * @return int */ public function throughputForQueue($queue); /** * Get the average runtime for a given job in milliseconds. * * @param string $job * @return float */ public function runtimeForJob($job); /** * Get the average runtime for a given queue in milliseconds. * * @param string $queue * @return float */ public function runtimeForQueue($queue); /** * Get the queue that has the longest runtime. * * @return int */ public function queueWithMaximumRuntime(); /** * Get the queue that has the most throughput. * * @return int */ public function queueWithMaximumThroughput(); /** * Increment the metrics information for a job. * * @param string $job * @param float|null $runtime * @return void */ public function incrementJob($job, $runtime); /** * Increment the metrics information for a queue. * * @param string $queue * @param float|null $runtime * @return void */ public function incrementQueue($queue, $runtime); /** * Get all of the snapshots for the given job. * * @param string $job * @return array */ public function snapshotsForJob($job); /** * Get all of the snapshots for the given queue. * * @param string $queue * @return array */ public function snapshotsForQueue($queue); /** * Store a snapshot of the metrics information. * * @return void */ public function snapshot(); /** * Attempt to acquire a lock to monitor the queue wait times. * * @return bool */ public function acquireWaitTimeMonitorLock(); /** * Clear the metrics for a key. * * @param string $key * @return void */ public function forget($key); /** * Delete all stored metrics information. * * @return void */ public function clear(); } Contracts/HorizonCommandQueue.php 0000755 00000001202 00000000000 0013110 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; interface HorizonCommandQueue { /** * Push a command onto a queue. * * @param string $name * @param string $command * @param array $options * @return void */ public function push($name, $command, array $options = []); /** * Get the pending commands for a given queue name. * * @param string $name * @return array */ public function pending($name); /** * Flush the command queue for a given queue name. * * @param string $name * @return void */ public function flush($name); } Contracts/ProcessRepository.php 0000755 00000001666 00000000000 0012710 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; interface ProcessRepository { /** * Get all of the orphan process IDs and the times they were observed. * * @param string $master * @return array */ public function allOrphans($master); /** * Record the given process IDs as orphaned. * * @param string $master * @param array $processIds * @return array */ public function orphaned($master, array $processIds); /** * Get the process IDs orphaned for at least the given number of seconds. * * @param string $master * @param int $seconds * @return array */ public function orphanedFor($master, $seconds); /** * Remove the given process IDs from the orphan list. * * @param string $master * @param array $processIds * @return void */ public function forgetOrphans($master, array $processIds); } Contracts/SupervisorRepository.php 0000755 00000002514 00000000000 0013444 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; use Laravel\Horizon\Supervisor; interface SupervisorRepository { /** * Get the names of all the supervisors currently running. * * @return array */ public function names(); /** * Get information on all of the supervisors. * * @return array */ public function all(); /** * Get information on a supervisor by name. * * @param string $name * @return array */ public function find($name); /** * Get information on the given supervisors. * * @param array $names * @return array */ public function get(array $names); /** * Get the longest active timeout setting for a supervisor. * * @return int */ public function longestActiveTimeout(); /** * Update the information about the given supervisor process. * * @param \Laravel\Horizon\Supervisor $supervisor * @return void */ public function update(Supervisor $supervisor); /** * Remove the supervisor information from storage. * * @param array|string $names * @return void */ public function forget($names); /** * Remove expired supervisors from storage. * * @return void */ public function flushExpired(); } Contracts/TagRepository.php 0000755 00000003366 00000000000 0012004 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; interface TagRepository { /** * Get the currently monitored tags. * * @return array */ public function monitoring(); /** * Return the tags which are being monitored. * * @param array $tags * @return array */ public function monitored(array $tags); /** * Start monitoring the given tag. * * @param string $tag * @return void */ public function monitor($tag); /** * Stop monitoring the given tag. * * @param string $tag * @return void */ public function stopMonitoring($tag); /** * Store the tags for the given job. * * @param string $id * @param array $tags * @return void */ public function add($id, array $tags); /** * Store the tags for the given job temporarily. * * @param int $minutes * @param string $id * @param array $tags * @return void */ public function addTemporary($minutes, $id, array $tags); /** * Get the number of jobs matching a given tag. * * @param string $tag * @return int */ public function count($tag); /** * Get all of the job IDs for a given tag. * * @param string $tag * @return array */ public function jobs($tag); /** * Paginate the job IDs for a given tag. * * @param string $tag * @param int $startingAt * @param int $limit * @return array */ public function paginate($tag, $startingAt = 0, $limit = 25); /** * Delete the given tag from storage. * * @param string $tag * @return void */ public function forget($tag); } Contracts/Pausable.php 0000755 00000000443 00000000000 0010716 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; interface Pausable { /** * Pause the process. * * @return void */ public function pause(); /** * Instruct the process to continue working. * * @return void */ public function continue(); } Contracts/MasterSupervisorRepository.php 0000755 00000002346 00000000000 0014623 0 ustar 00 <?php namespace Laravel\Horizon\Contracts; use Laravel\Horizon\MasterSupervisor; interface MasterSupervisorRepository { /** * Get the names of all the master supervisors currently running. * * @return array */ public function names(); /** * Get information on all of the master supervisors. * * @return array */ public function all(); /** * Get information on a master supervisor by name. * * @param string $name * @return array */ public function find($name); /** * Get information on the given master supervisors. * * @param array $names * @return array */ public function get(array $names); /** * Update the information about the given master supervisor. * * @param \Laravel\Horizon\MasterSupervisor $master * @return void */ public function update(MasterSupervisor $master); /** * Remove the master supervisor information from storage. * * @param string $name * @return void */ public function forget($name); /** * Remove expired master supervisors from storage. * * @return void */ public function flushExpired(); } AutoScaler.php 0000755 00000013232 00000000000 0007264 0 ustar 00 <?php namespace Laravel\Horizon; use Illuminate\Contracts\Queue\Factory as QueueFactory; use Illuminate\Support\Collection; use Laravel\Horizon\Contracts\MetricsRepository; class AutoScaler { /** * The queue factory implementation. * * @var \Illuminate\Contracts\Queue\Factory */ public $queue; /** * The metrics repository implementation. * * @var \Laravel\Horizon\Contracts\MetricsRepository */ public $metrics; /** * Create a new auto-scaler instance. * * @param \Illuminate\Contracts\Queue\Factory $queue * @param \Laravel\Horizon\Contracts\MetricsRepository $metrics * @return void */ public function __construct(QueueFactory $queue, MetricsRepository $metrics) { $this->queue = $queue; $this->metrics = $metrics; } /** * Balance the workers on the given supervisor. * * @param \Laravel\Horizon\Supervisor $supervisor * @return void */ public function scale(Supervisor $supervisor) { $pools = $this->poolsByQueue($supervisor); $workers = $this->numberOfWorkersPerQueue( $supervisor, $this->timeToClearPerQueue($supervisor, $pools) ); $workers->each(function ($workers, $queue) use ($supervisor, $pools) { $this->scalePool($supervisor, $pools[$queue], $workers); }); } /** * Get the process pools keyed by their queue name. * * @param \Laravel\Horizon\Supervisor $supervisor * @return \Illuminate\Support\Collection */ protected function poolsByQueue(Supervisor $supervisor) { return $supervisor->processPools->mapWithKeys(function ($pool) { return [$pool->queue() => $pool]; }); } /** * Get the times in milliseconds needed to clear the queues. * * @param \Laravel\Horizon\Supervisor $supervisor * @param \Illuminate\Support\Collection $pools * @return \Illuminate\Support\Collection */ protected function timeToClearPerQueue(Supervisor $supervisor, Collection $pools) { return $pools->mapWithKeys(function ($pool, $queue) use ($supervisor) { $queues = collect(explode(',', $queue))->map(function ($_queue) use ($supervisor) { $size = $this->queue->connection($supervisor->options->connection)->readyNow($_queue); return [ 'size' => $size, 'time' => ($size * $this->metrics->runtimeForQueue($_queue)), ]; }); return [$queue => [ 'size' => $queues->sum('size'), 'time' => $queues->sum('time'), ]]; }); } /** * Get the number of workers needed per queue for proper balance. * * @param \Laravel\Horizon\Supervisor $supervisor * @param \Illuminate\Support\Collection $queues * @return \Illuminate\Support\Collection */ protected function numberOfWorkersPerQueue(Supervisor $supervisor, Collection $queues) { $timeToClearAll = $queues->sum('time'); $totalJobs = $queues->sum('size'); return $queues->mapWithKeys(function ($timeToClear, $queue) use ($supervisor, $timeToClearAll, $totalJobs) { if ($timeToClearAll > 0 && $supervisor->options->autoScaling()) { $numberOfProcesses = $supervisor->options->autoScaleByNumberOfJobs() ? ($timeToClear['size'] / $totalJobs) : ($timeToClear['time'] / $timeToClearAll); return [$queue => $numberOfProcesses *= $supervisor->options->maxProcesses]; } elseif ($timeToClearAll == 0 && $supervisor->options->autoScaling()) { return [ $queue => $timeToClear['size'] ? $supervisor->options->maxProcesses : $supervisor->options->minProcesses, ]; } return [$queue => $supervisor->options->maxProcesses / count($supervisor->processPools)]; })->sort(); } /** * Scale the given pool to the recommended number of workers. * * @param \Laravel\Horizon\Supervisor $supervisor * @param \Laravel\Horizon\ProcessPool $pool * @param float $workers * @return void */ protected function scalePool(Supervisor $supervisor, $pool, $workers) { $supervisor->pruneTerminatingProcesses(); $totalProcessCount = $pool->totalProcessCount(); $desiredProcessCount = ceil($workers); if ($desiredProcessCount > $totalProcessCount) { $maxUpShift = min( max(0, $supervisor->options->maxProcesses - $supervisor->totalProcessCount()), $supervisor->options->balanceMaxShift ); $pool->scale( min( $totalProcessCount + $maxUpShift, max($supervisor->options->minProcesses, $supervisor->options->maxProcesses - (($supervisor->processPools->count() - 1) * $supervisor->options->minProcesses)), $desiredProcessCount ) ); } elseif ($desiredProcessCount < $totalProcessCount) { $maxDownShift = min( $supervisor->totalProcessCount() - $supervisor->options->minProcesses, $supervisor->options->balanceMaxShift ); $pool->scale( max( $totalProcessCount - $maxDownShift, $supervisor->options->minProcesses, $desiredProcessCount ) ); } } } ListensForSignals.php 0000755 00000002251 00000000000 0010632 0 ustar 00 <?php namespace Laravel\Horizon; use Illuminate\Support\Arr; trait ListensForSignals { /** * The pending signals that need to be processed. * * @var array */ protected $pendingSignals = []; /** * Listen for incoming process signals. * * @return void */ protected function listenForSignals() { pcntl_async_signals(true); pcntl_signal(SIGTERM, function () { $this->pendingSignals['terminate'] = 'terminate'; }); pcntl_signal(SIGUSR1, function () { $this->pendingSignals['restart'] = 'restart'; }); pcntl_signal(SIGUSR2, function () { $this->pendingSignals['pause'] = 'pause'; }); pcntl_signal(SIGCONT, function () { $this->pendingSignals['continue'] = 'continue'; }); } /** * Process the pending signals. * * @return void */ protected function processPendingSignals() { while ($this->pendingSignals) { $signal = Arr::first($this->pendingSignals); $this->{$signal}(); unset($this->pendingSignals[$signal]); } } } WorkerCommandString.php 0000755 00000002267 00000000000 0011167 0 ustar 00 <?php namespace Laravel\Horizon; class WorkerCommandString { /** * The base worker command. * * @var string */ public static $command = 'exec @php artisan horizon:work'; /** * Get the command-line representation of the options for a worker. * * @param \Laravel\Horizon\SupervisorOptions $options * @return string */ public static function fromOptions(SupervisorOptions $options) { $command = str_replace('@php', PhpBinary::path(), static::$command); return sprintf( "%s {$options->connection} %s", $command, static::toOptionsString($options) ); } /** * Get the additional option string for the command. * * @param \Laravel\Horizon\SupervisorOptions $options * @return string */ public static function toOptionsString(SupervisorOptions $options) { return QueueCommandString::toWorkerOptionsString($options); } /** * Reset the base command back to its default value. * * @return void */ public static function reset() { static::$command = 'exec @php artisan horizon:work'; } } ProvisioningPlan.php 0000755 00000012044 00000000000 0010523 0 ustar 00 <?php namespace Laravel\Horizon; use Exception; use Illuminate\Support\Arr; use Illuminate\Support\Str; use Laravel\Horizon\Contracts\HorizonCommandQueue; use Laravel\Horizon\Events\MasterSupervisorDeployed; use Laravel\Horizon\MasterSupervisorCommands\AddSupervisor; class ProvisioningPlan { /** * The master supervisor's name. * * @var string */ public $master; /** * The raw provisioning plan. * * @var array */ public $plan; /** * The parsed provisioning plan. * * @var array */ public $parsed; /** * Create a new provisioning plan instance. * * @param string $master * @param array $plan * @param array $defaults * @return void */ public function __construct($master, array $plan, array $defaults = []) { $this->plan = $this->applyDefaultOptions($plan, $defaults); $this->master = $master; $this->parsed = $this->toSupervisorOptions(); } /** * Get the current provisioning plan. * * @param string $master * @return static */ public static function get($master) { return new static($master, config('horizon.environments'), config('horizon.defaults', [])); } /** * Apply the default supervisor options to each environment. * * @param array $plan * @param array $defaults * @return array */ protected function applyDefaultOptions(array $plan, array $defaults = []) { return collect($plan)->map(function ($plan) use ($defaults) { return array_replace_recursive($defaults, $plan); })->all(); } /** * Get all of the defined environments for the provisioning plan. * * @return array */ public function environments() { return array_keys($this->parsed); } /** * Determine if the provisioning plan has a given environment. * * @param string $environment * @return bool */ public function hasEnvironment($environment) { return array_key_exists($environment, $this->parsed); } /** * Deploy a provisioning plan to the current machine. * * @param string $environment * @return void */ public function deploy($environment) { $supervisors = collect($this->parsed)->first(function ($_, $name) use ($environment) { return Str::is($name, $environment); }); if (empty($supervisors)) { return; } foreach ($supervisors as $supervisor => $options) { $this->add($options); } event(new MasterSupervisorDeployed($this->master)); } /** * Add a supervisor with the given options. * * @param \Laravel\Horizon\SupervisorOptions $options * @return void */ protected function add(SupervisorOptions $options) { app(HorizonCommandQueue::class)->push( MasterSupervisor::commandQueueFor($this->master), AddSupervisor::class, $options->toArray() ); } /** * Get the SupervisorOptions for a given environment and supervisor. * * @param string $environment * @param string $supervisor * @return mixed */ public function optionsFor($environment, $supervisor) { if (isset($this->parsed[$environment]) && isset($this->parsed[$environment][$supervisor])) { return $this->parsed[$environment][$supervisor]; } } /** * Convert the provisioning plan into an array of SupervisorOptions. * * @return array */ public function toSupervisorOptions() { return collect($this->plan)->mapWithKeys(function ($plan, $environment) { return [$environment => collect($plan)->mapWithKeys(function ($options, $supervisor) { return [$supervisor => $this->convert($supervisor, $options)]; })]; })->all(); } /** * Convert the given array of options into a SupervisorOptions instance. * * @param string $supervisor * @param array $options * @return \Laravel\Horizon\SupervisorOptions */ protected function convert($supervisor, $options) { $options = collect($options)->mapWithKeys(function ($value, $key) { $key = $key === 'tries' ? 'max_tries' : $key; $key = $key === 'processes' ? 'max_processes' : $key; $value = $key === 'queue' && is_array($value) ? implode(',', $value) : $value; $value = $key === 'backoff' && is_array($value) ? implode(',', $value) : $value; return [Str::camel($key) => $value]; })->all(); if (isset($options['minProcesses']) && $options['minProcesses'] < 1) { throw new Exception("The value of [{$supervisor}.minProcesses] must be greater than 0."); } $options['parentId'] = getmypid(); return SupervisorOptions::fromArray( Arr::add($options, 'name', $this->master.":{$supervisor}") ); } } Events/SupervisorOutOfMemory.php 0000755 00000000727 00000000000 0013022 0 ustar 00 <?php namespace Laravel\Horizon\Events; use Laravel\Horizon\Supervisor; class SupervisorOutOfMemory { /** * The supervisor instance. * * @var \Laravel\Horizon\Supervisor */ public $supervisor; /** * Create a new event instance. * * @param \Laravel\Horizon\Supervisor $supervisor * @return void */ public function __construct(Supervisor $supervisor) { $this->supervisor = $supervisor; } } Events/JobReserved.php 0000755 00000000132 00000000000 0010673 0 ustar 00 <?php namespace Laravel\Horizon\Events; class JobReserved extends RedisEvent { // } Events/JobPushed.php 0000755 00000000130 00000000000 0010342 0 ustar 00 <?php namespace Laravel\Horizon\Events; class JobPushed extends RedisEvent { // } Events/MasterSupervisorReviving.php 0000755 00000000574 00000000000 0013542 0 ustar 00 <?php namespace Laravel\Horizon\Events; class MasterSupervisorReviving { /** * The master supervisor that was dead. * * @var string */ public $master; /** * Create a new event instance. * * @param string $master * @return void */ public function __construct($master) { $this->master = $master; } } Events/MasterSupervisorLooped.php 0000755 00000000743 00000000000 0013171 0 ustar 00 <?php namespace Laravel\Horizon\Events; use Laravel\Horizon\MasterSupervisor; class MasterSupervisorLooped { /** * The master supervisor instance. * * @var \Laravel\Horizon\MasterSupervisor */ public $master; /** * Create a new event instance. * * @param \Laravel\Horizon\MasterSupervisor $master * @return void */ public function __construct(MasterSupervisor $master) { $this->master = $master; } } Events/LongWaitDetected.php 0000755 00000002114 00000000000 0011651 0 ustar 00 <?php namespace Laravel\Horizon\Events; use Laravel\Horizon\Notifications\LongWaitDetected as LongWaitDetectedNotification; class LongWaitDetected { /** * The queue connection name. * * @var string */ public $connection; /** * The queue name. * * @var string */ public $queue; /** * The wait time in seconds. * * @var int */ public $seconds; /** * Create a new event instance. * * @param string $connection * @param string $queue * @param int $seconds * @return void */ public function __construct($connection, $queue, $seconds) { $this->queue = $queue; $this->seconds = $seconds; $this->connection = $connection; } /** * Get a notification representation of the event. * * @return \Laravel\Horizon\Notifications\LongWaitDetected */ public function toNotification() { return new LongWaitDetectedNotification( $this->connection, $this->queue, $this->seconds ); } } Events/JobDeleted.php 0000755 00000000737 00000000000 0010475 0 ustar 00 <?php namespace Laravel\Horizon\Events; class JobDeleted extends RedisEvent { /** * The queue job instance. * * @var \Illuminate\Queue\Jobs\Job */ public $job; /** * Create a new event instance. * * @param \Illuminate\Queue\Jobs\Job $job * @param string $payload * @return void */ public function __construct($job, $payload) { $this->job = $job; parent::__construct($payload); } } Events/UnableToLaunchProcess.php 0000755 00000000730 00000000000 0012670 0 ustar 00 <?php namespace Laravel\Horizon\Events; use Laravel\Horizon\WorkerProcess; class UnableToLaunchProcess { /** * The worker process instance. * * @var \Laravel\Horizon\WorkerProcess */ public $process; /** * Create a new event instance. * * @param \Laravel\Horizon\WorkerProcess $process * @return void */ public function __construct(WorkerProcess $process) { $this->process = $process; } } Events/JobReleased.php 0000755 00000000132 00000000000 0010640 0 ustar 00 <?php namespace Laravel\Horizon\Events; class JobReleased extends RedisEvent { // } Events/MasterSupervisorDeployed.php 0000755 00000000600 00000000000 0013504 0 ustar 00 <?php namespace Laravel\Horizon\Events; class MasterSupervisorDeployed { /** * The master supervisor that was deployed. * * @var string */ public $master; /** * Create a new event instance. * * @param string $master * @return void */ public function __construct($master) { $this->master = $master; } } Events/JobsMigrated.php 0000755 00000002200 00000000000 0011031 0 ustar 00 <?php namespace Laravel\Horizon\Events; use Laravel\Horizon\JobPayload; class JobsMigrated { /** * The connection name. * * @var string */ public $connectionName; /** * The queue name. * * @var string */ public $queue; /** * The job payloads that were migrated. * * @var \Illuminate\Support\Collection */ public $payloads; /** * Create a new event instance. * * @param array $payloads * @return void */ public function __construct($payloads) { $this->payloads = collect($payloads)->map(function ($job) { return new JobPayload($job); }); } /** * Set the connection name. * * @param string $connectionName * @return $this */ public function connection($connectionName) { $this->connectionName = $connectionName; return $this; } /** * Set the queue name. * * @param string $queue * @return $this */ public function queue($queue) { $this->queue = $queue; return $this; } } Events/WorkerProcessRestarting.php 0000755 00000000732 00000000000 0013342 0 ustar 00 <?php namespace Laravel\Horizon\Events; use Laravel\Horizon\WorkerProcess; class WorkerProcessRestarting { /** * The worker process instance. * * @var \Laravel\Horizon\WorkerProcess */ public $process; /** * Create a new event instance. * * @param \Laravel\Horizon\WorkerProcess $process * @return void */ public function __construct(WorkerProcess $process) { $this->process = $process; } } Events/JobFailed.php 0000755 00000001253 00000000000 0010305 0 ustar 00 <?php namespace Laravel\Horizon\Events; class JobFailed extends RedisEvent { /** * The exception that caused the failure. * * @var \Exception */ public $exception; /** * The queue job instance. * * @var \Illuminate\Queue\Jobs\Job */ public $job; /** * Create a new event instance. * * @param \Exception $exception * @param \Illuminate\Queue\Jobs\Job $job * @param string $payload * @return void */ public function __construct($exception, $job, $payload) { $this->job = $job; $this->exception = $exception; parent::__construct($payload); } } Events/SupervisorProcessRestarting.php 0000755 00000000762 00000000000 0014255 0 ustar 00 <?php namespace Laravel\Horizon\Events; use Laravel\Horizon\SupervisorProcess; class SupervisorProcessRestarting { /** * The supervisor process instance. * * @var \Laravel\Horizon\SupervisorProcess */ public $process; /** * Create a new event instance. * * @param \Laravel\Horizon\SupervisorProcess $process * @return void */ public function __construct(SupervisorProcess $process) { $this->process = $process; } } Events/SupervisorLooped.php 0000755 00000000722 00000000000 0012012 0 ustar 00 <?php namespace Laravel\Horizon\Events; use Laravel\Horizon\Supervisor; class SupervisorLooped { /** * The supervisor instance. * * @var \Laravel\Horizon\Supervisor */ public $supervisor; /** * Create a new event instance. * * @param \Laravel\Horizon\Supervisor $supervisor * @return void */ public function __construct(Supervisor $supervisor) { $this->supervisor = $supervisor; } } Events/RedisEvent.php 0000755 00000002016 00000000000 0010534 0 ustar 00 <?php namespace Laravel\Horizon\Events; use Laravel\Horizon\JobPayload; class RedisEvent { /** * The connection name. * * @var string */ public $connectionName; /** * The queue name. * * @var string */ public $queue; /** * The job payload. * * @var JobPayload */ public $payload; /** * Create a new event instance. * * @param string $payload * @return void */ public function __construct($payload) { $this->payload = new JobPayload($payload); } /** * Set the connection name. * * @param string $connectionName * @return $this */ public function connection($connectionName) { $this->connectionName = $connectionName; return $this; } /** * Set the queue name. * * @param string $queue * @return $this */ public function queue($queue) { $this->queue = $queue; return $this; } } Events/MasterSupervisorOutOfMemory.php 0000755 00000000750 00000000000 0014172 0 ustar 00 <?php namespace Laravel\Horizon\Events; use Laravel\Horizon\MasterSupervisor; class MasterSupervisorOutOfMemory { /** * The master supervisor instance. * * @var \Laravel\Horizon\MasterSupervisor */ public $master; /** * Create a new event instance. * * @param \Laravel\Horizon\MasterSupervisor $master * @return void */ public function __construct(MasterSupervisor $master) { $this->master = $master; } } Horizon.php 0000755 00000012551 00000000000 0006655 0 ustar 00 <?php namespace Laravel\Horizon; use Closure; use Exception; use Illuminate\Support\HtmlString; use Illuminate\Support\Js; use RuntimeException; class Horizon { /** * The callback that should be used to authenticate Horizon users. * * @var \Closure */ public static $authUsing; /** * The Slack notifications webhook URL. * * @var string */ public static $slackWebhookUrl; /** * The Slack notifications channel. * * @var string */ public static $slackChannel; /** * The SMS notifications phone number. * * @var string */ public static $smsNumber; /** * The email address for notifications. * * @var string */ public static $email; /** * Indicates if Horizon should use the dark theme. * * @deprecated * * @var bool */ public static $useDarkTheme = false; /** * The database configuration methods. * * @var array */ public static $databases = [ 'Jobs', 'Supervisors', 'CommandQueue', 'Tags', 'Metrics', 'Locks', 'Processes', ]; /** * Determine if the given request can access the Horizon dashboard. * * @param \Illuminate\Http\Request $request * @return bool */ public static function check($request) { return (static::$authUsing ?: function () { return app()->environment('local'); })($request); } /** * Set the callback that should be used to authenticate Horizon users. * * @param \Closure $callback * @return static */ public static function auth(Closure $callback) { static::$authUsing = $callback; return new static; } /** * Configure the Redis databases that will store Horizon data. * * @param string $connection * @return void * * @throws \Exception */ public static function use($connection) { if (! is_null($config = config("database.redis.clusters.{$connection}.0"))) { config(["database.redis.{$connection}" => $config]); } elseif (is_null($config) && is_null($config = config("database.redis.{$connection}"))) { throw new Exception("Redis connection [{$connection}] has not been configured."); } $config['options']['prefix'] = config('horizon.prefix') ?: 'horizon:'; config(['database.redis.horizon' => $config]); } /** * Get the CSS for the Horizon dashboard. * * @return Illuminate\Contracts\Support\Htmlable */ public static function css() { if (($light = @file_get_contents(__DIR__.'/../dist/styles.css')) === false) { throw new RuntimeException('Unable to load the Horizon dashboard light CSS.'); } if (($dark = @file_get_contents(__DIR__.'/../dist/styles-dark.css')) === false) { throw new RuntimeException('Unable to load the Horizon dashboard dark CSS.'); } if (($app = @file_get_contents(__DIR__.'/../dist/app.css')) === false) { throw new RuntimeException('Unable to load the Horizon dashboard CSS.'); } return new HtmlString(<<<HTML <style data-scheme="light">{$light}</style> <style data-scheme="dark">{$dark}</style> <style>{$app}</style> HTML); } /** * Get the JS for the Horizon dashboard. * * @return \Illuminate\Contracts\Support\Htmlable */ public static function js() { if (($js = @file_get_contents(__DIR__.'/../dist/app.js')) === false) { throw new RuntimeException('Unable to load the Horizon dashboard JavaScript.'); } $horizon = Js::from(static::scriptVariables()); return new HtmlString(<<<HTML <script type="module"> window.Horizon = {$horizon}; {$js} </script> HTML); } /** * Specifies that Horizon should use the dark theme. * * @deprecated * * @return static */ public static function night() { static::$useDarkTheme = true; return new static; } /** * Get the default JavaScript variables for Horizon. * * @return array */ public static function scriptVariables() { return [ 'path' => config('horizon.path'), ]; } /** * Specify the email address to which email notifications should be routed. * * @param string $email * @return static */ public static function routeMailNotificationsTo($email) { static::$email = $email; return new static; } /** * Specify the webhook URL and channel to which Slack notifications should be routed. * * @param string $url * @param string $channel * @return static */ public static function routeSlackNotificationsTo($url, $channel = null) { static::$slackWebhookUrl = $url; static::$slackChannel = $channel; return new static; } /** * Specify the phone number to which SMS notifications should be routed. * * @param string $number * @return static */ public static function routeSmsNotificationsTo($number) { static::$smsNumber = $number; return new static; } } ConnectionException.php 0000755 00000001625 00000000000 0011203 0 ustar 00 <?php namespace Doctrine\DBAL; /** @psalm-immutable */ class ConnectionException extends Exception { /** @return ConnectionException */ public static function commitFailedRollbackOnly() { return new self('Transaction commit failed because the transaction has been marked for rollback only.'); } /** @return ConnectionException */ public static function noActiveTransaction() { return new self('There is no active transaction.'); } /** @return ConnectionException */ public static function savepointsNotSupported() { return new self('Savepoints are not supported by this driver.'); } /** @return ConnectionException */ public static function mayNotAlterNestedTransactionWithSavepointsInTransaction() { return new self('May not alter the nested transaction with savepoints behavior while a transaction is open.'); } } LockMode.php 0000755 00000000643 00000000000 0006721 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Contains all DBAL LockModes. */ class LockMode { public const NONE = 0; public const OPTIMISTIC = 1; public const PESSIMISTIC_READ = 2; public const PESSIMISTIC_WRITE = 4; /** * Private constructor. This class cannot be instantiated. * * @codeCoverageIgnore */ final private function __construct() { } } Driver/ServerInfoAwareConnection.php 0000755 00000001037 00000000000 0013537 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; /** * Contract for a connection that is able to provide information about the server it is connected to. * * @deprecated The methods defined in this interface will be made part of the {@see Driver} interface * in the next major release. */ interface ServerInfoAwareConnection extends Connection { /** * Returns information about the version of the database server connected to. * * @return string * * @throws Exception */ public function getServerVersion(); } Driver/AbstractException.php 0000755 00000002063 00000000000 0012077 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; use Exception as BaseException; use Throwable; /** * Base implementation of the {@see Exception} interface. * * @internal * * @psalm-immutable */ abstract class AbstractException extends BaseException implements Exception { /** * The SQLSTATE of the driver. */ private ?string $sqlState = null; /** * @param string $message The driver error message. * @param string|null $sqlState The SQLSTATE the driver is in at the time the error occurred, if any. * @param int $code The driver specific error code if any. * @param Throwable|null $previous The previous throwable used for the exception chaining. */ public function __construct($message, $sqlState = null, $code = 0, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->sqlState = $sqlState; } /** * {@inheritDoc} */ public function getSQLState() { return $this->sqlState; } } Driver/AbstractSQLiteDriver/Middleware/EnableForeignKeys.php 0000755 00000001456 00000000000 0020074 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\AbstractSQLiteDriver\Middleware; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use SensitiveParameter; class EnableForeignKeys implements Middleware { public function wrap(Driver $driver): Driver { return new class ($driver) extends AbstractDriverMiddleware { /** * {@inheritDoc} */ public function connect( #[SensitiveParameter] array $params ): Connection { $connection = parent::connect($params); $connection->exec('PRAGMA foreign_keys=ON'); return $connection; } }; } } Driver/Exception/UnknownParameterType.php 0000755 00000000637 00000000000 0014562 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class UnknownParameterType extends AbstractException { /** @param mixed $type */ public static function new($type): self { return new self(sprintf('Unknown parameter type, %d given.', $type)); } } Driver/Result.php 0000755 00000004701 00000000000 0007734 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; /** * Driver-level statement execution result. */ interface Result { /** * Returns the next row of the result as a numeric array or FALSE if there are no more rows. * * @return list<mixed>|false * * @throws Exception */ public function fetchNumeric(); /** * Returns the next row of the result as an associative array or FALSE if there are no more rows. * * @return array<string,mixed>|false * * @throws Exception */ public function fetchAssociative(); /** * Returns the first value of the next row of the result or FALSE if there are no more rows. * * @return mixed|false * * @throws Exception */ public function fetchOne(); /** * Returns an array containing all of the result rows represented as numeric arrays. * * @return list<list<mixed>> * * @throws Exception */ public function fetchAllNumeric(): array; /** * Returns an array containing all of the result rows represented as associative arrays. * * @return list<array<string,mixed>> * * @throws Exception */ public function fetchAllAssociative(): array; /** * Returns an array containing the values of the first column of the result. * * @return list<mixed> * * @throws Exception */ public function fetchFirstColumn(): array; /** * Returns the number of rows affected by the DELETE, INSERT, or UPDATE statement that produced the result. * * If the statement executed a SELECT query or a similar platform-specific SQL (e.g. DESCRIBE, SHOW, etc.), * some database drivers may return the number of rows returned by that query. However, this behaviour * is not guaranteed for all drivers and should not be relied on in portable applications. * * @return int The number of rows. * * @throws Exception */ public function rowCount(): int; /** * Returns the number of columns in the result * * @return int The number of columns in the result. If the columns cannot be counted, * this method must return 0. * * @throws Exception */ public function columnCount(): int; /** * Discards the non-fetched portion of the result, enabling the originating statement to be executed again. */ public function free(): void; } Driver/API/OCI/ExceptionConverter.php 0000755 00000004716 00000000000 0013335 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API\OCI; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DatabaseDoesNotExist; use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; use Doctrine\DBAL\Exception\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Query; /** @internal */ final class ExceptionConverter implements ExceptionConverterInterface { /** @link http://www.dba-oracle.com/t_error_code_list.htm */ public function convert(Exception $exception, ?Query $query): DriverException { switch ($exception->getCode()) { case 1: case 2299: case 38911: return new UniqueConstraintViolationException($exception, $query); case 904: return new InvalidFieldNameException($exception, $query); case 918: case 960: return new NonUniqueFieldNameException($exception, $query); case 923: return new SyntaxErrorException($exception, $query); case 942: return new TableNotFoundException($exception, $query); case 955: return new TableExistsException($exception, $query); case 1017: case 12545: return new ConnectionException($exception, $query); case 1400: return new NotNullConstraintViolationException($exception, $query); case 1918: return new DatabaseDoesNotExist($exception, $query); case 2289: case 2443: case 4080: return new DatabaseObjectNotFoundException($exception, $query); case 2266: case 2291: case 2292: return new ForeignKeyConstraintViolationException($exception, $query); } return new DriverException($exception, $query); } } Driver/API/ExceptionConverter.php 0000755 00000001516 00000000000 0012716 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Query; interface ExceptionConverter { /** * Converts a given driver-level exception into a DBAL-level driver exception. * * Implementors should use the vendor-specific error code and SQLSTATE of the exception * and instantiate the most appropriate specialized {@see DriverException} subclass. * * @param Exception $exception The driver exception to convert. * @param Query|null $query The SQL query that triggered the exception, if any. * * @return DriverException An instance of {@see DriverException} or one of its subclasses. */ public function convert(Exception $exception, ?Query $query): DriverException; } Driver/API/IBMDB2/ExceptionConverter.php 0000755 00000004124 00000000000 0013613 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API\IBMDB2; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; use Doctrine\DBAL\Exception\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Query; /** * @internal * * @link https://www.ibm.com/docs/en/db2/11.5?topic=messages-sql */ final class ExceptionConverter implements ExceptionConverterInterface { public function convert(Exception $exception, ?Query $query): DriverException { switch ($exception->getCode()) { case -104: return new SyntaxErrorException($exception, $query); case -203: return new NonUniqueFieldNameException($exception, $query); case -204: return new TableNotFoundException($exception, $query); case -206: return new InvalidFieldNameException($exception, $query); case -407: return new NotNullConstraintViolationException($exception, $query); case -530: case -531: case -532: case -20356: return new ForeignKeyConstraintViolationException($exception, $query); case -601: return new TableExistsException($exception, $query); case -803: return new UniqueConstraintViolationException($exception, $query); case -1336: case -30082: return new ConnectionException($exception, $query); } return new DriverException($exception, $query); } } Driver/API/MySQL/ExceptionConverter.php 0000755 00000007167 00000000000 0013673 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API\MySQL; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\ConnectionLost; use Doctrine\DBAL\Exception\DatabaseDoesNotExist; use Doctrine\DBAL\Exception\DeadlockException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; use Doctrine\DBAL\Exception\LockWaitTimeoutException; use Doctrine\DBAL\Exception\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Query; /** @internal */ final class ExceptionConverter implements ExceptionConverterInterface { /** * @link https://dev.mysql.com/doc/mysql-errors/8.0/en/client-error-reference.html * @link https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html */ public function convert(Exception $exception, ?Query $query): DriverException { switch ($exception->getCode()) { case 1008: return new DatabaseDoesNotExist($exception, $query); case 1213: return new DeadlockException($exception, $query); case 1205: return new LockWaitTimeoutException($exception, $query); case 1050: return new TableExistsException($exception, $query); case 1051: case 1146: return new TableNotFoundException($exception, $query); case 1216: case 1217: case 1451: case 1452: case 1701: return new ForeignKeyConstraintViolationException($exception, $query); case 1062: case 1557: case 1569: case 1586: return new UniqueConstraintViolationException($exception, $query); case 1054: case 1166: case 1611: return new InvalidFieldNameException($exception, $query); case 1052: case 1060: case 1110: return new NonUniqueFieldNameException($exception, $query); case 1064: case 1149: case 1287: case 1341: case 1342: case 1343: case 1344: case 1382: case 1479: case 1541: case 1554: case 1626: return new SyntaxErrorException($exception, $query); case 1044: case 1045: case 1046: case 1049: case 1095: case 1142: case 1143: case 1227: case 1370: case 1429: case 2002: case 2005: case 2054: return new ConnectionException($exception, $query); case 2006: case 4031: return new ConnectionLost($exception, $query); case 1048: case 1121: case 1138: case 1171: case 1252: case 1263: case 1364: case 1566: return new NotNullConstraintViolationException($exception, $query); } return new DriverException($exception, $query); } } Driver/API/SQLSrv/ExceptionConverter.php 0000755 00000004443 00000000000 0014052 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API\SQLSrv; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; use Doctrine\DBAL\Exception\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Query; /** * @internal * * @link https://docs.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors */ final class ExceptionConverter implements ExceptionConverterInterface { public function convert(Exception $exception, ?Query $query): DriverException { switch ($exception->getCode()) { case 102: return new SyntaxErrorException($exception, $query); case 207: return new InvalidFieldNameException($exception, $query); case 208: return new TableNotFoundException($exception, $query); case 209: return new NonUniqueFieldNameException($exception, $query); case 515: return new NotNullConstraintViolationException($exception, $query); case 547: case 4712: return new ForeignKeyConstraintViolationException($exception, $query); case 2601: case 2627: return new UniqueConstraintViolationException($exception, $query); case 2714: return new TableExistsException($exception, $query); case 3701: case 15151: return new DatabaseObjectNotFoundException($exception, $query); case 11001: case 18456: return new ConnectionException($exception, $query); } return new DriverException($exception, $query); } } Driver/API/SQLite/ExceptionConverter.php 0000755 00000006444 00000000000 0014064 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API\SQLite; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; use Doctrine\DBAL\Exception\LockWaitTimeoutException; use Doctrine\DBAL\Exception\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\ReadOnlyException; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Query; use function strpos; /** @internal */ final class ExceptionConverter implements ExceptionConverterInterface { /** @link http://www.sqlite.org/c3ref/c_abort.html */ public function convert(Exception $exception, ?Query $query): DriverException { if (strpos($exception->getMessage(), 'database is locked') !== false) { return new LockWaitTimeoutException($exception, $query); } if ( strpos($exception->getMessage(), 'must be unique') !== false || strpos($exception->getMessage(), 'is not unique') !== false || strpos($exception->getMessage(), 'are not unique') !== false || strpos($exception->getMessage(), 'UNIQUE constraint failed') !== false ) { return new UniqueConstraintViolationException($exception, $query); } if ( strpos($exception->getMessage(), 'may not be NULL') !== false || strpos($exception->getMessage(), 'NOT NULL constraint failed') !== false ) { return new NotNullConstraintViolationException($exception, $query); } if (strpos($exception->getMessage(), 'no such table:') !== false) { return new TableNotFoundException($exception, $query); } if (strpos($exception->getMessage(), 'already exists') !== false) { return new TableExistsException($exception, $query); } if (strpos($exception->getMessage(), 'has no column named') !== false) { return new InvalidFieldNameException($exception, $query); } if (strpos($exception->getMessage(), 'ambiguous column name') !== false) { return new NonUniqueFieldNameException($exception, $query); } if (strpos($exception->getMessage(), 'syntax error') !== false) { return new SyntaxErrorException($exception, $query); } if (strpos($exception->getMessage(), 'attempt to write a readonly database') !== false) { return new ReadOnlyException($exception, $query); } if (strpos($exception->getMessage(), 'unable to open database file') !== false) { return new ConnectionException($exception, $query); } if (strpos($exception->getMessage(), 'FOREIGN KEY constraint failed') !== false) { return new ForeignKeyConstraintViolationException($exception, $query); } return new DriverException($exception, $query); } } Driver/API/SQLite/UserDefinedFunctions.php 0000755 00000004367 00000000000 0014326 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\API\SQLite; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\Deprecations\Deprecation; use function array_merge; use function strpos; /** * User-defined SQLite functions. * * @internal */ final class UserDefinedFunctions { private const DEFAULT_FUNCTIONS = [ 'sqrt' => ['callback' => [SqlitePlatform::class, 'udfSqrt'], 'numArgs' => 1], 'mod' => ['callback' => [SqlitePlatform::class, 'udfMod'], 'numArgs' => 2], 'locate' => ['callback' => [SqlitePlatform::class, 'udfLocate'], 'numArgs' => -1], ]; /** * @param callable(string, callable, int): bool $callback * @param array<string, array{callback: callable, numArgs: int}> $additionalFunctions */ public static function register(callable $callback, array $additionalFunctions = []): void { $userDefinedFunctions = array_merge(self::DEFAULT_FUNCTIONS, $additionalFunctions); foreach ($userDefinedFunctions as $function => $data) { $callback($function, $data['callback'], $data['numArgs']); } } /** * User-defined function that implements MOD(). * * @param int $a * @param int $b */ public static function mod($a, $b): int { return $a % $b; } /** * User-defined function that implements LOCATE(). * * @param string $str * @param string $substr * @param int $offset */ public static function locate($str, $substr, $offset = 0): int { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5749', 'Relying on DBAL\'s emulated LOCATE() function is deprecated. ' . 'Use INSTR() or %s::getLocateExpression() instead.', AbstractPlatform::class, ); // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions. // So we have to make them compatible if an offset is given. if ($offset > 0) { $offset -= 1; } $pos = strpos($str, $substr, $offset); if ($pos !== false) { return $pos + 1; } return 0; } } Driver/API/PostgreSQL/ExceptionConverter.php 0000755 00000006520 00000000000 0014721 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API\PostgreSQL; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DatabaseDoesNotExist; use Doctrine\DBAL\Exception\DeadlockException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; use Doctrine\DBAL\Exception\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\SchemaDoesNotExist; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Query; use function strpos; /** @internal */ final class ExceptionConverter implements ExceptionConverterInterface { /** @link http://www.postgresql.org/docs/9.4/static/errcodes-appendix.html */ public function convert(Exception $exception, ?Query $query): DriverException { switch ($exception->getSQLState()) { case '40001': case '40P01': return new DeadlockException($exception, $query); case '0A000': // Foreign key constraint violations during a TRUNCATE operation // are considered "feature not supported" in PostgreSQL. if (strpos($exception->getMessage(), 'truncate') !== false) { return new ForeignKeyConstraintViolationException($exception, $query); } break; case '23502': return new NotNullConstraintViolationException($exception, $query); case '23503': return new ForeignKeyConstraintViolationException($exception, $query); case '23505': return new UniqueConstraintViolationException($exception, $query); case '3D000': return new DatabaseDoesNotExist($exception, $query); case '3F000': return new SchemaDoesNotExist($exception, $query); case '42601': return new SyntaxErrorException($exception, $query); case '42702': return new NonUniqueFieldNameException($exception, $query); case '42703': return new InvalidFieldNameException($exception, $query); case '42P01': return new TableNotFoundException($exception, $query); case '42P07': return new TableExistsException($exception, $query); case '08006': return new ConnectionException($exception, $query); } // Prior to fixing https://bugs.php.net/bug.php?id=64705 (PHP 7.4.10), // in some cases (mainly connection errors) the PDO exception wouldn't provide a SQLSTATE via its code. // We have to match against the SQLSTATE in the error message in these cases. if ($exception->getCode() === 7 && strpos($exception->getMessage(), 'SQLSTATE[08006]') !== false) { return new ConnectionException($exception, $query); } return new DriverException($exception, $query); } } Driver/AbstractOracleDriver/EasyConnectString.php 0000755 00000005270 00000000000 0016127 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\AbstractOracleDriver; use function implode; use function is_array; use function sprintf; /** * Represents an Oracle Easy Connect string * * @link https://docs.oracle.com/database/121/NETAG/naming.htm */ final class EasyConnectString { private string $string; private function __construct(string $string) { $this->string = $string; } public function __toString(): string { return $this->string; } /** * Creates the object from an array representation * * @param mixed[] $params */ public static function fromArray(array $params): self { return new self(self::renderParams($params)); } /** * Creates the object from the given DBAL connection parameters. * * @param mixed[] $params */ public static function fromConnectionParameters(array $params): self { if (isset($params['connectstring'])) { return new self($params['connectstring']); } if (! isset($params['host'])) { return new self($params['dbname'] ?? ''); } $connectData = []; if (isset($params['servicename']) || isset($params['dbname'])) { $serviceKey = 'SID'; if (isset($params['service'])) { $serviceKey = 'SERVICE_NAME'; } $serviceName = $params['servicename'] ?? $params['dbname']; $connectData[$serviceKey] = $serviceName; } if (isset($params['instancename'])) { $connectData['INSTANCE_NAME'] = $params['instancename']; } if (! empty($params['pooled'])) { $connectData['SERVER'] = 'POOLED'; } return self::fromArray([ 'DESCRIPTION' => [ 'ADDRESS' => [ 'PROTOCOL' => 'TCP', 'HOST' => $params['host'], 'PORT' => $params['port'] ?? 1521, ], 'CONNECT_DATA' => $connectData, ], ]); } /** @param mixed[] $params */ private static function renderParams(array $params): string { $chunks = []; foreach ($params as $key => $value) { $string = self::renderValue($value); if ($string === '') { continue; } $chunks[] = sprintf('(%s=%s)', $key, $string); } return implode('', $chunks); } /** @param mixed $value */ private static function renderValue($value): string { if (is_array($value)) { return self::renderParams($value); } return (string) $value; } } Driver/AbstractSQLiteDriver.php 0000755 00000002617 00000000000 0012463 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Driver\API\SQLite; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\SqliteSchemaManager; use Doctrine\Deprecations\Deprecation; use function assert; /** * Abstract base implementation of the {@see Doctrine\DBAL\Driver} interface for SQLite based drivers. */ abstract class AbstractSQLiteDriver implements Driver { /** * {@inheritDoc} */ public function getDatabasePlatform() { return new SqlitePlatform(); } /** * {@inheritDoc} * * @deprecated Use {@link SqlitePlatform::createSchemaManager()} instead. */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5458', 'AbstractSQLiteDriver::getSchemaManager() is deprecated.' . ' Use SqlitePlatform::createSchemaManager() instead.', ); assert($platform instanceof SqlitePlatform); return new SqliteSchemaManager($conn, $platform); } public function getExceptionConverter(): ExceptionConverter { return new SQLite\ExceptionConverter(); } } Driver/OCI8/Exception/UnknownParameterIndex.php 0000755 00000000706 00000000000 0015407 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class UnknownParameterIndex extends AbstractException { public static function new(int $index): self { return new self( sprintf('Could not find variable mapping with index %d, in the SQL statement', $index), ); } } Driver/OCI8/Exception/NonTerminatedStringLiteral.php 0000755 00000001004 00000000000 0016362 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class NonTerminatedStringLiteral extends AbstractException { public static function new(int $offset): self { return new self( sprintf( 'The statement contains non-terminated string literal starting at offset %d.', $offset, ), ); } } Driver/OCI8/Exception/Error.php 0000755 00000000765 00000000000 0012215 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; use function assert; use function oci_error; /** * @internal * * @psalm-immutable */ final class Error extends AbstractException { /** @param resource $resource */ public static function new($resource): self { $error = oci_error($resource); assert($error !== false); return new self($error['message'], null, $error['code']); } } Driver/OCI8/Exception/SequenceDoesNotExist.php 0000755 00000000574 00000000000 0015203 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class SequenceDoesNotExist extends AbstractException { public static function new(): self { return new self('lastInsertId failed: Query was executed but no result was returned.'); } } Driver/OCI8/Exception/ConnectionFailed.php 0000755 00000000711 00000000000 0014317 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; use function assert; use function oci_error; /** * @internal * * @psalm-immutable */ final class ConnectionFailed extends AbstractException { public static function new(): self { $error = oci_error(); assert($error !== false); return new self($error['message'], null, $error['code']); } } Driver/OCI8/Exception/InvalidConfiguration.php 0000755 00000000642 00000000000 0015234 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class InvalidConfiguration extends AbstractException { public static function forPersistentAndExclusive(): self { return new self('The "persistent" parameter and the "exclusive" driver option are mutually exclusive'); } } Driver/OCI8/Result.php 0000755 00000005641 00000000000 0010442 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\OCI8\Exception\Error; use Doctrine\DBAL\Driver\Result as ResultInterface; use function oci_cancel; use function oci_error; use function oci_fetch_all; use function oci_fetch_array; use function oci_num_fields; use function oci_num_rows; use const OCI_ASSOC; use const OCI_FETCHSTATEMENT_BY_COLUMN; use const OCI_FETCHSTATEMENT_BY_ROW; use const OCI_NUM; use const OCI_RETURN_LOBS; use const OCI_RETURN_NULLS; final class Result implements ResultInterface { /** @var resource */ private $statement; /** * @internal The result can be only instantiated by its driver connection or statement. * * @param resource $statement */ public function __construct($statement) { $this->statement = $statement; } /** * {@inheritDoc} */ public function fetchNumeric() { return $this->fetch(OCI_NUM); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->fetch(OCI_ASSOC); } /** * {@inheritDoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return $this->fetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0]; } public function rowCount(): int { $count = oci_num_rows($this->statement); if ($count !== false) { return $count; } return 0; } public function columnCount(): int { $count = oci_num_fields($this->statement); if ($count !== false) { return $count; } return 0; } public function free(): void { oci_cancel($this->statement); } /** * @return mixed|false * * @throws Exception */ private function fetch(int $mode) { $result = oci_fetch_array($this->statement, $mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS); if ($result === false && oci_error($this->statement) !== false) { throw Error::new($this->statement); } return $result; } /** @return array<mixed> */ private function fetchAll(int $mode, int $fetchStructure): array { oci_fetch_all( $this->statement, $result, 0, -1, $mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS, ); return $result; } } Driver/OCI8/ConvertPositionalToNamedPlaceholders.php 0000755 00000002442 00000000000 0016440 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\SQL\Parser\Visitor; use function count; use function implode; /** * Converts positional (?) into named placeholders (:param<num>). * * Oracle does not support positional parameters, hence this method converts all * positional parameters into artificially named parameters. * * @internal This class is not covered by the backward compatibility promise */ final class ConvertPositionalToNamedPlaceholders implements Visitor { /** @var list<string> */ private array $buffer = []; /** @var array<int,string> */ private array $parameterMap = []; public function acceptOther(string $sql): void { $this->buffer[] = $sql; } public function acceptPositionalParameter(string $sql): void { $position = count($this->parameterMap) + 1; $param = ':param' . $position; $this->parameterMap[$position] = $param; $this->buffer[] = $param; } public function acceptNamedParameter(string $sql): void { $this->buffer[] = $sql; } public function getSQL(): string { return implode('', $this->buffer); } /** @return array<int,string> */ public function getParameterMap(): array { return $this->parameterMap; } } Driver/OCI8/Connection.php 0000755 00000010123 00000000000 0011252 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\OCI8\Exception\Error; use Doctrine\DBAL\Driver\OCI8\Exception\SequenceDoesNotExist; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\SQL\Parser; use Doctrine\Deprecations\Deprecation; use function addcslashes; use function assert; use function is_float; use function is_int; use function is_resource; use function oci_commit; use function oci_parse; use function oci_rollback; use function oci_server_version; use function preg_match; use function str_replace; final class Connection implements ServerInfoAwareConnection { /** @var resource */ private $connection; private Parser $parser; private ExecutionMode $executionMode; /** * @internal The connection can be only instantiated by its driver. * * @param resource $connection */ public function __construct($connection) { $this->connection = $connection; $this->parser = new Parser(false); $this->executionMode = new ExecutionMode(); } public function getServerVersion(): string { $version = oci_server_version($this->connection); if ($version === false) { throw Error::new($this->connection); } $result = preg_match('/\s+(\d+\.\d+\.\d+\.\d+\.\d+)\s+/', $version, $matches); assert($result === 1); return $matches[1]; } /** @throws Parser\Exception */ public function prepare(string $sql): DriverStatement { $visitor = new ConvertPositionalToNamedPlaceholders(); $this->parser->parse($sql, $visitor); $statement = oci_parse($this->connection, $visitor->getSQL()); assert(is_resource($statement)); return new Statement($this->connection, $statement, $visitor->getParameterMap(), $this->executionMode); } /** * @throws Exception * @throws Parser\Exception */ public function query(string $sql): ResultInterface { return $this->prepare($sql)->execute(); } /** * {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { if (is_int($value) || is_float($value)) { return $value; } $value = str_replace("'", "''", $value); return "'" . addcslashes($value, "\000\n\r\\\032") . "'"; } /** * @throws Exception * @throws Parser\Exception */ public function exec(string $sql): int { return $this->prepare($sql)->execute()->rowCount(); } /** * {@inheritDoc} * * @param string|null $name * * @return int|false * * @throws Parser\Exception */ public function lastInsertId($name = null) { if ($name === null) { return false; } Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); $result = $this->query('SELECT ' . $name . '.CURRVAL FROM DUAL')->fetchOne(); if ($result === false) { throw SequenceDoesNotExist::new(); } return (int) $result; } public function beginTransaction(): bool { $this->executionMode->disableAutoCommit(); return true; } public function commit(): bool { if (! oci_commit($this->connection)) { throw Error::new($this->connection); } $this->executionMode->enableAutoCommit(); return true; } public function rollBack(): bool { if (! oci_rollback($this->connection)) { throw Error::new($this->connection); } $this->executionMode->enableAutoCommit(); return true; } /** @return resource */ public function getNativeConnection() { return $this->connection; } } Driver/OCI8/Statement.php 0000755 00000011672 00000000000 0011131 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\OCI8\Exception\Error; use Doctrine\DBAL\Driver\OCI8\Exception\UnknownParameterIndex; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function func_num_args; use function is_int; use function oci_bind_by_name; use function oci_execute; use function oci_new_descriptor; use const OCI_B_BIN; use const OCI_B_BLOB; use const OCI_COMMIT_ON_SUCCESS; use const OCI_D_LOB; use const OCI_NO_AUTO_COMMIT; use const OCI_TEMP_BLOB; use const SQLT_CHR; final class Statement implements StatementInterface { /** @var resource */ private $connection; /** @var resource */ private $statement; /** @var array<int,string> */ private array $parameterMap; private ExecutionMode $executionMode; /** * @internal The statement can be only instantiated by its driver connection. * * @param resource $connection * @param resource $statement * @param array<int,string> $parameterMap */ public function __construct($connection, $statement, array $parameterMap, ExecutionMode $executionMode) { $this->connection = $connection; $this->statement = $statement; $this->parameterMap = $parameterMap; $this->executionMode = $executionMode; } /** * {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->bindParam($param, $value, $type); } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (is_int($param)) { if (! isset($this->parameterMap[$param])) { throw UnknownParameterIndex::new($param); } $param = $this->parameterMap[$param]; } if ($type === ParameterType::LARGE_OBJECT) { if ($variable !== null) { $lob = oci_new_descriptor($this->connection, OCI_D_LOB); $lob->writeTemporary($variable, OCI_TEMP_BLOB); $variable =& $lob; } else { $type = ParameterType::STRING; } } return oci_bind_by_name( $this->statement, $param, $variable, $length ?? -1, $this->convertParameterType($type), ); } /** * Converts DBAL parameter type to oci8 parameter type */ private function convertParameterType(int $type): int { switch ($type) { case ParameterType::BINARY: return OCI_B_BIN; case ParameterType::LARGE_OBJECT: return OCI_B_BLOB; default: return SQLT_CHR; } } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); foreach ($params as $key => $val) { if (is_int($key)) { $this->bindValue($key + 1, $val, ParameterType::STRING); } else { $this->bindValue($key, $val, ParameterType::STRING); } } } if ($this->executionMode->isAutoCommitEnabled()) { $mode = OCI_COMMIT_ON_SUCCESS; } else { $mode = OCI_NO_AUTO_COMMIT; } $ret = @oci_execute($this->statement, $mode); if (! $ret) { throw Error::new($this->statement); } return new Result($this->statement); } } Driver/OCI8/ExecutionMode.php 0000755 00000001174 00000000000 0011731 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8; /** * Encapsulates the execution mode that is shared between the connection and its statements. * * @internal This class is not covered by the backward compatibility promise */ final class ExecutionMode { private bool $isAutoCommitEnabled = true; public function enableAutoCommit(): void { $this->isAutoCommitEnabled = true; } public function disableAutoCommit(): void { $this->isAutoCommitEnabled = false; } public function isAutoCommitEnabled(): bool { return $this->isAutoCommitEnabled; } } Driver/OCI8/Driver.php 0000755 00000003255 00000000000 0010416 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\AbstractOracleDriver; use Doctrine\DBAL\Driver\OCI8\Exception\ConnectionFailed; use Doctrine\DBAL\Driver\OCI8\Exception\InvalidConfiguration; use SensitiveParameter; use function oci_connect; use function oci_new_connect; use function oci_pconnect; use const OCI_NO_AUTO_COMMIT; /** * A Doctrine DBAL driver for the Oracle OCI8 PHP extensions. */ final class Driver extends AbstractOracleDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $username = $params['user'] ?? ''; $password = $params['password'] ?? ''; $charset = $params['charset'] ?? ''; $sessionMode = $params['sessionMode'] ?? OCI_NO_AUTO_COMMIT; $connectionString = $this->getEasyConnectString($params); $persistent = ! empty($params['persistent']); $exclusive = ! empty($params['driverOptions']['exclusive']); if ($persistent && $exclusive) { throw InvalidConfiguration::forPersistentAndExclusive(); } if ($persistent) { $connection = @oci_pconnect($username, $password, $connectionString, $charset, $sessionMode); } elseif ($exclusive) { $connection = @oci_new_connect($username, $password, $connectionString, $charset, $sessionMode); } else { $connection = @oci_connect($username, $password, $connectionString, $charset, $sessionMode); } if ($connection === false) { throw ConnectionFailed::new(); } return new Connection($connection); } } Driver/OCI8/Middleware/InitializeSession.php 0000755 00000002241 00000000000 0014677 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8\Middleware; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use SensitiveParameter; class InitializeSession implements Middleware { public function wrap(Driver $driver): Driver { return new class ($driver) extends AbstractDriverMiddleware { /** * {@inheritDoc} */ public function connect( #[SensitiveParameter] array $params ): Connection { $connection = parent::connect($params); $connection->exec( 'ALTER SESSION SET' . " NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'" . " NLS_TIME_FORMAT = 'HH24:MI:SS'" . " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'" . " NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS TZH:TZM'" . " NLS_NUMERIC_CHARACTERS = '.,'", ); return $connection; } }; } } Driver/PgSQL/Exception/UnknownParameter.php 0000755 00000000604 00000000000 0014640 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PgSQL\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** @psalm-immutable */ final class UnknownParameter extends AbstractException { public static function new(string $param): self { return new self( sprintf('Could not find parameter %s in the SQL statement', $param), ); } } Driver/PgSQL/Exception/UnexpectedValue.php 0000755 00000001137 00000000000 0014443 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PgSQL\Exception; use Doctrine\DBAL\Driver\Exception; use UnexpectedValueException; use function sprintf; /** @psalm-immutable */ final class UnexpectedValue extends UnexpectedValueException implements Exception { public static function new(string $value, string $type): self { return new self(sprintf( 'Unexpected value "%s" of type "%s" returned by Postgres', $value, $type, )); } /** @return null */ public function getSQLState() { return null; } } Driver/PgSQL/Result.php 0000755 00000015266 00000000000 0010672 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\PgSQL\Exception\UnexpectedValue; use Doctrine\DBAL\Driver\Result as ResultInterface; use PgSql\Result as PgSqlResult; use TypeError; use function array_keys; use function array_map; use function assert; use function get_class; use function gettype; use function hex2bin; use function is_object; use function is_resource; use function pg_affected_rows; use function pg_fetch_all; use function pg_fetch_all_columns; use function pg_fetch_assoc; use function pg_fetch_row; use function pg_field_name; use function pg_field_type; use function pg_free_result; use function pg_num_fields; use function sprintf; use function substr; use const PGSQL_ASSOC; use const PGSQL_NUM; use const PHP_INT_SIZE; final class Result implements ResultInterface { /** @var PgSqlResult|resource|null */ private $result; /** @param PgSqlResult|resource $result */ public function __construct($result) { if (! is_resource($result) && ! $result instanceof PgSqlResult) { throw new TypeError(sprintf( 'Expected result to be a resource or an instance of %s, got %s.', PgSqlResult::class, is_object($result) ? get_class($result) : gettype($result), )); } $this->result = $result; } public function __destruct() { if (! isset($this->result)) { return; } $this->free(); } /** {@inheritDoc} */ public function fetchNumeric() { if ($this->result === null) { return false; } $row = pg_fetch_row($this->result); if ($row === false) { return false; } return $this->mapNumericRow($row, $this->fetchNumericColumnTypes()); } /** {@inheritDoc} */ public function fetchAssociative() { if ($this->result === null) { return false; } $row = pg_fetch_assoc($this->result); if ($row === false) { return false; } return $this->mapAssociativeRow($row, $this->fetchAssociativeColumnTypes()); } /** {@inheritDoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** {@inheritDoc} */ public function fetchAllNumeric(): array { if ($this->result === null) { return []; } $resultSet = pg_fetch_all($this->result, PGSQL_NUM); // On PHP 7.4, pg_fetch_all() might return false for empty result sets. if ($resultSet === false) { return []; } $types = $this->fetchNumericColumnTypes(); return array_map( fn (array $row) => $this->mapNumericRow($row, $types), $resultSet, ); } /** {@inheritDoc} */ public function fetchAllAssociative(): array { if ($this->result === null) { return []; } $resultSet = pg_fetch_all($this->result, PGSQL_ASSOC); // On PHP 7.4, pg_fetch_all() might return false for empty result sets. if ($resultSet === false) { return []; } $types = $this->fetchAssociativeColumnTypes(); return array_map( fn (array $row) => $this->mapAssociativeRow($row, $types), $resultSet, ); } /** {@inheritDoc} */ public function fetchFirstColumn(): array { if ($this->result === null) { return []; } $postgresType = pg_field_type($this->result, 0); return array_map( fn ($value) => $this->mapType($postgresType, $value), pg_fetch_all_columns($this->result), ); } public function rowCount(): int { if ($this->result === null) { return 0; } return pg_affected_rows($this->result); } public function columnCount(): int { if ($this->result === null) { return 0; } return pg_num_fields($this->result); } public function free(): void { if ($this->result === null) { return; } pg_free_result($this->result); $this->result = null; } /** @return array<int, string> */ private function fetchNumericColumnTypes(): array { assert($this->result !== null); $types = []; $numFields = pg_num_fields($this->result); for ($i = 0; $i < $numFields; ++$i) { $types[$i] = pg_field_type($this->result, $i); } return $types; } /** @return array<string, string> */ private function fetchAssociativeColumnTypes(): array { assert($this->result !== null); $types = []; $numFields = pg_num_fields($this->result); for ($i = 0; $i < $numFields; ++$i) { $types[pg_field_name($this->result, $i)] = pg_field_type($this->result, $i); } return $types; } /** * @param list<string|null> $row * @param array<int, string> $types * * @return list<mixed> */ private function mapNumericRow(array $row, array $types): array { assert($this->result !== null); return array_map( fn ($value, $field) => $this->mapType($types[$field], $value), $row, array_keys($row), ); } /** * @param array<string, string|null> $row * @param array<string, string> $types * * @return array<string, mixed> */ private function mapAssociativeRow(array $row, array $types): array { assert($this->result !== null); $mappedRow = []; foreach ($row as $field => $value) { $mappedRow[$field] = $this->mapType($types[$field], $value); } return $mappedRow; } /** @return string|int|float|bool|null */ private function mapType(string $postgresType, ?string $value) { if ($value === null) { return null; } switch ($postgresType) { case 'bool': switch ($value) { case 't': return true; case 'f': return false; } throw UnexpectedValue::new($value, $postgresType); case 'bytea': return hex2bin(substr($value, 2)); case 'float4': case 'float8': return (float) $value; case 'int2': case 'int4': return (int) $value; case 'int8': return PHP_INT_SIZE >= 8 ? (int) $value : $value; } return $value; } } Driver/PgSQL/Connection.php 0000755 00000010311 00000000000 0011475 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\SQL\Parser; use Doctrine\Deprecations\Deprecation; use PgSql\Connection as PgSqlConnection; use TypeError; use function assert; use function get_class; use function gettype; use function is_object; use function is_resource; use function pg_close; use function pg_escape_bytea; use function pg_escape_literal; use function pg_get_result; use function pg_last_error; use function pg_result_error; use function pg_send_prepare; use function pg_send_query; use function pg_version; use function sprintf; use function uniqid; final class Connection implements ServerInfoAwareConnection { /** @var PgSqlConnection|resource */ private $connection; private Parser $parser; /** @param PgSqlConnection|resource $connection */ public function __construct($connection) { if (! is_resource($connection) && ! $connection instanceof PgSqlConnection) { throw new TypeError(sprintf( 'Expected connection to be a resource or an instance of %s, got %s.', PgSqlConnection::class, is_object($connection) ? get_class($connection) : gettype($connection), )); } $this->connection = $connection; $this->parser = new Parser(false); } public function __destruct() { if (! isset($this->connection)) { return; } @pg_close($this->connection); } public function prepare(string $sql): Statement { $visitor = new ConvertParameters(); $this->parser->parse($sql, $visitor); $statementName = uniqid('dbal', true); if (@pg_send_prepare($this->connection, $statementName, $visitor->getSQL()) !== true) { throw new Exception(pg_last_error($this->connection)); } $result = @pg_get_result($this->connection); assert($result !== false); if ((bool) pg_result_error($result)) { throw Exception::fromResult($result); } return new Statement($this->connection, $statementName, $visitor->getParameterMap()); } public function query(string $sql): Result { if (@pg_send_query($this->connection, $sql) !== true) { throw new Exception(pg_last_error($this->connection)); } $result = @pg_get_result($this->connection); assert($result !== false); if ((bool) pg_result_error($result)) { throw Exception::fromResult($result); } return new Result($result); } /** {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { if ($type === ParameterType::BINARY || $type === ParameterType::LARGE_OBJECT) { return sprintf("'%s'", pg_escape_bytea($this->connection, $value)); } return pg_escape_literal($this->connection, $value); } public function exec(string $sql): int { return $this->query($sql)->rowCount(); } /** {@inheritDoc} */ public function lastInsertId($name = null) { if ($name !== null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); return $this->query(sprintf('SELECT CURRVAL(%s)', $this->quote($name)))->fetchOne(); } return $this->query('SELECT LASTVAL()')->fetchOne(); } /** @return true */ public function beginTransaction(): bool { $this->exec('BEGIN'); return true; } /** @return true */ public function commit(): bool { $this->exec('COMMIT'); return true; } /** @return true */ public function rollBack(): bool { $this->exec('ROLLBACK'); return true; } public function getServerVersion(): string { return (string) pg_version($this->connection)['server']; } /** @return PgSqlConnection|resource */ public function getNativeConnection() { return $this->connection; } } Driver/PgSQL/Statement.php 0000755 00000012677 00000000000 0011363 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\Driver\PgSQL\Exception\UnknownParameter; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use PgSql\Connection as PgSqlConnection; use TypeError; use function assert; use function func_num_args; use function get_class; use function gettype; use function is_int; use function is_object; use function is_resource; use function ksort; use function pg_escape_bytea; use function pg_escape_identifier; use function pg_get_result; use function pg_last_error; use function pg_query; use function pg_result_error; use function pg_send_execute; use function sprintf; use function stream_get_contents; final class Statement implements StatementInterface { /** @var PgSqlConnection|resource */ private $connection; private string $name; /** @var array<array-key, int> */ private array $parameterMap; /** @var array<int, mixed> */ private array $parameters = []; /** @psalm-var array<int, int> */ private array $parameterTypes = []; /** * @param PgSqlConnection|resource $connection * @param array<array-key, int> $parameterMap */ public function __construct($connection, string $name, array $parameterMap) { if (! is_resource($connection) && ! $connection instanceof PgSqlConnection) { throw new TypeError(sprintf( 'Expected connection to be a resource or an instance of %s, got %s.', PgSqlConnection::class, is_object($connection) ? get_class($connection) : gettype($connection), )); } $this->connection = $connection; $this->name = $name; $this->parameterMap = $parameterMap; } public function __destruct() { if (! isset($this->connection)) { return; } @pg_query( $this->connection, 'DEALLOCATE ' . pg_escape_identifier($this->connection, $this->name), ); } /** {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { if (! isset($this->parameterMap[$param])) { throw UnknownParameter::new((string) $param); } $this->parameters[$this->parameterMap[$param]] = $value; $this->parameterTypes[$this->parameterMap[$param]] = $type; return true; } /** {@inheritDoc} */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (func_num_args() > 4) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4533', 'The $driverOptions argument of Statement::bindParam() is deprecated.', ); } if (! isset($this->parameterMap[$param])) { throw UnknownParameter::new((string) $param); } $this->parameters[$this->parameterMap[$param]] = &$variable; $this->parameterTypes[$this->parameterMap[$param]] = $type; return true; } /** {@inheritDoc} */ public function execute($params = null): Result { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); foreach ($params as $param => $value) { if (is_int($param)) { $this->bindValue($param + 1, $value, ParameterType::STRING); } else { $this->bindValue($param, $value, ParameterType::STRING); } } } ksort($this->parameters); $escapedParameters = []; foreach ($this->parameters as $parameter => $value) { switch ($this->parameterTypes[$parameter]) { case ParameterType::BINARY: case ParameterType::LARGE_OBJECT: $escapedParameters[] = $value === null ? null : pg_escape_bytea( $this->connection, is_resource($value) ? stream_get_contents($value) : $value, ); break; default: $escapedParameters[] = $value; } } if (@pg_send_execute($this->connection, $this->name, $escapedParameters) !== true) { throw new Exception(pg_last_error($this->connection)); } $result = @pg_get_result($this->connection); assert($result !== false); if ((bool) pg_result_error($result)) { throw Exception::fromResult($result); } return new Result($result); } } Driver/PgSQL/Driver.php 0000755 00000005040 00000000000 0010634 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; use ErrorException; use SensitiveParameter; use function addslashes; use function array_filter; use function array_keys; use function array_map; use function array_slice; use function array_values; use function func_get_args; use function implode; use function pg_connect; use function restore_error_handler; use function set_error_handler; use function sprintf; use const PGSQL_CONNECT_FORCE_NEW; final class Driver extends AbstractPostgreSQLDriver { /** {@inheritDoc} */ public function connect( #[SensitiveParameter] array $params ): Connection { set_error_handler( static function (int $severity, string $message) { throw new ErrorException($message, 0, $severity, ...array_slice(func_get_args(), 2, 2)); }, ); try { $connection = pg_connect($this->constructConnectionString($params), PGSQL_CONNECT_FORCE_NEW); } catch (ErrorException $e) { throw new Exception($e->getMessage(), '08006', 0, $e); } finally { restore_error_handler(); } if ($connection === false) { throw new Exception('Unable to connect to Postgres server.'); } $driverConnection = new Connection($connection); if (isset($params['application_name'])) { $driverConnection->exec('SET application_name = ' . $driverConnection->quote($params['application_name'])); } return $driverConnection; } /** * Constructs the Postgres connection string * * @param array<string, mixed> $params */ private function constructConnectionString( #[SensitiveParameter] array $params ): string { $components = array_filter( [ 'host' => $params['host'] ?? null, 'port' => $params['port'] ?? null, 'dbname' => $params['dbname'] ?? 'postgres', 'user' => $params['user'] ?? null, 'password' => $params['password'] ?? null, 'sslmode' => $params['sslmode'] ?? null, 'gssencmode' => $params['gssencmode'] ?? null, ], static fn ($value) => $value !== '' && $value !== null, ); return implode(' ', array_map( static fn ($value, string $key) => sprintf("%s='%s'", $key, addslashes($value)), array_values($components), array_keys($components), )); } } Driver/PgSQL/ConvertParameters.php 0000755 00000002223 00000000000 0013045 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\SQL\Parser\Visitor; use function count; use function implode; final class ConvertParameters implements Visitor { /** @var list<string> */ private array $buffer = []; /** @var array<array-key, int> */ private array $parameterMap = []; public function acceptPositionalParameter(string $sql): void { $position = count($this->parameterMap) + 1; $this->parameterMap[$position] = $position; $this->buffer[] = '$' . $position; } public function acceptNamedParameter(string $sql): void { $position = count($this->parameterMap) + 1; $this->parameterMap[$sql] = $position; $this->buffer[] = '$' . $position; } public function acceptOther(string $sql): void { $this->buffer[] = $sql; } public function getSQL(): string { return implode('', $this->buffer); } /** @return array<array-key, int> */ public function getParameterMap(): array { return $this->parameterMap; } } Driver/PgSQL/Exception.php 0000755 00000001265 00000000000 0011344 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\Driver\AbstractException; use PgSql\Result as PgSqlResult; use function pg_result_error_field; use const PGSQL_DIAG_MESSAGE_PRIMARY; use const PGSQL_DIAG_SQLSTATE; /** * @internal * * @psalm-immutable */ final class Exception extends AbstractException { /** @param PgSqlResult|resource $result */ public static function fromResult($result): self { $sqlstate = pg_result_error_field($result, PGSQL_DIAG_SQLSTATE); if ($sqlstate === false) { $sqlstate = null; } return new self((string) pg_result_error_field($result, PGSQL_DIAG_MESSAGE_PRIMARY), $sqlstate); } } Driver/AbstractPostgreSQLDriver.php 0000755 00000005565 00000000000 0013332 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Driver\API\PostgreSQL; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\PostgreSQL100Platform; use Doctrine\DBAL\Platforms\PostgreSQL120Platform; use Doctrine\DBAL\Platforms\PostgreSQL94Platform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Schema\PostgreSQLSchemaManager; use Doctrine\DBAL\VersionAwarePlatformDriver; use Doctrine\Deprecations\Deprecation; use function assert; use function preg_match; use function version_compare; /** * Abstract base implementation of the {@see Driver} interface for PostgreSQL based drivers. */ abstract class AbstractPostgreSQLDriver implements VersionAwarePlatformDriver { /** * {@inheritDoc} */ public function createDatabasePlatformForVersion($version) { if (preg_match('/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?)?/', $version, $versionParts) !== 1) { throw Exception::invalidPlatformVersionSpecified( $version, '<major_version>.<minor_version>.<patch_version>', ); } $majorVersion = $versionParts['major']; $minorVersion = $versionParts['minor'] ?? 0; $patchVersion = $versionParts['patch'] ?? 0; $version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion; if (version_compare($version, '12.0', '>=')) { return new PostgreSQL120Platform(); } if (version_compare($version, '10.0', '>=')) { return new PostgreSQL100Platform(); } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5060', 'PostgreSQL 9 support is deprecated and will be removed in DBAL 4.' . ' Consider upgrading to Postgres 10 or later.', ); return new PostgreSQL94Platform(); } /** * {@inheritDoc} */ public function getDatabasePlatform() { return new PostgreSQL94Platform(); } /** * {@inheritDoc} * * @deprecated Use {@link PostgreSQLPlatform::createSchemaManager()} instead. */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5458', 'AbstractPostgreSQLDriver::getSchemaManager() is deprecated.' . ' Use PostgreSQLPlatform::createSchemaManager() instead.', ); assert($platform instanceof PostgreSQLPlatform); return new PostgreSQLSchemaManager($conn, $platform); } public function getExceptionConverter(): ExceptionConverter { return new PostgreSQL\ExceptionConverter(); } } Driver/Middleware.php 0000755 00000000250 00000000000 0010526 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver; interface Middleware { public function wrap(Driver $driver): Driver; } Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php 0000755 00000001041 00000000000 0016356 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class CannotCreateTemporaryFile extends AbstractException { /** @psalm-param array{message: string}|null $error */ public static function new(?array $error): self { $message = 'Could not create temporary file'; if ($error !== null) { $message .= ': ' . $error['message']; } return new self($message); } } Driver/IBMDB2/Exception/StatementError.php 0000755 00000001477 00000000000 0014300 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; use function db2_stmt_error; use function db2_stmt_errormsg; /** * @internal * * @psalm-immutable */ final class StatementError extends AbstractException { /** @param resource|null $statement */ public static function new($statement = null): self { if ($statement !== null) { $message = db2_stmt_errormsg($statement); $sqlState = db2_stmt_error($statement); } else { $message = db2_stmt_errormsg(); $sqlState = db2_stmt_error(); } return Factory::create($message, static function (int $code) use ($message, $sqlState): self { return new self($message, $sqlState, $code); }); } } Driver/IBMDB2/Exception/ConnectionError.php 0000755 00000001233 00000000000 0014421 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; use function db2_conn_error; use function db2_conn_errormsg; /** * @internal * * @psalm-immutable */ final class ConnectionError extends AbstractException { /** @param resource $connection */ public static function new($connection): self { $message = db2_conn_errormsg($connection); $sqlState = db2_conn_error($connection); return Factory::create($message, static function (int $code) use ($message, $sqlState): self { return new self($message, $sqlState, $code); }); } } Driver/IBMDB2/Exception/Factory.php 0000755 00000001172 00000000000 0012721 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; use function preg_match; /** * @internal * * @psalm-immutable */ final class Factory { /** * @param callable(int): T $constructor * * @return T * * @template T of AbstractException */ public static function create(string $message, callable $constructor): AbstractException { $code = 0; if (preg_match('/ SQL(\d+)N /', $message, $matches) === 1) { $code = -(int) $matches[1]; } return $constructor($code); } } Driver/IBMDB2/Exception/ConnectionFailed.php 0000755 00000001124 00000000000 0014513 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; use function db2_conn_error; use function db2_conn_errormsg; /** * @internal * * @psalm-immutable */ final class ConnectionFailed extends AbstractException { public static function new(): self { $message = db2_conn_errormsg(); $sqlState = db2_conn_error(); return Factory::create($message, static function (int $code) use ($message, $sqlState): self { return new self($message, $sqlState, $code); }); } } Driver/IBMDB2/Exception/PrepareFailed.php 0000755 00000000743 00000000000 0014020 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class PrepareFailed extends AbstractException { /** @psalm-param array{message: string}|null $error */ public static function new(?array $error): self { if ($error === null) { return new self('Unknown error'); } return new self($error['message']); } } Driver/IBMDB2/Exception/CannotCopyStreamToStream.php 0000755 00000001057 00000000000 0016224 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class CannotCopyStreamToStream extends AbstractException { /** @psalm-param array{message: string}|null $error */ public static function new(?array $error): self { $message = 'Could not copy source stream to temporary file'; if ($error !== null) { $message .= ': ' . $error['message']; } return new self($message); } } Driver/IBMDB2/Result.php 0000755 00000004400 00000000000 0010627 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; use Doctrine\DBAL\Driver\Result as ResultInterface; use function db2_fetch_array; use function db2_fetch_assoc; use function db2_free_result; use function db2_num_fields; use function db2_num_rows; use function db2_stmt_error; final class Result implements ResultInterface { /** @var resource */ private $statement; /** * @internal The result can be only instantiated by its driver connection or statement. * * @param resource $statement */ public function __construct($statement) { $this->statement = $statement; } /** * {@inheritDoc} */ public function fetchNumeric() { $row = @db2_fetch_array($this->statement); if ($row === false && db2_stmt_error($this->statement) !== '02000') { throw StatementError::new($this->statement); } return $row; } /** * {@inheritDoc} */ public function fetchAssociative() { $row = @db2_fetch_assoc($this->statement); if ($row === false && db2_stmt_error($this->statement) !== '02000') { throw StatementError::new($this->statement); } return $row; } /** * {@inheritDoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } public function rowCount(): int { return @db2_num_rows($this->statement); } public function columnCount(): int { $count = db2_num_fields($this->statement); if ($count !== false) { return $count; } return 0; } public function free(): void { db2_free_result($this->statement); } } Driver/IBMDB2/Connection.php 0000755 00000006622 00000000000 0011460 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionError; use Doctrine\DBAL\Driver\IBMDB2\Exception\PrepareFailed; use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use stdClass; use function assert; use function db2_autocommit; use function db2_commit; use function db2_escape_string; use function db2_exec; use function db2_last_insert_id; use function db2_num_rows; use function db2_prepare; use function db2_rollback; use function db2_server_info; use function error_get_last; use const DB2_AUTOCOMMIT_OFF; use const DB2_AUTOCOMMIT_ON; final class Connection implements ServerInfoAwareConnection { /** @var resource */ private $connection; /** * @internal The connection can be only instantiated by its driver. * * @param resource $connection */ public function __construct($connection) { $this->connection = $connection; } /** * {@inheritDoc} */ public function getServerVersion() { $serverInfo = db2_server_info($this->connection); assert($serverInfo instanceof stdClass); return $serverInfo->DBMS_VER; } public function prepare(string $sql): DriverStatement { $stmt = @db2_prepare($this->connection, $sql); if ($stmt === false) { throw PrepareFailed::new(error_get_last()); } return new Statement($stmt); } public function query(string $sql): ResultInterface { return $this->prepare($sql)->execute(); } /** * {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { $value = db2_escape_string($value); if ($type === ParameterType::INTEGER) { return $value; } return "'" . $value . "'"; } public function exec(string $sql): int { $stmt = @db2_exec($this->connection, $sql); if ($stmt === false) { throw StatementError::new(); } return db2_num_rows($stmt); } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name !== null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); } return db2_last_insert_id($this->connection) ?? false; } public function beginTransaction(): bool { return db2_autocommit($this->connection, DB2_AUTOCOMMIT_OFF); } public function commit(): bool { if (! db2_commit($this->connection)) { throw ConnectionError::new($this->connection); } return db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON); } public function rollBack(): bool { if (! db2_rollback($this->connection)) { throw ConnectionError::new($this->connection); } return db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON); } /** @return resource */ public function getNativeConnection() { return $this->connection; } } Driver/IBMDB2/Statement.php 0000755 00000013431 00000000000 0011321 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCopyStreamToStream; use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCreateTemporaryFile; use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function assert; use function db2_bind_param; use function db2_execute; use function error_get_last; use function fclose; use function func_num_args; use function is_int; use function is_resource; use function stream_copy_to_stream; use function stream_get_meta_data; use function tmpfile; use const DB2_BINARY; use const DB2_CHAR; use const DB2_LONG; use const DB2_PARAM_FILE; use const DB2_PARAM_IN; final class Statement implements StatementInterface { /** @var resource */ private $stmt; /** @var mixed[] */ private array $parameters = []; /** * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement * and the temporary file handle bound to the underlying statement * * @var array<int,string|resource|null> */ private array $lobs = []; /** * @internal The statement can be only instantiated by its driver connection. * * @param resource $stmt */ public function __construct($stmt) { $this->stmt = $stmt; } /** * {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->bindParam($param, $value, $type); } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } switch ($type) { case ParameterType::INTEGER: $this->bind($param, $variable, DB2_PARAM_IN, DB2_LONG); break; case ParameterType::LARGE_OBJECT: $this->lobs[$param] = &$variable; break; default: $this->bind($param, $variable, DB2_PARAM_IN, DB2_CHAR); break; } return true; } /** * @param int $position Parameter position * @param mixed $variable * * @throws Exception */ private function bind($position, &$variable, int $parameterType, int $dataType): void { $this->parameters[$position] =& $variable; if (! db2_bind_param($this->stmt, $position, '', $parameterType, $dataType)) { throw StatementError::new($this->stmt); } } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); } $handles = $this->bindLobs(); $result = @db2_execute($this->stmt, $params ?? $this->parameters); foreach ($handles as $handle) { fclose($handle); } $this->lobs = []; if ($result === false) { throw StatementError::new($this->stmt); } return new Result($this->stmt); } /** * @return list<resource> * * @throws Exception */ private function bindLobs(): array { $handles = []; foreach ($this->lobs as $param => $value) { if (is_resource($value)) { $handle = $handles[] = $this->createTemporaryFile(); $path = stream_get_meta_data($handle)['uri']; $this->copyStreamToStream($value, $handle); $this->bind($param, $path, DB2_PARAM_FILE, DB2_BINARY); } else { $this->bind($param, $value, DB2_PARAM_IN, DB2_CHAR); } unset($value); } return $handles; } /** * @return resource * * @throws Exception */ private function createTemporaryFile() { $handle = @tmpfile(); if ($handle === false) { throw CannotCreateTemporaryFile::new(error_get_last()); } return $handle; } /** * @param resource $source * @param resource $target * * @throws Exception */ private function copyStreamToStream($source, $target): void { if (@stream_copy_to_stream($source, $target) === false) { throw CannotCopyStreamToStream::new(error_get_last()); } } } Driver/IBMDB2/DataSourceName.php 0000755 00000003447 00000000000 0012216 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2; use SensitiveParameter; use function implode; use function sprintf; use function strpos; /** * IBM DB2 DSN */ final class DataSourceName { private string $string; private function __construct( #[SensitiveParameter] string $string ) { $this->string = $string; } public function toString(): string { return $this->string; } /** * Creates the object from an array representation * * @param array<string,mixed> $params */ public static function fromArray( #[SensitiveParameter] array $params ): self { $chunks = []; foreach ($params as $key => $value) { $chunks[] = sprintf('%s=%s', $key, $value); } return new self(implode(';', $chunks)); } /** * Creates the object from the given DBAL connection parameters. * * @param array<string,mixed> $params */ public static function fromConnectionParameters( #[SensitiveParameter] array $params ): self { if (isset($params['dbname']) && strpos($params['dbname'], '=') !== false) { return new self($params['dbname']); } $dsnParams = []; foreach ( [ 'host' => 'HOSTNAME', 'port' => 'PORT', 'protocol' => 'PROTOCOL', 'dbname' => 'DATABASE', 'user' => 'UID', 'password' => 'PWD', ] as $dbalParam => $dsnParam ) { if (! isset($params[$dbalParam])) { continue; } $dsnParams[$dsnParam] = $params[$dbalParam]; } return self::fromArray($dsnParams); } } Driver/IBMDB2/Driver.php 0000755 00000002102 00000000000 0010601 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\AbstractDB2Driver; use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionFailed; use SensitiveParameter; use function db2_connect; use function db2_pconnect; final class Driver extends AbstractDB2Driver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $dataSourceName = DataSourceName::fromConnectionParameters($params)->toString(); $username = $params['user'] ?? ''; $password = $params['password'] ?? ''; $driverOptions = $params['driverOptions'] ?? []; if (! empty($params['persistent'])) { $connection = db2_pconnect($dataSourceName, $username, $password, $driverOptions); } else { $connection = db2_connect($dataSourceName, $username, $password, $driverOptions); } if ($connection === false) { throw ConnectionFailed::new(); } return new Connection($connection); } } Driver/SQLite3/Result.php 0000755 00000003575 00000000000 0011170 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result as ResultInterface; use SQLite3Result; use const SQLITE3_ASSOC; use const SQLITE3_NUM; final class Result implements ResultInterface { private ?SQLite3Result $result; private int $changes; /** @internal The result can be only instantiated by its driver connection or statement. */ public function __construct(SQLite3Result $result, int $changes) { $this->result = $result; $this->changes = $changes; } /** @inheritDoc */ public function fetchNumeric() { if ($this->result === null) { return false; } return $this->result->fetchArray(SQLITE3_NUM); } /** @inheritDoc */ public function fetchAssociative() { if ($this->result === null) { return false; } return $this->result->fetchArray(SQLITE3_ASSOC); } /** @inheritDoc */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** @inheritDoc */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** @inheritDoc */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** @inheritDoc */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } public function rowCount(): int { return $this->changes; } public function columnCount(): int { if ($this->result === null) { return 0; } return $this->result->numColumns(); } public function free(): void { if ($this->result === null) { return; } $this->result->finalize(); $this->result = null; } } Driver/SQLite3/Connection.php 0000755 00000004612 00000000000 0012002 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\ParameterType; use SQLite3; use function assert; use function sprintf; final class Connection implements ServerInfoAwareConnection { private SQLite3 $connection; /** @internal The connection can be only instantiated by its driver. */ public function __construct(SQLite3 $connection) { $this->connection = $connection; } public function prepare(string $sql): Statement { try { $statement = $this->connection->prepare($sql); } catch (\Exception $e) { throw Exception::new($e); } assert($statement !== false); return new Statement($this->connection, $statement); } public function query(string $sql): Result { try { $result = $this->connection->query($sql); } catch (\Exception $e) { throw Exception::new($e); } assert($result !== false); return new Result($result, $this->connection->changes()); } /** @inheritDoc */ public function quote($value, $type = ParameterType::STRING): string { return sprintf('\'%s\'', SQLite3::escapeString($value)); } public function exec(string $sql): int { try { $this->connection->exec($sql); } catch (\Exception $e) { throw Exception::new($e); } return $this->connection->changes(); } /** @inheritDoc */ public function lastInsertId($name = null): int { return $this->connection->lastInsertRowID(); } public function beginTransaction(): bool { try { return $this->connection->exec('BEGIN'); } catch (\Exception $e) { return false; } } public function commit(): bool { try { return $this->connection->exec('COMMIT'); } catch (\Exception $e) { return false; } } public function rollBack(): bool { try { return $this->connection->exec('ROLLBACK'); } catch (\Exception $e) { return false; } } public function getNativeConnection(): SQLite3 { return $this->connection; } public function getServerVersion(): string { return SQLite3::version()['versionString']; } } Driver/SQLite3/Statement.php 0000755 00000010031 00000000000 0011637 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use SQLite3; use SQLite3Stmt; use function assert; use function func_num_args; use function is_int; use const SQLITE3_BLOB; use const SQLITE3_INTEGER; use const SQLITE3_NULL; use const SQLITE3_TEXT; final class Statement implements StatementInterface { private const PARAM_TYPE_MAP = [ ParameterType::NULL => SQLITE3_NULL, ParameterType::INTEGER => SQLITE3_INTEGER, ParameterType::STRING => SQLITE3_TEXT, ParameterType::ASCII => SQLITE3_TEXT, ParameterType::BINARY => SQLITE3_BLOB, ParameterType::LARGE_OBJECT => SQLITE3_BLOB, ParameterType::BOOLEAN => SQLITE3_INTEGER, ]; private SQLite3 $connection; private SQLite3Stmt $statement; /** @internal The statement can be only instantiated by its driver connection. */ public function __construct(SQLite3 $connection, SQLite3Stmt $statement) { $this->connection = $connection; $this->statement = $statement; } /** * @throws UnknownParameterType * * {@inheritDoc} * * @psalm-assert ParameterType::* $type */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->statement->bindValue($param, $value, $this->convertParamType($type)); } /** * @throws UnknownParameterType * * {@inheritDoc} * * @psalm-assert ParameterType::* $type */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->statement->bindParam($param, $variable, $this->convertParamType($type)); } /** @inheritDoc */ public function execute($params = null): Result { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); foreach ($params as $param => $value) { if (is_int($param)) { $this->bindValue($param + 1, $value, ParameterType::STRING); } else { $this->bindValue($param, $value, ParameterType::STRING); } } } try { $result = $this->statement->execute(); } catch (\Exception $e) { throw Exception::new($e); } assert($result !== false); return new Result($result, $this->connection->changes()); } /** * @psalm-return value-of<self::PARAM_TYPE_MAP> * * @psalm-assert ParameterType::* $type */ private function convertParamType(int $type): int { if (! isset(self::PARAM_TYPE_MAP[$type])) { throw UnknownParameterType::new($type); } return self::PARAM_TYPE_MAP[$type]; } } Driver/SQLite3/Driver.php 0000755 00000002430 00000000000 0011132 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\AbstractSQLiteDriver; use Doctrine\DBAL\Driver\API\SQLite\UserDefinedFunctions; use SensitiveParameter; use SQLite3; final class Driver extends AbstractSQLiteDriver { /** * {@inheritDoc} */ public function connect( #[SensitiveParameter] array $params ): Connection { $isMemory = (bool) ($params['memory'] ?? false); if (isset($params['path'])) { if ($isMemory) { throw new Exception( 'Invalid connection settings: specifying both parameters "path" and "memory" is ambiguous.', ); } $filename = $params['path']; } elseif ($isMemory) { $filename = ':memory:'; } else { throw new Exception( 'Invalid connection settings: specify either the "path" or the "memory" parameter for SQLite3.', ); } try { $connection = new SQLite3($filename); } catch (\Exception $e) { throw Exception::new($e); } $connection->enableExceptions(true); UserDefinedFunctions::register([$connection, 'createFunction']); return new Connection($connection); } } Driver/SQLite3/Exception.php 0000755 00000000547 00000000000 0011644 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class Exception extends AbstractException { public static function new(\Exception $exception): self { return new self($exception->getMessage(), null, (int) $exception->getCode(), $exception); } } Driver/AbstractSQLServerDriver.php 0000755 00000003025 00000000000 0013142 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\API\SQLSrv\ExceptionConverter; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Schema\SQLServerSchemaManager; use Doctrine\Deprecations\Deprecation; use function assert; /** * Abstract base implementation of the {@see Driver} interface for Microsoft SQL Server based drivers. */ abstract class AbstractSQLServerDriver implements Driver { /** * {@inheritDoc} */ public function getDatabasePlatform() { return new SQLServer2012Platform(); } /** * {@inheritDoc} * * @deprecated Use {@link SQLServerPlatform::createSchemaManager()} instead. */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5458', 'AbstractSQLServerDriver::getSchemaManager() is deprecated.' . ' Use SQLServerPlatform::createSchemaManager() instead.', ); assert($platform instanceof SQLServerPlatform); return new SQLServerSchemaManager($conn, $platform); } public function getExceptionConverter(): ExceptionConverterInterface { return new ExceptionConverter(); } } Driver/PDO/OCI/Driver.php 0000755 00000002624 00000000000 0010747 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\OCI; use Doctrine\DBAL\Driver\AbstractOracleDriver; use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDO\Exception; use PDO; use PDOException; use SensitiveParameter; final class Driver extends AbstractOracleDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $driverOptions = $params['driverOptions'] ?? []; if (! empty($params['persistent'])) { $driverOptions[PDO::ATTR_PERSISTENT] = true; } $safeParams = $params; unset($safeParams['password'], $safeParams['url']); try { $pdo = new PDO( $this->constructPdoDsn($params), $params['user'] ?? '', $params['password'] ?? '', $driverOptions, ); } catch (PDOException $exception) { throw Exception::new($exception); } return new Connection($pdo); } /** * Constructs the Oracle PDO DSN. * * @param mixed[] $params */ private function constructPdoDsn(array $params): string { $dsn = 'oci:dbname=' . $this->getEasyConnectString($params); if (isset($params['charset'])) { $dsn .= ';charset=' . $params['charset']; } return $dsn; } } Driver/PDO/Result.php 0000755 00000004674 00000000000 0010367 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\Result as ResultInterface; use PDO; use PDOException; use PDOStatement; final class Result implements ResultInterface { private PDOStatement $statement; /** @internal The result can be only instantiated by its driver connection or statement. */ public function __construct(PDOStatement $statement) { $this->statement = $statement; } /** * {@inheritDoc} */ public function fetchNumeric() { return $this->fetch(PDO::FETCH_NUM); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->fetch(PDO::FETCH_ASSOC); } /** * {@inheritDoc} */ public function fetchOne() { return $this->fetch(PDO::FETCH_COLUMN); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return $this->fetchAll(PDO::FETCH_NUM); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return $this->fetchAll(PDO::FETCH_ASSOC); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return $this->fetchAll(PDO::FETCH_COLUMN); } public function rowCount(): int { try { return $this->statement->rowCount(); } catch (PDOException $exception) { throw Exception::new($exception); } } public function columnCount(): int { try { return $this->statement->columnCount(); } catch (PDOException $exception) { throw Exception::new($exception); } } public function free(): void { $this->statement->closeCursor(); } /** * @psalm-param PDO::FETCH_* $mode * * @return mixed * * @throws Exception */ private function fetch(int $mode) { try { return $this->statement->fetch($mode); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * @psalm-param PDO::FETCH_* $mode * * @return list<mixed> * * @throws Exception */ private function fetchAll(int $mode): array { try { return $this->statement->fetchAll($mode); } catch (PDOException $exception) { throw Exception::new($exception); } } } Driver/PDO/PgSQL/Driver.php 0000755 00000010151 00000000000 0011255 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\PgSQL; use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\Deprecations\Deprecation; use PDO; use PDOException; use SensitiveParameter; final class Driver extends AbstractPostgreSQLDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $driverOptions = $params['driverOptions'] ?? []; if (! empty($params['persistent'])) { $driverOptions[PDO::ATTR_PERSISTENT] = true; } $safeParams = $params; unset($safeParams['password'], $safeParams['url']); try { $pdo = new PDO( $this->constructPdoDsn($safeParams), $params['user'] ?? '', $params['password'] ?? '', $driverOptions, ); } catch (PDOException $exception) { throw Exception::new($exception); } if ( ! isset($driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES]) || $driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES] === true ) { $pdo->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true); } $connection = new Connection($pdo); /* defining client_encoding via SET NAMES to avoid inconsistent DSN support * - passing client_encoding via the 'options' param breaks pgbouncer support */ if (isset($params['charset'])) { $connection->exec('SET NAMES \'' . $params['charset'] . '\''); } return $connection; } /** * Constructs the Postgres PDO DSN. * * @param array<string, mixed> $params */ private function constructPdoDsn(array $params): string { $dsn = 'pgsql:'; if (isset($params['host']) && $params['host'] !== '') { $dsn .= 'host=' . $params['host'] . ';'; } if (isset($params['port']) && $params['port'] !== '') { $dsn .= 'port=' . $params['port'] . ';'; } if (isset($params['dbname'])) { $dsn .= 'dbname=' . $params['dbname'] . ';'; } elseif (isset($params['default_dbname'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5705', 'The "default_dbname" connection parameter is deprecated. Use "dbname" instead.', ); $dsn .= 'dbname=' . $params['default_dbname'] . ';'; } else { if (isset($params['user']) && $params['user'] !== 'postgres') { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5705', 'Relying on the DBAL connecting to the "postgres" database by default is deprecated.' . ' Unless you want to have the server determine the default database for the connection,' . ' specify the database name explicitly.', ); } // Used for temporary connections to allow operations like dropping the database currently connected to. $dsn .= 'dbname=postgres;'; } if (isset($params['sslmode'])) { $dsn .= 'sslmode=' . $params['sslmode'] . ';'; } if (isset($params['sslrootcert'])) { $dsn .= 'sslrootcert=' . $params['sslrootcert'] . ';'; } if (isset($params['sslcert'])) { $dsn .= 'sslcert=' . $params['sslcert'] . ';'; } if (isset($params['sslkey'])) { $dsn .= 'sslkey=' . $params['sslkey'] . ';'; } if (isset($params['sslcrl'])) { $dsn .= 'sslcrl=' . $params['sslcrl'] . ';'; } if (isset($params['application_name'])) { $dsn .= 'application_name=' . $params['application_name'] . ';'; } if (isset($params['gssencmode'])) { $dsn .= 'gssencmode=' . $params['gssencmode'] . ';'; } return $dsn; } } Driver/PDO/ParameterTypeMap.php 0000755 00000002173 00000000000 0012321 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\ParameterType; use PDO; /** @internal */ final class ParameterTypeMap { private const PARAM_TYPE_MAP = [ ParameterType::NULL => PDO::PARAM_NULL, ParameterType::INTEGER => PDO::PARAM_INT, ParameterType::STRING => PDO::PARAM_STR, ParameterType::ASCII => PDO::PARAM_STR, ParameterType::BINARY => PDO::PARAM_LOB, ParameterType::LARGE_OBJECT => PDO::PARAM_LOB, ParameterType::BOOLEAN => PDO::PARAM_BOOL, ]; /** * Converts DBAL parameter type to PDO parameter type * * @psalm-return PDO::PARAM_* * * @throws UnknownParameterType * * @psalm-assert ParameterType::* $type */ public static function convertParamType(int $type): int { if (! isset(self::PARAM_TYPE_MAP[$type])) { throw UnknownParameterType::new($type); } return self::PARAM_TYPE_MAP[$type]; } private function __construct() { } private function __clone() { } } Driver/PDO/PDOException.php 0000755 00000001307 00000000000 0011400 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\Exception as DriverException; /** * @internal * * @psalm-immutable */ final class PDOException extends \PDOException implements DriverException { private ?string $sqlState = null; public static function new(\PDOException $previous): self { $exception = new self($previous->message, 0, $previous); $exception->errorInfo = $previous->errorInfo; $exception->code = $previous->code; $exception->sqlState = $previous->errorInfo[0] ?? null; return $exception; } public function getSQLState(): ?string { return $this->sqlState; } } Driver/PDO/MySQL/Driver.php 0000755 00000003474 00000000000 0011306 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\MySQL; use Doctrine\DBAL\Driver\AbstractMySQLDriver; use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDO\Exception; use PDO; use PDOException; use SensitiveParameter; final class Driver extends AbstractMySQLDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $driverOptions = $params['driverOptions'] ?? []; if (! empty($params['persistent'])) { $driverOptions[PDO::ATTR_PERSISTENT] = true; } $safeParams = $params; unset($safeParams['password'], $safeParams['url']); try { $pdo = new PDO( $this->constructPdoDsn($safeParams), $params['user'] ?? '', $params['password'] ?? '', $driverOptions, ); } catch (PDOException $exception) { throw Exception::new($exception); } return new Connection($pdo); } /** * Constructs the MySQL PDO DSN. * * @param mixed[] $params */ private function constructPdoDsn(array $params): string { $dsn = 'mysql:'; if (isset($params['host']) && $params['host'] !== '') { $dsn .= 'host=' . $params['host'] . ';'; } if (isset($params['port'])) { $dsn .= 'port=' . $params['port'] . ';'; } if (isset($params['dbname'])) { $dsn .= 'dbname=' . $params['dbname'] . ';'; } if (isset($params['unix_socket'])) { $dsn .= 'unix_socket=' . $params['unix_socket'] . ';'; } if (isset($params['charset'])) { $dsn .= 'charset=' . $params['charset'] . ';'; } return $dsn; } } Driver/PDO/Connection.php 0000755 00000010042 00000000000 0011172 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\Driver\PDO\PDOException as DriverPDOException; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use PDO; use PDOException; use PDOStatement; use function assert; final class Connection implements ServerInfoAwareConnection { private PDO $connection; /** @internal The connection can be only instantiated by its driver. */ public function __construct(PDO $connection) { $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->connection = $connection; } public function exec(string $sql): int { try { $result = $this->connection->exec($sql); assert($result !== false); return $result; } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritDoc} */ public function getServerVersion() { return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); } /** * {@inheritDoc} * * @return Statement */ public function prepare(string $sql): StatementInterface { try { $stmt = $this->connection->prepare($sql); assert($stmt instanceof PDOStatement); return new Statement($stmt); } catch (PDOException $exception) { throw Exception::new($exception); } } public function query(string $sql): ResultInterface { try { $stmt = $this->connection->query($sql); assert($stmt instanceof PDOStatement); return new Result($stmt); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritDoc} * * @throws UnknownParameterType * * @psalm-assert ParameterType::* $type */ public function quote($value, $type = ParameterType::STRING) { return $this->connection->quote($value, ParameterTypeMap::convertParamType($type)); } /** * {@inheritDoc} */ public function lastInsertId($name = null) { try { if ($name === null) { return $this->connection->lastInsertId(); } Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); return $this->connection->lastInsertId($name); } catch (PDOException $exception) { throw Exception::new($exception); } } public function beginTransaction(): bool { try { return $this->connection->beginTransaction(); } catch (PDOException $exception) { throw DriverPDOException::new($exception); } } public function commit(): bool { try { return $this->connection->commit(); } catch (PDOException $exception) { throw DriverPDOException::new($exception); } } public function rollBack(): bool { try { return $this->connection->rollBack(); } catch (PDOException $exception) { throw DriverPDOException::new($exception); } } public function getNativeConnection(): PDO { return $this->connection; } /** @deprecated Call {@see getNativeConnection()} instead. */ public function getWrappedConnection(): PDO { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5037', '%s is deprecated, call getNativeConnection() instead.', __METHOD__, ); return $this->getNativeConnection(); } } Driver/PDO/SQLSrv/Connection.php 0000755 00000003553 00000000000 0012335 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLSrv; use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\Deprecations\Deprecation; use PDO; final class Connection extends AbstractConnectionMiddleware { private PDOConnection $connection; public function __construct(PDOConnection $connection) { parent::__construct($connection); $this->connection = $connection; } public function prepare(string $sql): StatementInterface { return new Statement( $this->connection->prepare($sql), ); } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name === null) { return parent::lastInsertId($name); } Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); $statement = $this->prepare( 'SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?', ); $statement->bindValue(1, $name); return $statement->execute() ->fetchOne(); } public function getNativeConnection(): PDO { return $this->connection->getNativeConnection(); } /** @deprecated Call {@see getNativeConnection()} instead. */ public function getWrappedConnection(): PDO { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5037', '%s is deprecated, call getNativeConnection() instead.', __METHOD__, ); return $this->connection->getWrappedConnection(); } } Driver/PDO/SQLSrv/Statement.php 0000755 00000006327 00000000000 0012204 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLSrv; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; use Doctrine\DBAL\Driver\PDO\Statement as PDOStatement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use PDO; use function func_num_args; final class Statement extends AbstractStatementMiddleware { private PDOStatement $statement; /** @internal The statement can be only instantiated by its driver connection. */ public function __construct(PDOStatement $statement) { parent::__construct($statement); $this->statement = $statement; } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. * * @param string|int $param * @param mixed $variable * @param int $type * @param int|null $length * @param mixed $driverOptions The usage of the argument is deprecated. * * @throws UnknownParameterType * * @psalm-assert ParameterType::* $type */ public function bindParam( $param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null ): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (func_num_args() > 4) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4533', 'The $driverOptions argument of Statement::bindParam() is deprecated.', ); } switch ($type) { case ParameterType::LARGE_OBJECT: case ParameterType::BINARY: $driverOptions ??= PDO::SQLSRV_ENCODING_BINARY; break; case ParameterType::ASCII: $type = ParameterType::STRING; $length = 0; $driverOptions = PDO::SQLSRV_ENCODING_SYSTEM; break; } return $this->statement->bindParam($param, $variable, $type, $length ?? 0, $driverOptions); } /** * @throws UnknownParameterType * * {@inheritDoc} * * @psalm-assert ParameterType::* $type */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->bindParam($param, $value, $type); } } Driver/PDO/SQLSrv/Driver.php 0000755 00000005660 00000000000 0011472 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLSrv; use Doctrine\DBAL\Driver\AbstractSQLServerDriver; use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection; use Doctrine\DBAL\Driver\PDO\Exception as PDOException; use PDO; use SensitiveParameter; use function is_int; use function sprintf; final class Driver extends AbstractSQLServerDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $driverOptions = $dsnOptions = []; if (isset($params['driverOptions'])) { foreach ($params['driverOptions'] as $option => $value) { if (is_int($option)) { $driverOptions[$option] = $value; } else { $dsnOptions[$option] = $value; } } } if (! empty($params['persistent'])) { $driverOptions[PDO::ATTR_PERSISTENT] = true; } $safeParams = $params; unset($safeParams['password'], $safeParams['url']); try { $pdo = new PDO( $this->constructDsn($safeParams, $dsnOptions), $params['user'] ?? '', $params['password'] ?? '', $driverOptions, ); } catch (\PDOException $exception) { throw PDOException::new($exception); } return new Connection(new PDOConnection($pdo)); } /** * Constructs the Sqlsrv PDO DSN. * * @param mixed[] $params * @param string[] $connectionOptions * * @throws Exception */ private function constructDsn(array $params, array $connectionOptions): string { $dsn = 'sqlsrv:server='; if (isset($params['host'])) { $dsn .= $params['host']; if (isset($params['port'])) { $dsn .= ',' . $params['port']; } } elseif (isset($params['port'])) { throw PortWithoutHost::new(); } if (isset($params['dbname'])) { $connectionOptions['Database'] = $params['dbname']; } if (isset($params['MultipleActiveResultSets'])) { $connectionOptions['MultipleActiveResultSets'] = $params['MultipleActiveResultSets'] ? 'true' : 'false'; } return $dsn . $this->getConnectionOptionsDsn($connectionOptions); } /** * Converts a connection options array to the DSN * * @param string[] $connectionOptions */ private function getConnectionOptionsDsn(array $connectionOptions): string { $connectionOptionsDsn = ''; foreach ($connectionOptions as $paramName => $paramValue) { $connectionOptionsDsn .= sprintf(';%s=%s', $paramName, $paramValue); } return $connectionOptionsDsn; } } Driver/PDO/SQLite/Driver.php 0000755 00000004140 00000000000 0011471 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLite; use Doctrine\DBAL\Driver\AbstractSQLiteDriver; use Doctrine\DBAL\Driver\API\SQLite\UserDefinedFunctions; use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\Deprecations\Deprecation; use PDO; use PDOException; use SensitiveParameter; use function array_intersect_key; final class Driver extends AbstractSQLiteDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $driverOptions = $params['driverOptions'] ?? []; $userDefinedFunctions = []; if (isset($driverOptions['userDefinedFunctions'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5742', 'The SQLite-specific driver option "userDefinedFunctions" is deprecated.' . ' Register function directly on the native connection instead.', ); $userDefinedFunctions = $driverOptions['userDefinedFunctions']; unset($driverOptions['userDefinedFunctions']); } try { $pdo = new PDO( $this->constructPdoDsn(array_intersect_key($params, ['path' => true, 'memory' => true])), $params['user'] ?? '', $params['password'] ?? '', $driverOptions, ); } catch (PDOException $exception) { throw Exception::new($exception); } UserDefinedFunctions::register( [$pdo, 'sqliteCreateFunction'], $userDefinedFunctions, ); return new Connection($pdo); } /** * Constructs the Sqlite PDO DSN. * * @param array<string, mixed> $params */ private function constructPdoDsn(array $params): string { $dsn = 'sqlite:'; if (isset($params['path'])) { $dsn .= $params['path']; } elseif (isset($params['memory'])) { $dsn .= ':memory:'; } return $dsn; } } Driver/PDO/Statement.php 0000755 00000007636 00000000000 0011056 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use PDOException; use PDOStatement; use function array_slice; use function func_get_args; use function func_num_args; final class Statement implements StatementInterface { private PDOStatement $stmt; /** @internal The statement can be only instantiated by its driver connection. */ public function __construct(PDOStatement $stmt) { $this->stmt = $stmt; } /** * {@inheritDoc} * * @throws UnknownParameterType * * @psalm-assert ParameterType::* $type */ public function bindValue($param, $value, $type = ParameterType::STRING) { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } $pdoType = ParameterTypeMap::convertParamType($type); try { return $this->stmt->bindValue($param, $value, $pdoType); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. * * @param mixed $param * @param mixed $variable * @param int $type * @param int|null $length * @param mixed $driverOptions The usage of the argument is deprecated. * * @throws UnknownParameterType * * @psalm-assert ParameterType::* $type */ public function bindParam( $param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null ): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (func_num_args() > 4) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4533', 'The $driverOptions argument of Statement::bindParam() is deprecated.', ); } $pdoType = ParameterTypeMap::convertParamType($type); try { return $this->stmt->bindParam( $param, $variable, $pdoType, $length ?? 0, ...array_slice(func_get_args(), 4), ); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); } try { $this->stmt->execute($params); } catch (PDOException $exception) { throw Exception::new($exception); } return new Result($this->stmt); } } Driver/PDO/Exception.php 0000755 00000001151 00000000000 0011032 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\AbstractException; use PDOException; /** * @internal * * @psalm-immutable */ final class Exception extends AbstractException { public static function new(PDOException $exception): self { if ($exception->errorInfo !== null) { [$sqlState, $code] = $exception->errorInfo; $code ??= 0; } else { $code = $exception->getCode(); $sqlState = null; } return new self($exception->getMessage(), $sqlState, $code, $exception); } } Driver/Mysqli/Exception/FailedReadingStreamOffset.php 0000755 00000000657 00000000000 0016701 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class FailedReadingStreamOffset extends AbstractException { public static function new(int $parameter): self { return new self(sprintf('Failed reading the stream resource for parameter #%d.', $parameter)); } } Driver/Mysqli/Exception/InvalidCharset.php 0000755 00000002021 00000000000 0014563 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use mysqli; use mysqli_sql_exception; use ReflectionProperty; use function sprintf; /** * @internal * * @psalm-immutable */ final class InvalidCharset extends AbstractException { public static function fromCharset(mysqli $connection, string $charset): self { return new self( sprintf('Failed to set charset "%s": %s', $charset, $connection->error), $connection->sqlstate, $connection->errno, ); } public static function upcast(mysqli_sql_exception $exception, string $charset): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); $p->setAccessible(true); return new self( sprintf('Failed to set charset "%s": %s', $charset, $exception->getMessage()), $p->getValue($exception), (int) $exception->getCode(), $exception, ); } } Driver/Mysqli/Exception/StatementError.php 0000755 00000001402 00000000000 0014643 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use mysqli_sql_exception; use mysqli_stmt; use ReflectionProperty; /** * @internal * * @psalm-immutable */ final class StatementError extends AbstractException { public static function new(mysqli_stmt $statement): self { return new self($statement->error, $statement->sqlstate, $statement->errno); } public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); $p->setAccessible(true); return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); } } Driver/Mysqli/Exception/NonStreamResourceUsedAsLargeObject.php 0000755 00000000746 00000000000 0020524 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class NonStreamResourceUsedAsLargeObject extends AbstractException { public static function new(int $parameter): self { return new self( sprintf('The resource passed as a LARGE_OBJECT parameter #%d must be of type "stream"', $parameter), ); } } Driver/Mysqli/Exception/ConnectionError.php 0000755 00000001375 00000000000 0015007 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use mysqli; use mysqli_sql_exception; use ReflectionProperty; /** * @internal * * @psalm-immutable */ final class ConnectionError extends AbstractException { public static function new(mysqli $connection): self { return new self($connection->error, $connection->sqlstate, $connection->errno); } public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); $p->setAccessible(true); return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); } } Driver/Mysqli/Exception/InvalidOption.php 0000755 00000000734 00000000000 0014453 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class InvalidOption extends AbstractException { /** @param mixed $value */ public static function fromOption(int $option, $value): self { return new self( sprintf('Failed to set option %d with value "%s"', $option, $value), ); } } Driver/Mysqli/Exception/HostRequired.php 0000755 00000000603 00000000000 0014305 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class HostRequired extends AbstractException { public static function forPersistentConnection(): self { return new self('The "host" parameter is required for a persistent connection'); } } Driver/Mysqli/Exception/ConnectionFailed.php 0000755 00000001521 00000000000 0015073 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use mysqli; use mysqli_sql_exception; use ReflectionProperty; use function assert; /** * @internal * * @psalm-immutable */ final class ConnectionFailed extends AbstractException { public static function new(mysqli $connection): self { $error = $connection->connect_error; assert($error !== null); return new self($error, 'HY000', $connection->connect_errno); } public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); $p->setAccessible(true); return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); } } Driver/Mysqli/Result.php 0000755 00000011313 00000000000 0011207 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError; use Doctrine\DBAL\Driver\Result as ResultInterface; use mysqli_sql_exception; use mysqli_stmt; use function array_column; use function array_combine; use function array_fill; use function count; final class Result implements ResultInterface { private mysqli_stmt $statement; /** * Whether the statement result has columns. The property should be used only after the result metadata * has been fetched ({@see $metadataFetched}). Otherwise, the property value is undetermined. */ private bool $hasColumns = false; /** * Mapping of statement result column indexes to their names. The property should be used only * if the statement result has columns ({@see $hasColumns}). Otherwise, the property value is undetermined. * * @var array<int,string> */ private array $columnNames = []; /** @var mixed[] */ private array $boundValues = []; /** * @internal The result can be only instantiated by its driver connection or statement. * * @throws Exception */ public function __construct(mysqli_stmt $statement) { $this->statement = $statement; $meta = $statement->result_metadata(); if ($meta === false) { return; } $this->hasColumns = true; $this->columnNames = array_column($meta->fetch_fields(), 'name'); $meta->free(); // Store result of every execution which has it. Otherwise it will be impossible // to execute a new statement in case if the previous one has non-fetched rows // @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html $this->statement->store_result(); // Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql, // it will have to allocate as much memory as it may be needed for the given column type // (e.g. for a LONGBLOB column it's 4 gigabytes) // @link https://bugs.php.net/bug.php?id=51386#1270673122 // // Make sure that the values are bound after each execution. Otherwise, if free() has been // previously called on the result, the values are unbound making the statement unusable. // // It's also important that row values are bound after _each_ call to store_result(). Otherwise, // if mysqli is compiled with libmysql, subsequently fetched string values will get truncated // to the length of the ones fetched during the previous execution. $this->boundValues = array_fill(0, count($this->columnNames), null); // The following is necessary as PHP cannot handle references to properties properly $refs = &$this->boundValues; if (! $this->statement->bind_result(...$refs)) { throw StatementError::new($this->statement); } } /** * {@inheritDoc} */ public function fetchNumeric() { try { $ret = $this->statement->fetch(); } catch (mysqli_sql_exception $e) { throw StatementError::upcast($e); } if ($ret === false) { throw StatementError::new($this->statement); } if ($ret === null) { return false; } $values = []; foreach ($this->boundValues as $v) { $values[] = $v; } return $values; } /** * {@inheritDoc} */ public function fetchAssociative() { $values = $this->fetchNumeric(); if ($values === false) { return false; } return array_combine($this->columnNames, $values); } /** * {@inheritDoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } public function rowCount(): int { if ($this->hasColumns) { return $this->statement->num_rows; } return $this->statement->affected_rows; } public function columnCount(): int { return $this->statement->field_count; } public function free(): void { $this->statement->free_result(); } } Driver/Mysqli/Initializer/Secure.php 0000755 00000001526 00000000000 0013447 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Initializer; use Doctrine\DBAL\Driver\Mysqli\Initializer; use mysqli; use SensitiveParameter; final class Secure implements Initializer { private string $key; private string $cert; private string $ca; private string $capath; private string $cipher; public function __construct( #[SensitiveParameter] string $key, string $cert, string $ca, string $capath, string $cipher ) { $this->key = $key; $this->cert = $cert; $this->ca = $ca; $this->capath = $capath; $this->cipher = $cipher; } public function initialize(mysqli $connection): void { $connection->ssl_set($this->key, $this->cert, $this->ca, $this->capath, $this->cipher); } } Driver/Mysqli/Initializer/Charset.php 0000755 00000001431 00000000000 0013605 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Initializer; use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidCharset; use Doctrine\DBAL\Driver\Mysqli\Initializer; use mysqli; use mysqli_sql_exception; final class Charset implements Initializer { private string $charset; public function __construct(string $charset) { $this->charset = $charset; } public function initialize(mysqli $connection): void { try { $success = $connection->set_charset($this->charset); } catch (mysqli_sql_exception $e) { throw InvalidCharset::upcast($e, $this->charset); } if ($success) { return; } throw InvalidCharset::fromCharset($connection, $this->charset); } } Driver/Mysqli/Initializer/Options.php 0000755 00000001370 00000000000 0013651 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Initializer; use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidOption; use Doctrine\DBAL\Driver\Mysqli\Initializer; use mysqli; use function mysqli_options; final class Options implements Initializer { /** @var array<int,mixed> */ private array $options; /** @param array<int,mixed> $options */ public function __construct(array $options) { $this->options = $options; } public function initialize(mysqli $connection): void { foreach ($this->options as $option => $value) { if (! mysqli_options($connection, $option, $value)) { throw InvalidOption::fromOption($option, $value); } } } } Driver/Mysqli/Initializer.php 0000755 00000000353 00000000000 0012216 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\Exception; use mysqli; interface Initializer { /** @throws Exception */ public function initialize(mysqli $connection): void; } Driver/Mysqli/Connection.php 0000755 00000006710 00000000000 0012035 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use mysqli; use mysqli_sql_exception; final class Connection implements ServerInfoAwareConnection { /** * Name of the option to set connection flags */ public const OPTION_FLAGS = 'flags'; private mysqli $connection; /** @internal The connection can be only instantiated by its driver. */ public function __construct(mysqli $connection) { $this->connection = $connection; } /** * Retrieves mysqli native resource handle. * * Could be used if part of your application is not using DBAL. * * @deprecated Call {@see getNativeConnection()} instead. */ public function getWrappedResourceHandle(): mysqli { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5037', '%s is deprecated, call getNativeConnection() instead.', __METHOD__, ); return $this->getNativeConnection(); } public function getServerVersion(): string { return $this->connection->get_server_info(); } public function prepare(string $sql): DriverStatement { try { $stmt = $this->connection->prepare($sql); } catch (mysqli_sql_exception $e) { throw ConnectionError::upcast($e); } if ($stmt === false) { throw ConnectionError::new($this->connection); } return new Statement($stmt); } public function query(string $sql): ResultInterface { return $this->prepare($sql)->execute(); } /** * {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { return "'" . $this->connection->escape_string($value) . "'"; } public function exec(string $sql): int { try { $result = $this->connection->query($sql); } catch (mysqli_sql_exception $e) { throw ConnectionError::upcast($e); } if ($result === false) { throw ConnectionError::new($this->connection); } return $this->connection->affected_rows; } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name !== null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); } return $this->connection->insert_id; } public function beginTransaction(): bool { $this->connection->begin_transaction(); return true; } public function commit(): bool { try { return $this->connection->commit(); } catch (mysqli_sql_exception $e) { return false; } } public function rollBack(): bool { try { return $this->connection->rollback(); } catch (mysqli_sql_exception $e) { return false; } } public function getNativeConnection(): mysqli { return $this->connection; } } Driver/Mysqli/Statement.php 0000755 00000015605 00000000000 0011705 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\Driver\Mysqli\Exception\FailedReadingStreamOffset; use Doctrine\DBAL\Driver\Mysqli\Exception\NonStreamResourceUsedAsLargeObject; use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use mysqli_sql_exception; use mysqli_stmt; use function array_fill; use function assert; use function count; use function feof; use function fread; use function func_num_args; use function get_resource_type; use function is_int; use function is_resource; use function str_repeat; final class Statement implements StatementInterface { private const PARAM_TYPE_MAP = [ ParameterType::ASCII => 's', ParameterType::STRING => 's', ParameterType::BINARY => 's', ParameterType::BOOLEAN => 'i', ParameterType::NULL => 's', ParameterType::INTEGER => 'i', ParameterType::LARGE_OBJECT => 'b', ]; private mysqli_stmt $stmt; /** @var mixed[] */ private array $boundValues; private string $types; /** * Contains ref values for bindValue(). * * @var mixed[] */ private array $values = []; /** @internal The statement can be only instantiated by its driver connection. */ public function __construct(mysqli_stmt $stmt) { $this->stmt = $stmt; $paramCount = $this->stmt->param_count; $this->types = str_repeat('s', $paramCount); $this->boundValues = array_fill(1, $paramCount, null); } /** * @deprecated Use {@see bindValue()} instead. * * {@inheritDoc} * * @psalm-assert ParameterType::* $type */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (! isset(self::PARAM_TYPE_MAP[$type])) { throw UnknownParameterType::new($type); } $this->boundValues[$param] =& $variable; $this->types[$param - 1] = self::PARAM_TYPE_MAP[$type]; return true; } /** * {@inheritDoc} * * @psalm-assert ParameterType::* $type */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (! isset(self::PARAM_TYPE_MAP[$type])) { throw UnknownParameterType::new($type); } $this->values[$param] = $value; $this->boundValues[$param] =& $this->values[$param]; $this->types[$param - 1] = self::PARAM_TYPE_MAP[$type]; return true; } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); } if ($params !== null && count($params) > 0) { if (! $this->bindUntypedValues($params)) { throw StatementError::new($this->stmt); } } elseif (count($this->boundValues) > 0) { $this->bindTypedParameters(); } try { $result = $this->stmt->execute(); } catch (mysqli_sql_exception $e) { throw StatementError::upcast($e); } if (! $result) { throw StatementError::new($this->stmt); } return new Result($this->stmt); } /** * Binds parameters with known types previously bound to the statement * * @throws Exception */ private function bindTypedParameters(): void { $streams = $values = []; $types = $this->types; foreach ($this->boundValues as $parameter => $value) { assert(is_int($parameter)); if (! isset($types[$parameter - 1])) { $types[$parameter - 1] = self::PARAM_TYPE_MAP[ParameterType::STRING]; } if ($types[$parameter - 1] === self::PARAM_TYPE_MAP[ParameterType::LARGE_OBJECT]) { if (is_resource($value)) { if (get_resource_type($value) !== 'stream') { throw NonStreamResourceUsedAsLargeObject::new($parameter); } $streams[$parameter] = $value; $values[$parameter] = null; continue; } $types[$parameter - 1] = self::PARAM_TYPE_MAP[ParameterType::STRING]; } $values[$parameter] = $value; } if (! $this->stmt->bind_param($types, ...$values)) { throw StatementError::new($this->stmt); } $this->sendLongData($streams); } /** * Handle $this->_longData after regular query parameters have been bound * * @param array<int, resource> $streams * * @throws Exception */ private function sendLongData(array $streams): void { foreach ($streams as $paramNr => $stream) { while (! feof($stream)) { $chunk = fread($stream, 8192); if ($chunk === false) { throw FailedReadingStreamOffset::new($paramNr); } if (! $this->stmt->send_long_data($paramNr - 1, $chunk)) { throw StatementError::new($this->stmt); } } } } /** * Binds a array of values to bound parameters. * * @param mixed[] $values */ private function bindUntypedValues(array $values): bool { return $this->stmt->bind_param(str_repeat('s', count($values)), ...$values); } } Driver/Mysqli/Driver.php 0000755 00000006212 00000000000 0011166 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\AbstractMySQLDriver; use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionFailed; use Doctrine\DBAL\Driver\Mysqli\Exception\HostRequired; use Doctrine\DBAL\Driver\Mysqli\Initializer\Charset; use Doctrine\DBAL\Driver\Mysqli\Initializer\Options; use Doctrine\DBAL\Driver\Mysqli\Initializer\Secure; use Generator; use mysqli; use mysqli_sql_exception; use SensitiveParameter; final class Driver extends AbstractMySQLDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { if (! empty($params['persistent'])) { if (! isset($params['host'])) { throw HostRequired::forPersistentConnection(); } $host = 'p:' . $params['host']; } else { $host = $params['host'] ?? null; } $connection = new mysqli(); foreach ($this->compilePreInitializers($params) as $initializer) { $initializer->initialize($connection); } try { $success = @$connection->real_connect( $host, $params['user'] ?? null, $params['password'] ?? null, $params['dbname'] ?? null, $params['port'] ?? null, $params['unix_socket'] ?? null, $params['driverOptions'][Connection::OPTION_FLAGS] ?? 0, ); } catch (mysqli_sql_exception $e) { throw ConnectionFailed::upcast($e); } if (! $success) { throw ConnectionFailed::new($connection); } foreach ($this->compilePostInitializers($params) as $initializer) { $initializer->initialize($connection); } return new Connection($connection); } /** * @param array<string, mixed> $params * * @return Generator<int, Initializer> */ private function compilePreInitializers( #[SensitiveParameter] array $params ): Generator { unset($params['driverOptions'][Connection::OPTION_FLAGS]); if (isset($params['driverOptions']) && $params['driverOptions'] !== []) { yield new Options($params['driverOptions']); } if ( ! isset($params['ssl_key']) && ! isset($params['ssl_cert']) && ! isset($params['ssl_ca']) && ! isset($params['ssl_capath']) && ! isset($params['ssl_cipher']) ) { return; } yield new Secure( $params['ssl_key'] ?? '', $params['ssl_cert'] ?? '', $params['ssl_ca'] ?? '', $params['ssl_capath'] ?? '', $params['ssl_cipher'] ?? '', ); } /** * @param array<string, mixed> $params * * @return Generator<int, Initializer> */ private function compilePostInitializers( #[SensitiveParameter] array $params ): Generator { if (! isset($params['charset'])) { return; } yield new Charset($params['charset']); } } Driver/Connection.php 0000755 00000003620 00000000000 0010554 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\ParameterType; /** * Connection interface. * Driver connections must implement this interface. * * @method resource|object getNativeConnection() */ interface Connection { /** * Prepares a statement for execution and returns a Statement object. * * @throws Exception */ public function prepare(string $sql): Statement; /** * Executes an SQL statement, returning a result set as a Statement object. * * @throws Exception */ public function query(string $sql): Result; /** * Quotes a string for use in a query. * * The usage of this method is discouraged. Use prepared statements * or {@see AbstractPlatform::quoteStringLiteral()} instead. * * @param mixed $value * @param int $type * * @return mixed */ public function quote($value, $type = ParameterType::STRING); /** * Executes an SQL statement and return the number of affected rows. * * @throws Exception */ public function exec(string $sql): int; /** * Returns the ID of the last inserted row or sequence value. * * @param string|null $name * * @return string|int|false * * @throws Exception */ public function lastInsertId($name = null); /** * Initiates a transaction. * * @return bool TRUE on success or FALSE on failure. * * @throws Exception */ public function beginTransaction(); /** * Commits a transaction. * * @return bool TRUE on success or FALSE on failure. * * @throws Exception */ public function commit(); /** * Rolls back the current transaction, as initiated by beginTransaction(). * * @return bool TRUE on success or FALSE on failure. * * @throws Exception */ public function rollBack(); } Driver/SQLSrv/Exception/Error.php 0000755 00000001727 00000000000 0012644 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\SQLSrv\Exception; use Doctrine\DBAL\Driver\AbstractException; use function rtrim; use function sqlsrv_errors; use const SQLSRV_ERR_ERRORS; /** * @internal * * @psalm-immutable */ final class Error extends AbstractException { public static function new(): self { $message = ''; $sqlState = null; $code = 0; foreach ((array) sqlsrv_errors(SQLSRV_ERR_ERRORS) as $error) { $message .= 'SQLSTATE [' . $error['SQLSTATE'] . ', ' . $error['code'] . ']: ' . $error['message'] . "\n"; $sqlState ??= $error['SQLSTATE']; if ($code !== 0) { continue; } $code = $error['code']; } if ($message === '') { $message = 'SQL Server error occurred but no error message was retrieved from driver.'; } return new self(rtrim($message), $sqlState, $code); } } Driver/SQLSrv/Result.php 0000755 00000004760 00000000000 0011073 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result as ResultInterface; use function sqlsrv_fetch; use function sqlsrv_fetch_array; use function sqlsrv_num_fields; use function sqlsrv_rows_affected; use const SQLSRV_FETCH_ASSOC; use const SQLSRV_FETCH_NUMERIC; final class Result implements ResultInterface { /** @var resource */ private $statement; /** * @internal The result can be only instantiated by its driver connection or statement. * * @param resource $stmt */ public function __construct($stmt) { $this->statement = $stmt; } /** * {@inheritDoc} */ public function fetchNumeric() { return $this->fetch(SQLSRV_FETCH_NUMERIC); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->fetch(SQLSRV_FETCH_ASSOC); } /** * {@inheritDoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } public function rowCount(): int { $count = sqlsrv_rows_affected($this->statement); if ($count !== false) { return $count; } return 0; } public function columnCount(): int { $count = sqlsrv_num_fields($this->statement); if ($count !== false) { return $count; } return 0; } public function free(): void { // emulate it by fetching and discarding rows, similarly to what PDO does in this case // @link http://php.net/manual/en/pdostatement.closecursor.php // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075 // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them while (sqlsrv_fetch($this->statement)) { } } /** @return mixed|false */ private function fetch(int $fetchType) { return sqlsrv_fetch_array($this->statement, $fetchType) ?? false; } } Driver/SQLSrv/Connection.php 0000755 00000006450 00000000000 0011712 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\SQLSrv\Exception\Error; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function is_float; use function is_int; use function sprintf; use function sqlsrv_begin_transaction; use function sqlsrv_commit; use function sqlsrv_query; use function sqlsrv_rollback; use function sqlsrv_rows_affected; use function sqlsrv_server_info; use function str_replace; final class Connection implements ServerInfoAwareConnection { /** @var resource */ private $connection; /** * @internal The connection can be only instantiated by its driver. * * @param resource $connection */ public function __construct($connection) { $this->connection = $connection; } /** * {@inheritDoc} */ public function getServerVersion() { $serverInfo = sqlsrv_server_info($this->connection); return $serverInfo['SQLServerVersion']; } public function prepare(string $sql): DriverStatement { return new Statement($this->connection, $sql); } public function query(string $sql): ResultInterface { return $this->prepare($sql)->execute(); } /** * {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { if (is_int($value)) { return $value; } if (is_float($value)) { return sprintf('%F', $value); } return "'" . str_replace("'", "''", $value) . "'"; } public function exec(string $sql): int { $stmt = sqlsrv_query($this->connection, $sql); if ($stmt === false) { throw Error::new(); } $rowsAffected = sqlsrv_rows_affected($stmt); if ($rowsAffected === false) { throw Error::new(); } return $rowsAffected; } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name !== null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); $result = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?') ->execute([$name]); } else { $result = $this->query('SELECT @@IDENTITY'); } return $result->fetchOne(); } public function beginTransaction(): bool { if (! sqlsrv_begin_transaction($this->connection)) { throw Error::new(); } return true; } public function commit(): bool { if (! sqlsrv_commit($this->connection)) { throw Error::new(); } return true; } public function rollBack(): bool { if (! sqlsrv_rollback($this->connection)) { throw Error::new(); } return true; } /** @return resource */ public function getNativeConnection() { return $this->connection; } } Driver/SQLSrv/Statement.php 0000755 00000013510 00000000000 0011552 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\SQLSrv\Exception\Error; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function assert; use function func_num_args; use function is_int; use function sqlsrv_execute; use function SQLSRV_PHPTYPE_STREAM; use function SQLSRV_PHPTYPE_STRING; use function sqlsrv_prepare; use function SQLSRV_SQLTYPE_VARBINARY; use function stripos; use const SQLSRV_ENC_BINARY; use const SQLSRV_ENC_CHAR; use const SQLSRV_PARAM_IN; final class Statement implements StatementInterface { /** * The SQLSRV Resource. * * @var resource */ private $conn; /** * The SQL statement to execute. */ private string $sql; /** * The SQLSRV statement resource. * * @var resource|null */ private $stmt; /** * References to the variables bound as statement parameters. * * @var array<int, mixed> */ private array $variables = []; /** * Bound parameter types. * * @var array<int, int> */ private array $types = []; /** * Append to any INSERT query to retrieve the last insert id. */ private const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;'; /** * @internal The statement can be only instantiated by its driver connection. * * @param resource $conn * @param string $sql */ public function __construct($conn, $sql) { $this->conn = $conn; $this->sql = $sql; if (stripos($sql, 'INSERT INTO ') !== 0) { return; } $this->sql .= self::LAST_INSERT_ID_SQL; } /** * {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } $this->variables[$param] = $value; $this->types[$param] = $type; return true; } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } $this->variables[$param] =& $variable; $this->types[$param] = $type; // unset the statement resource if it exists as the new one will need to be bound to the new variable $this->stmt = null; return true; } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); foreach ($params as $key => $val) { if (is_int($key)) { $this->bindValue($key + 1, $val, ParameterType::STRING); } else { $this->bindValue($key, $val, ParameterType::STRING); } } } $this->stmt ??= $this->prepare(); if (! sqlsrv_execute($this->stmt)) { throw Error::new(); } return new Result($this->stmt); } /** * Prepares SQL Server statement resource * * @return resource * * @throws Exception */ private function prepare() { $params = []; foreach ($this->variables as $column => &$variable) { switch ($this->types[$column]) { case ParameterType::LARGE_OBJECT: $params[$column - 1] = [ &$variable, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), SQLSRV_SQLTYPE_VARBINARY('max'), ]; break; case ParameterType::BINARY: $params[$column - 1] = [ &$variable, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), ]; break; case ParameterType::ASCII: $params[$column - 1] = [ &$variable, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), ]; break; default: $params[$column - 1] =& $variable; break; } } $stmt = sqlsrv_prepare($this->conn, $this->sql, $params); if ($stmt === false) { throw Error::new(); } return $stmt; } } Driver/SQLSrv/Driver.php 0000755 00000003414 00000000000 0011043 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\AbstractSQLServerDriver; use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost; use Doctrine\DBAL\Driver\SQLSrv\Exception\Error; use SensitiveParameter; use function sqlsrv_configure; use function sqlsrv_connect; /** * Driver for ext/sqlsrv. */ final class Driver extends AbstractSQLServerDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $serverName = ''; if (isset($params['host'])) { $serverName = $params['host']; if (isset($params['port'])) { $serverName .= ',' . $params['port']; } } elseif (isset($params['port'])) { throw PortWithoutHost::new(); } $driverOptions = $params['driverOptions'] ?? []; if (isset($params['dbname'])) { $driverOptions['Database'] = $params['dbname']; } if (isset($params['charset'])) { $driverOptions['CharacterSet'] = $params['charset']; } if (isset($params['user'])) { $driverOptions['UID'] = $params['user']; } if (isset($params['password'])) { $driverOptions['PWD'] = $params['password']; } if (! isset($driverOptions['ReturnDatesAsStrings'])) { $driverOptions['ReturnDatesAsStrings'] = 1; } if (! sqlsrv_configure('WarningsReturnAsErrors', 0)) { throw Error::new(); } $connection = sqlsrv_connect($serverName, $driverOptions); if ($connection === false) { throw Error::new(); } return new Connection($connection); } } Driver/AbstractDB2Driver.php 0000755 00000005671 00000000000 0011674 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\API\IBMDB2\ExceptionConverter; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\DB2111Platform; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Schema\DB2SchemaManager; use Doctrine\DBAL\VersionAwarePlatformDriver; use Doctrine\Deprecations\Deprecation; use function assert; use function preg_match; use function version_compare; /** * Abstract base implementation of the {@see Driver} interface for IBM DB2 based drivers. */ abstract class AbstractDB2Driver implements VersionAwarePlatformDriver { /** * {@inheritDoc} */ public function getDatabasePlatform() { return new DB2Platform(); } /** * {@inheritDoc} * * @deprecated Use {@link DB2Platform::createSchemaManager()} instead. */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5458', 'AbstractDB2Driver::getSchemaManager() is deprecated.' . ' Use DB2Platform::createSchemaManager() instead.', ); assert($platform instanceof DB2Platform); return new DB2SchemaManager($conn, $platform); } public function getExceptionConverter(): ExceptionConverterInterface { return new ExceptionConverter(); } /** * {@inheritDoc} */ public function createDatabasePlatformForVersion($version) { if (version_compare($this->getVersionNumber($version), '11.1', '>=')) { return new DB2111Platform(); } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5156', 'IBM DB2 < 11.1 support is deprecated and will be removed in DBAL 4.' . ' Consider upgrading to IBM DB2 11.1 or later.', ); return $this->getDatabasePlatform(); } /** * Detects IBM DB2 server version * * @param string $versionString Version string as returned by IBM DB2 server, i.e. 'DB2/LINUXX8664 11.5.8.0' * * @throws DBALException */ private function getVersionNumber(string $versionString): string { if ( preg_match( '/^(?:[^\s]+\s)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)/i', $versionString, $versionParts, ) !== 1 ) { throw DBALException::invalidPlatformVersionSpecified( $versionString, '^(?:[^\s]+\s)?<major_version>.<minor_version>.<patch_version>', ); } return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch']; } } Driver/FetchUtils.php 0000755 00000002421 00000000000 0010525 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; /** @internal */ final class FetchUtils { /** * @return mixed|false * * @throws Exception */ public static function fetchOne(Result $result) { $row = $result->fetchNumeric(); if ($row === false) { return false; } return $row[0]; } /** * @return list<list<mixed>> * * @throws Exception */ public static function fetchAllNumeric(Result $result): array { $rows = []; while (($row = $result->fetchNumeric()) !== false) { $rows[] = $row; } return $rows; } /** * @return list<array<string,mixed>> * * @throws Exception */ public static function fetchAllAssociative(Result $result): array { $rows = []; while (($row = $result->fetchAssociative()) !== false) { $rows[] = $row; } return $rows; } /** * @return list<mixed> * * @throws Exception */ public static function fetchFirstColumn(Result $result): array { $rows = []; while (($row = $result->fetchOne()) !== false) { $rows[] = $row; } return $rows; } } Driver/Statement.php 0000755 00000007617 00000000000 0010433 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\ParameterType; /** * Driver-level statement */ interface Statement { /** * Binds a value to a corresponding named (not supported by mysqli driver, see comment below) or positional * placeholder in the SQL statement that was used to prepare the statement. * * As mentioned above, the named parameters are not natively supported by the mysqli driver, use executeQuery(), * fetchAll(), fetchArray(), fetchColumn(), fetchAssoc() methods to have the named parameter emulated by doctrine. * * @param string|int $param Parameter identifier. For a prepared statement using named placeholders, * this will be a parameter name of the form :name. For a prepared statement * using question mark placeholders, this will be the 1-indexed position of the parameter. * @param mixed $value The value to bind to the parameter. * @param int $type Explicit data type for the parameter using the {@see ParameterType} * constants. * * @return bool TRUE on success or FALSE on failure. * * @throws Exception */ public function bindValue($param, $value, $type = ParameterType::STRING); /** * Binds a PHP variable to a corresponding named (not supported by mysqli driver, see comment below) or question * mark placeholder in the SQL statement that was use to prepare the statement. Unlike {@see bindValue()}, * the variable is bound as a reference and will only be evaluated at the time * that PDOStatement->execute() is called. * * As mentioned above, the named parameters are not natively supported by the mysqli driver, use executeQuery(), * fetchAll(), fetchArray(), fetchColumn(), fetchAssoc() methods to have the named parameter emulated by doctrine. * * Most parameters are input parameters, that is, parameters that are * used in a read-only fashion to build up the query. Some drivers support the invocation * of stored procedures that return data as output parameters, and some also as input/output * parameters that both send in data and are updated to receive it. * * @deprecated Use {@see bindValue()} instead. * * @param string|int $param Parameter identifier. For a prepared statement using named placeholders, * this will be a parameter name of the form :name. For a prepared statement using * question mark placeholders, this will be the 1-indexed position of the parameter. * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. * @param int $type Explicit data type for the parameter using the {@see ParameterType} * constants. * @param int|null $length You must specify maxlength when using an OUT bind * so that PHP allocates enough memory to hold the returned value. * * @return bool TRUE on success or FALSE on failure. * * @throws Exception */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null); /** * Executes a prepared statement * * If the prepared statement included parameter markers, you must either: * call {@see bindParam()} to bind PHP variables to the parameter markers: * bound variables pass their value as input and receive the output value, * if any, of their associated parameter markers or pass an array of input-only * parameter values. * * @param mixed[]|null $params A numeric array of values with as many elements as there are * bound parameters in the SQL statement being executed. * * @throws Exception */ public function execute($params = null): Result; } Driver/AbstractSQLServerDriver/Exception/PortWithoutHost.php 0000755 00000000561 00000000000 0020250 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class PortWithoutHost extends AbstractException { public static function new(): self { return new self('Connection port specified without the host'); } } Driver/AbstractMySQLDriver.php 0000755 00000017614 00000000000 0012272 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Driver\API\MySQL; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\MariaDb1010Platform; use Doctrine\DBAL\Platforms\MariaDb1027Platform; use Doctrine\DBAL\Platforms\MariaDb1043Platform; use Doctrine\DBAL\Platforms\MariaDb1052Platform; use Doctrine\DBAL\Platforms\MariaDb1060Platform; use Doctrine\DBAL\Platforms\MySQL57Platform; use Doctrine\DBAL\Platforms\MySQL80Platform; use Doctrine\DBAL\Platforms\MySQL84Platform; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Schema\MySQLSchemaManager; use Doctrine\DBAL\VersionAwarePlatformDriver; use Doctrine\Deprecations\Deprecation; use function assert; use function preg_match; use function stripos; use function version_compare; /** * Abstract base implementation of the {@see Driver} interface for MySQL based drivers. */ abstract class AbstractMySQLDriver implements VersionAwarePlatformDriver { /** * {@inheritDoc} * * @throws Exception */ public function createDatabasePlatformForVersion($version) { $mariadb = stripos($version, 'mariadb') !== false; if ($mariadb) { $mariaDbVersion = $this->getMariaDbMysqlVersionNumber($version); if (version_compare($mariaDbVersion, '10.10.0', '>=')) { return new MariaDb1010Platform(); } if (version_compare($mariaDbVersion, '10.6.0', '>=')) { return new MariaDb1060Platform(); } if (version_compare($mariaDbVersion, '10.5.2', '>=')) { return new MariaDb1052Platform(); } if (version_compare($mariaDbVersion, '10.4.3', '>=')) { return new MariaDb1043Platform(); } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6110', 'Support for MariaDB < 10.4 is deprecated and will be removed in DBAL 4.' . ' Consider upgrading to a more recent version of MariaDB.', ); if (version_compare($mariaDbVersion, '10.2.7', '>=')) { return new MariaDb1027Platform(); } } else { $oracleMysqlVersion = $this->getOracleMysqlVersionNumber($version); if (version_compare($oracleMysqlVersion, '8.4.0', '>=')) { if (! version_compare($version, '8.4.0', '>=')) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/dbal/pull/5779', 'Version detection logic for MySQL will change in DBAL 4. ' . 'Please specify the version as the server reports it, e.g. "8.4.0" instead of "8.4".', ); } return new MySQL84Platform(); } if (version_compare($oracleMysqlVersion, '8', '>=')) { if (! version_compare($version, '8.0.0', '>=')) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/dbal/pull/5779', 'Version detection logic for MySQL will change in DBAL 4. ' . 'Please specify the version as the server reports it, e.g. "8.0.31" instead of "8".', ); } return new MySQL80Platform(); } if (version_compare($oracleMysqlVersion, '5.7.9', '>=')) { if (! version_compare($version, '5.7.9', '>=')) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/dbal/pull/5779', 'Version detection logic for MySQL will change in DBAL 4. ' . 'Please specify the version as the server reports it, e.g. "5.7.40" instead of "5.7".', ); } return new MySQL57Platform(); } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5072', 'MySQL 5.6 support is deprecated and will be removed in DBAL 4.' . ' Consider upgrading to MySQL 5.7 or later.', ); } return $this->getDatabasePlatform(); } /** * Get a normalized 'version number' from the server string * returned by Oracle MySQL servers. * * @param string $versionString Version string returned by the driver, i.e. '5.7.10' * * @throws Exception */ private function getOracleMysqlVersionNumber(string $versionString): string { if ( preg_match( '/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?)?/', $versionString, $versionParts, ) !== 1 ) { throw Exception::invalidPlatformVersionSpecified( $versionString, '<major_version>.<minor_version>.<patch_version>', ); } $majorVersion = $versionParts['major']; $minorVersion = $versionParts['minor'] ?? 0; $patchVersion = $versionParts['patch'] ?? null; if ($majorVersion === '5' && $minorVersion === '7') { $patchVersion ??= '9'; } else { $patchVersion ??= '0'; } return $majorVersion . '.' . $minorVersion . '.' . $patchVersion; } /** * Detect MariaDB server version, including hack for some mariadb distributions * that starts with the prefix '5.5.5-' * * @param string $versionString Version string as returned by mariadb server, i.e. '5.5.5-Mariadb-10.0.8-xenial' * * @throws Exception */ private function getMariaDbMysqlVersionNumber(string $versionString): string { if (stripos($versionString, 'MariaDB') === 0) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/dbal/pull/5779', 'Version detection logic for MySQL will change in DBAL 4. ' . 'Please specify the version as the server reports it, ' . 'e.g. "10.9.3-MariaDB" instead of "mariadb-10.9".', ); } if ( preg_match( '/^(?:5\.5\.5-)?(mariadb-)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)/i', $versionString, $versionParts, ) !== 1 ) { throw Exception::invalidPlatformVersionSpecified( $versionString, '^(?:5\.5\.5-)?(mariadb-)?<major_version>.<minor_version>.<patch_version>', ); } return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch']; } /** * {@inheritDoc} * * @return AbstractMySQLPlatform */ public function getDatabasePlatform() { return new MySQLPlatform(); } /** * {@inheritDoc} * * @deprecated Use {@link AbstractMySQLPlatform::createSchemaManager()} instead. * * @return MySQLSchemaManager */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5458', 'AbstractMySQLDriver::getSchemaManager() is deprecated.' . ' Use MySQLPlatform::createSchemaManager() instead.', ); assert($platform instanceof AbstractMySQLPlatform); return new MySQLSchemaManager($conn, $platform); } public function getExceptionConverter(): ExceptionConverter { return new MySQL\ExceptionConverter(); } } Driver/AbstractOracleDriver.php 0000755 00000003476 00000000000 0012533 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\AbstractOracleDriver\EasyConnectString; use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Driver\API\OCI; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Schema\OracleSchemaManager; use Doctrine\Deprecations\Deprecation; use function assert; /** * Abstract base implementation of the {@see Driver} interface for Oracle based drivers. */ abstract class AbstractOracleDriver implements Driver { /** * {@inheritDoc} */ public function getDatabasePlatform() { return new OraclePlatform(); } /** * {@inheritDoc} * * @deprecated Use {@link OraclePlatform::createSchemaManager()} instead. */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5458', 'AbstractOracleDriver::getSchemaManager() is deprecated.' . ' Use OraclePlatform::createSchemaManager() instead.', ); assert($platform instanceof OraclePlatform); return new OracleSchemaManager($conn, $platform); } public function getExceptionConverter(): ExceptionConverter { return new OCI\ExceptionConverter(); } /** * Returns an appropriate Easy Connect String for the given parameters. * * @param array<string, mixed> $params The connection parameters to return the Easy Connect String for. * * @return string */ protected function getEasyConnectString(array $params) { return (string) EasyConnectString::fromConnectionParameters($params); } } Driver/Middleware/AbstractDriverMiddleware.php 0000755 00000003644 00000000000 0015455 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\VersionAwarePlatformDriver; use Doctrine\Deprecations\Deprecation; use SensitiveParameter; abstract class AbstractDriverMiddleware implements VersionAwarePlatformDriver { private Driver $wrappedDriver; public function __construct(Driver $wrappedDriver) { $this->wrappedDriver = $wrappedDriver; } /** * {@inheritDoc} */ public function connect( #[SensitiveParameter] array $params ) { return $this->wrappedDriver->connect($params); } /** * {@inheritDoc} */ public function getDatabasePlatform() { return $this->wrappedDriver->getDatabasePlatform(); } /** * {@inheritDoc} * * @deprecated Use {@link AbstractPlatform::createSchemaManager()} instead. */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5458', 'AbstractDriverMiddleware::getSchemaManager() is deprecated.' . ' Use AbstractPlatform::createSchemaManager() instead.', ); return $this->wrappedDriver->getSchemaManager($conn, $platform); } public function getExceptionConverter(): ExceptionConverter { return $this->wrappedDriver->getExceptionConverter(); } /** * {@inheritDoc} */ public function createDatabasePlatformForVersion($version) { if ($this->wrappedDriver instanceof VersionAwarePlatformDriver) { return $this->wrappedDriver->createDatabasePlatformForVersion($version); } return $this->wrappedDriver->getDatabasePlatform(); } } Driver/Middleware/AbstractConnectionMiddleware.php 0000755 00000005414 00000000000 0016316 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use LogicException; use function get_class; use function method_exists; use function sprintf; abstract class AbstractConnectionMiddleware implements ServerInfoAwareConnection { private Connection $wrappedConnection; public function __construct(Connection $wrappedConnection) { $this->wrappedConnection = $wrappedConnection; } public function prepare(string $sql): Statement { return $this->wrappedConnection->prepare($sql); } public function query(string $sql): Result { return $this->wrappedConnection->query($sql); } /** * {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { return $this->wrappedConnection->quote($value, $type); } public function exec(string $sql): int { return $this->wrappedConnection->exec($sql); } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name !== null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); } return $this->wrappedConnection->lastInsertId($name); } /** * {@inheritDoc} */ public function beginTransaction() { return $this->wrappedConnection->beginTransaction(); } /** * {@inheritDoc} */ public function commit() { return $this->wrappedConnection->commit(); } /** * {@inheritDoc} */ public function rollBack() { return $this->wrappedConnection->rollBack(); } /** * {@inheritDoc} */ public function getServerVersion() { if (! $this->wrappedConnection instanceof ServerInfoAwareConnection) { throw new LogicException('The underlying connection is not a ServerInfoAwareConnection'); } return $this->wrappedConnection->getServerVersion(); } /** @return resource|object */ public function getNativeConnection() { if (! method_exists($this->wrappedConnection, 'getNativeConnection')) { throw new LogicException(sprintf( 'The driver connection %s does not support accessing the native connection.', get_class($this->wrappedConnection), )); } return $this->wrappedConnection->getNativeConnection(); } } Driver/Middleware/AbstractStatementMiddleware.php 0000755 00000003747 00000000000 0016172 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function func_num_args; abstract class AbstractStatementMiddleware implements Statement { private Statement $wrappedStatement; public function __construct(Statement $wrappedStatement) { $this->wrappedStatement = $wrappedStatement; } /** * {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING) { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->wrappedStatement->bindValue($param, $value, $type); } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->wrappedStatement->bindParam($param, $variable, $type, $length); } /** * {@inheritDoc} */ public function execute($params = null): Result { return $this->wrappedStatement->execute($params); } } Driver/Middleware/AbstractResultMiddleware.php 0000755 00000002646 00000000000 0015501 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Driver\Result; abstract class AbstractResultMiddleware implements Result { private Result $wrappedResult; public function __construct(Result $result) { $this->wrappedResult = $result; } /** * {@inheritDoc} */ public function fetchNumeric() { return $this->wrappedResult->fetchNumeric(); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->wrappedResult->fetchAssociative(); } /** * {@inheritDoc} */ public function fetchOne() { return $this->wrappedResult->fetchOne(); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return $this->wrappedResult->fetchAllNumeric(); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return $this->wrappedResult->fetchAllAssociative(); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return $this->wrappedResult->fetchFirstColumn(); } public function rowCount(): int { return $this->wrappedResult->rowCount(); } public function columnCount(): int { return $this->wrappedResult->columnCount(); } public function free(): void { $this->wrappedResult->free(); } } Driver/Exception.php 0000755 00000000624 00000000000 0010414 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; use Throwable; /** @psalm-immutable */ interface Exception extends Throwable { /** * Returns the SQLSTATE the driver was in at the time the error occurred. * * Returns null if the driver does not provide a SQLSTATE for the error occurred. * * @return string|null */ public function getSQLState(); } Exception/ConnectionException.php 0000755 00000000736 00000000000 0013143 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; /** * An exception relating to the client connection to the beanstalkd server. */ final class ConnectionException extends ClientException { /** * @param int $errno The connection error code * @param string $errstr The connection error message */ public function __construct($errno, $errstr) { parent::__construct(sprintf('Socket error %d: %s', $errno, $errstr)); } } Exception/NotNullConstraintViolationException.php 0000755 00000000346 00000000000 0016366 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a NOT NULL constraint violation detected in the driver. * * @psalm-immutable */ class NotNullConstraintViolationException extends ConstraintViolationException { } Exception/RetryableException.php 0000755 00000000340 00000000000 0012764 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Throwable; /** * Marker interface for all exceptions where retrying the transaction makes sense. * * @psalm-immutable */ interface RetryableException extends Throwable { } Exception/DeadlockException.php 0000755 00000000347 00000000000 0012550 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a deadlock error of a transaction detected in the driver. * * @psalm-immutable */ class DeadlockException extends ServerException implements RetryableException { } Exception/NonUniqueFieldNameException.php 0000755 00000000354 00000000000 0014526 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a non-unique/ambiguous specified field name in a statement detected in the driver. * * @psalm-immutable */ class NonUniqueFieldNameException extends ServerException { } Exception/InvalidLockMode.php 0000755 00000001013 00000000000 0012156 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception; use function sprintf; /** @psalm-immutable */ class InvalidLockMode extends Exception { public static function fromLockMode(int $lockMode): self { return new self( sprintf( 'Lock mode %d is invalid. The valid values are LockMode::NONE, LockMode::OPTIMISTIC' . ', LockMode::PESSIMISTIC_READ and LockMode::PESSIMISTIC_WRITE', $lockMode, ), ); } } Exception/ForeignKeyConstraintViolationException.php 0000755 00000000354 00000000000 0017034 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a foreign key constraint violation detected in the driver. * * @psalm-immutable */ class ForeignKeyConstraintViolationException extends ConstraintViolationException { } Exception/DatabaseDoesNotExist.php 0000755 00000000212 00000000000 0013167 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** @psalm-immutable */ class DatabaseDoesNotExist extends DatabaseObjectNotFoundException { } Exception/UniqueConstraintViolationException.php 0000755 00000000343 00000000000 0016236 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a unique constraint violation detected in the driver. * * @psalm-immutable */ class UniqueConstraintViolationException extends ConstraintViolationException { } Exception/DatabaseObjectNotFoundException.php 0000755 00000000647 00000000000 0015355 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all unknown database object related errors detected in the driver. * * A database object is considered any asset that can be created in a database * such as schemas, tables, views, sequences, triggers, constraints, indexes, * functions, stored procedures etc. * * @psalm-immutable */ class DatabaseObjectNotFoundException extends ServerException { } Exception/MalformedDsnException.php 0000755 00000000424 00000000000 0013411 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use InvalidArgumentException; /** @psalm-immutable */ class MalformedDsnException extends InvalidArgumentException { public static function new(): self { return new self('Malformed database connection URL'); } } Exception/ConnectionLost.php 0000755 00000000176 00000000000 0012124 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** @psalm-immutable */ final class ConnectionLost extends ConnectionException { } Exception/TableExistsException.php 0000755 00000000354 00000000000 0013267 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for an already existing table referenced in a statement detected in the driver. * * @psalm-immutable */ class TableExistsException extends DatabaseObjectExistsException { } Exception/ServerException.php 0000755 00000000347 00000000000 0012310 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; use Pheanstalk\Exception; /** * An exception originating as a beanstalkd server error. * @internal * @extensible */ class ServerException extends Exception { } Exception/DatabaseObjectExistsException.php 0000755 00000000656 00000000000 0015100 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all already existing database object related errors detected in the driver. * * A database object is considered any asset that can be created in a database * such as schemas, tables, views, sequences, triggers, constraints, indexes, * functions, stored procedures etc. * * @psalm-immutable */ class DatabaseObjectExistsException extends ServerException { } Exception/NoKeyValue.php 0000755 00000000732 00000000000 0011203 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception; use function sprintf; /** * @internal * * @psalm-immutable */ final class NoKeyValue extends Exception { public static function fromColumnCount(int $columnCount): self { return new self( sprintf( 'Fetching as key-value pairs requires the result to contain at least 2 columns, %d given.', $columnCount, ), ); } } Exception/SchemaDoesNotExist.php 0000755 00000000210 00000000000 0012661 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** @psalm-immutable */ class SchemaDoesNotExist extends DatabaseObjectNotFoundException { } Exception/ReadOnlyException.php 0000755 00000000341 00000000000 0012551 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a write operation attempt on a read-only database element detected in the driver. * * @psalm-immutable */ class ReadOnlyException extends ServerException { } Exception/InvalidFieldNameException.php 0000755 00000000336 00000000000 0014173 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for an invalid specified field name in a statement detected in the driver. * * @psalm-immutable */ class InvalidFieldNameException extends ServerException { } Exception/TableNotFoundException.php 0000755 00000000347 00000000000 0013546 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for an unknown table referenced in a statement detected in the driver. * * @psalm-immutable */ class TableNotFoundException extends DatabaseObjectNotFoundException { } Exception/InvalidArgumentException.php 0000755 00000000615 00000000000 0014131 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception; /** * Exception to be thrown when invalid arguments are passed to any DBAL API * * @psalm-immutable */ class InvalidArgumentException extends Exception { /** @return self */ public static function fromEmptyCriteria() { return new self('Empty criteria was used, expected non-empty criteria'); } } Exception/DriverException.php 0000755 00000002646 00000000000 0012301 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Driver\Exception as TheDriverException; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Query; use function assert; /** * Base class for all errors detected in the driver. * * @psalm-immutable */ class DriverException extends Exception implements TheDriverException { /** * The query that triggered the exception, if any. */ private ?Query $query; /** * @internal * * @param TheDriverException $driverException The DBAL driver exception to chain. * @param Query|null $query The SQL query that triggered the exception, if any. */ public function __construct(TheDriverException $driverException, ?Query $query) { if ($query !== null) { $message = 'An exception occurred while executing a query: ' . $driverException->getMessage(); } else { $message = 'An exception occurred in the driver: ' . $driverException->getMessage(); } parent::__construct($message, $driverException->getCode(), $driverException); $this->query = $query; } /** * {@inheritDoc} */ public function getSQLState() { $previous = $this->getPrevious(); assert($previous instanceof TheDriverException); return $previous->getSQLState(); } public function getQuery(): ?Query { return $this->query; } } Exception/SyntaxErrorException.php 0000755 00000000310 00000000000 0013330 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a syntax error in a statement detected in the driver. * * @psalm-immutable */ class SyntaxErrorException extends ServerException { } Exception/ConstraintViolationException.php 0000755 00000000333 00000000000 0015046 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all constraint violation related errors detected in the driver. * * @psalm-immutable */ class ConstraintViolationException extends ServerException { } Exception/LockWaitTimeoutException.php 0000755 00000000367 00000000000 0014130 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a lock wait timeout error of a transaction detected in the driver. * * @psalm-immutable */ class LockWaitTimeoutException extends ServerException implements RetryableException { } Exception/DatabaseRequired.php 0000755 00000000541 00000000000 0012364 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception; use function sprintf; /** @psalm-immutable */ class DatabaseRequired extends Exception { public static function new(string $methodName): self { return new self(sprintf('A database is required for the method: %s.', $methodName)); } } Events.php 0000755 00000003115 00000000000 0006465 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Container for all DBAL events. * * This class cannot be instantiated. * * @deprecated */ final class Events { /** * Private constructor. This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } /** @deprecated */ public const postConnect = 'postConnect'; /** @deprecated */ public const onSchemaCreateTable = 'onSchemaCreateTable'; /** @deprecated */ public const onSchemaCreateTableColumn = 'onSchemaCreateTableColumn'; /** @deprecated */ public const onSchemaDropTable = 'onSchemaDropTable'; /** @deprecated */ public const onSchemaAlterTable = 'onSchemaAlterTable'; /** @deprecated */ public const onSchemaAlterTableAddColumn = 'onSchemaAlterTableAddColumn'; /** @deprecated */ public const onSchemaAlterTableRemoveColumn = 'onSchemaAlterTableRemoveColumn'; /** @deprecated */ public const onSchemaAlterTableChangeColumn = 'onSchemaAlterTableChangeColumn'; /** @deprecated */ public const onSchemaAlterTableRenameColumn = 'onSchemaAlterTableRenameColumn'; /** @deprecated */ public const onSchemaColumnDefinition = 'onSchemaColumnDefinition'; /** @deprecated */ public const onSchemaIndexDefinition = 'onSchemaIndexDefinition'; /** @deprecated */ public const onTransactionBegin = 'onTransactionBegin'; /** @deprecated */ public const onTransactionCommit = 'onTransactionCommit'; /** @deprecated */ public const onTransactionRollBack = 'onTransactionRollBack'; } Result.php 0000755 00000021122 00000000000 0006475 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL; use Doctrine\DBAL\Driver\Exception as DriverException; use Doctrine\DBAL\Driver\Result as DriverResult; use Doctrine\DBAL\Exception\NoKeyValue; use Doctrine\Deprecations\Deprecation; use LogicException; use Traversable; use function array_shift; use function func_num_args; class Result { private DriverResult $result; private Connection $connection; /** @internal The result can be only instantiated by {@see Connection} or {@see Statement}. */ public function __construct(DriverResult $result, Connection $connection) { $this->result = $result; $this->connection = $connection; } /** * Returns the next row of the result as a numeric array or FALSE if there are no more rows. * * @return list<mixed>|false * * @throws Exception */ public function fetchNumeric() { try { return $this->result->fetchNumeric(); } catch (DriverException $e) { throw $this->connection->convertException($e); } } /** * Returns the next row of the result as an associative array or FALSE if there are no more rows. * * @return array<string,mixed>|false * * @throws Exception */ public function fetchAssociative() { try { return $this->result->fetchAssociative(); } catch (DriverException $e) { throw $this->connection->convertException($e); } } /** * Returns the first value of the next row of the result or FALSE if there are no more rows. * * @return mixed|false * * @throws Exception */ public function fetchOne() { try { return $this->result->fetchOne(); } catch (DriverException $e) { throw $this->connection->convertException($e); } } /** * Returns an array containing all of the result rows represented as numeric arrays. * * @return list<list<mixed>> * * @throws Exception */ public function fetchAllNumeric(): array { try { return $this->result->fetchAllNumeric(); } catch (DriverException $e) { throw $this->connection->convertException($e); } } /** * Returns an array containing all of the result rows represented as associative arrays. * * @return list<array<string,mixed>> * * @throws Exception */ public function fetchAllAssociative(): array { try { return $this->result->fetchAllAssociative(); } catch (DriverException $e) { throw $this->connection->convertException($e); } } /** * Returns an array containing the values of the first column of the result. * * @return array<mixed,mixed> * * @throws Exception */ public function fetchAllKeyValue(): array { $this->ensureHasKeyValue(); $data = []; foreach ($this->fetchAllNumeric() as [$key, $value]) { $data[$key] = $value; } return $data; } /** * Returns an associative array with the keys mapped to the first column and the values being * an associative array representing the rest of the columns and their values. * * @return array<mixed,array<string,mixed>> * * @throws Exception */ public function fetchAllAssociativeIndexed(): array { $data = []; foreach ($this->fetchAllAssociative() as $row) { $data[array_shift($row)] = $row; } return $data; } /** * @return list<mixed> * * @throws Exception */ public function fetchFirstColumn(): array { try { return $this->result->fetchFirstColumn(); } catch (DriverException $e) { throw $this->connection->convertException($e); } } /** * @return Traversable<int,list<mixed>> * * @throws Exception */ public function iterateNumeric(): Traversable { while (($row = $this->fetchNumeric()) !== false) { yield $row; } } /** * @return Traversable<int,array<string,mixed>> * * @throws Exception */ public function iterateAssociative(): Traversable { while (($row = $this->fetchAssociative()) !== false) { yield $row; } } /** * @return Traversable<mixed, mixed> * * @throws Exception */ public function iterateKeyValue(): Traversable { $this->ensureHasKeyValue(); foreach ($this->iterateNumeric() as [$key, $value]) { yield $key => $value; } } /** * Returns an iterator over the result set with the keys mapped to the first column and the values being * an associative array representing the rest of the columns and their values. * * @return Traversable<mixed,array<string,mixed>> * * @throws Exception */ public function iterateAssociativeIndexed(): Traversable { foreach ($this->iterateAssociative() as $row) { yield array_shift($row) => $row; } } /** * @return Traversable<int,mixed> * * @throws Exception */ public function iterateColumn(): Traversable { while (($value = $this->fetchOne()) !== false) { yield $value; } } /** @throws Exception */ public function rowCount(): int { try { return $this->result->rowCount(); } catch (DriverException $e) { throw $this->connection->convertException($e); } } /** @throws Exception */ public function columnCount(): int { try { return $this->result->columnCount(); } catch (DriverException $e) { throw $this->connection->convertException($e); } } public function free(): void { $this->result->free(); } /** @throws Exception */ private function ensureHasKeyValue(): void { $columnCount = $this->columnCount(); if ($columnCount < 2) { throw NoKeyValue::fromColumnCount($columnCount); } } /** * BC layer for a wide-spread use-case of old DBAL APIs * * @deprecated Use {@see fetchNumeric()}, {@see fetchAssociative()} or {@see fetchOne()} instead. * * @psalm-param FetchMode::* $mode * * @return mixed * * @throws Exception */ public function fetch(int $mode = FetchMode::ASSOCIATIVE) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4007', '%s is deprecated, please use fetchNumeric(), fetchAssociative() or fetchOne() instead.', __METHOD__, ); if (func_num_args() > 1) { throw new LogicException('Only invocations with one argument are still supported by this legacy API.'); } if ($mode === FetchMode::ASSOCIATIVE) { return $this->fetchAssociative(); } if ($mode === FetchMode::NUMERIC) { return $this->fetchNumeric(); } if ($mode === FetchMode::COLUMN) { return $this->fetchOne(); } throw new LogicException('Only fetch modes declared on Doctrine\DBAL\FetchMode are supported by legacy API.'); } /** * BC layer for a wide-spread use-case of old DBAL APIs * * @deprecated Use {@see fetchAllNumeric()}, {@see fetchAllAssociative()} or {@see fetchFirstColumn()} instead. * * @psalm-param FetchMode::* $mode * * @return list<mixed> * * @throws Exception */ public function fetchAll(int $mode = FetchMode::ASSOCIATIVE): array { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4007', '%s is deprecated, please use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.', __METHOD__, ); if (func_num_args() > 1) { throw new LogicException('Only invocations with one argument are still supported by this legacy API.'); } if ($mode === FetchMode::ASSOCIATIVE) { return $this->fetchAllAssociative(); } if ($mode === FetchMode::NUMERIC) { return $this->fetchAllNumeric(); } if ($mode === FetchMode::COLUMN) { return $this->fetchFirstColumn(); } throw new LogicException('Only fetch modes declared on Doctrine\DBAL\FetchMode are supported by legacy API.'); } } Query.php 0000755 00000002247 00000000000 0006333 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL; use Doctrine\DBAL\Types\Type; /** * An SQL query together with its bound parameters. * * @psalm-immutable */ final class Query { /** * The SQL query. */ private string $sql; /** * The parameters bound to the query. * * @var array<mixed> */ private array $params; /** * The types of the parameters bound to the query. * * @var array<Type|int|string|null> */ private array $types; /** * @param array<mixed> $params * @param array<Type|int|string|null> $types * * @psalm-suppress ImpurePropertyAssignment */ public function __construct(string $sql, array $params, array $types) { $this->sql = $sql; $this->params = $params; $this->types = $types; } public function getSQL(): string { return $this->sql; } /** @return array<mixed> */ public function getParams(): array { return $this->params; } /** @return array<Type|int|string|null> */ public function getTypes(): array { return $this->types; } } Logging/Middleware.php 0000755 00000001023 00000000000 0010660 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Logging; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface; use Psr\Log\LoggerInterface; final class Middleware implements MiddlewareInterface { private LoggerInterface $logger; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } public function wrap(DriverInterface $driver): DriverInterface { return new Driver($driver, $this->logger); } } Logging/Connection.php 0000755 00000003437 00000000000 0010715 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Logging; use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Psr\Log\LoggerInterface; final class Connection extends AbstractConnectionMiddleware { private LoggerInterface $logger; /** @internal This connection can be only instantiated by its driver. */ public function __construct(ConnectionInterface $connection, LoggerInterface $logger) { parent::__construct($connection); $this->logger = $logger; } public function __destruct() { $this->logger->info('Disconnecting'); } public function prepare(string $sql): DriverStatement { return new Statement( parent::prepare($sql), $this->logger, $sql, ); } public function query(string $sql): Result { $this->logger->debug('Executing query: {sql}', ['sql' => $sql]); return parent::query($sql); } public function exec(string $sql): int { $this->logger->debug('Executing statement: {sql}', ['sql' => $sql]); return parent::exec($sql); } /** * {@inheritDoc} */ public function beginTransaction() { $this->logger->debug('Beginning transaction'); return parent::beginTransaction(); } /** * {@inheritDoc} */ public function commit() { $this->logger->debug('Committing transaction'); return parent::commit(); } /** * {@inheritDoc} */ public function rollBack() { $this->logger->debug('Rolling back transaction'); return parent::rollBack(); } } Logging/Statement.php 0000755 00000005630 00000000000 0010557 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Logging; use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use Psr\Log\LoggerInterface; use function array_slice; use function func_get_args; use function func_num_args; final class Statement extends AbstractStatementMiddleware { private LoggerInterface $logger; private string $sql; /** @var array<int,mixed>|array<string,mixed> */ private array $params = []; /** @var array<int,int>|array<string,int> */ private array $types = []; /** @internal This statement can be only instantiated by its connection. */ public function __construct(StatementInterface $statement, LoggerInterface $logger, string $sql) { parent::__construct($statement); $this->logger = $logger; $this->sql = $sql; } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } $this->params[$param] = &$variable; $this->types[$param] = $type; return parent::bindParam($param, $variable, $type, ...array_slice(func_get_args(), 3)); } /** * {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING) { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } $this->params[$param] = $value; $this->types[$param] = $type; return parent::bindValue($param, $value, $type); } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { $this->logger->debug('Executing statement: {sql} (parameters: {params}, types: {types})', [ 'sql' => $this->sql, 'params' => $params ?? $this->params, 'types' => $this->types, ]); return parent::execute($params); } } Logging/DebugStack.php 0000755 00000002644 00000000000 0010631 0 ustar 00 <?php namespace Doctrine\DBAL\Logging; use Doctrine\Deprecations\Deprecation; use function microtime; /** * Includes executed SQLs in a Debug Stack. * * @deprecated */ class DebugStack implements SQLLogger { /** * Executed SQL queries. * * @var array<int, array<string, mixed>> */ public $queries = []; /** * If Debug Stack is enabled (log queries) or not. * * @var bool */ public $enabled = true; /** @var float|null */ public $start = null; /** @var int */ public $currentQuery = 0; public function __construct() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4967', 'DebugStack is deprecated.', ); } /** * {@inheritDoc} */ public function startQuery($sql, ?array $params = null, ?array $types = null) { if (! $this->enabled) { return; } $this->start = microtime(true); $this->queries[++$this->currentQuery] = [ 'sql' => $sql, 'params' => $params, 'types' => $types, 'executionMS' => 0, ]; } /** * {@inheritDoc} */ public function stopQuery() { if (! $this->enabled) { return; } $this->queries[$this->currentQuery]['executionMS'] = microtime(true) - $this->start; } } Logging/SQLLogger.php 0000755 00000001665 00000000000 0010416 0 ustar 00 <?php namespace Doctrine\DBAL\Logging; use Doctrine\DBAL\Types\Type; /** * Interface for SQL loggers. * * @deprecated Use {@see \Doctrine\DBAL\Logging\Middleware} or implement * {@see \Doctrine\DBAL\Driver\Middleware} instead. */ interface SQLLogger { /** * Logs a SQL statement somewhere. * * @param string $sql SQL statement * @param list<mixed>|array<string, mixed>|null $params Statement parameters * @param array<int, Type|int|string|null>|array<string, Type|int|string|null>|null $types Parameter types * * @return void */ public function startQuery($sql, ?array $params = null, ?array $types = null); /** * Marks the last started query as stopped. This can be used for timing of queries. * * @return void */ public function stopQuery(); } Logging/LoggerChain.php 0000755 00000001730 00000000000 0010772 0 ustar 00 <?php namespace Doctrine\DBAL\Logging; use Doctrine\Deprecations\Deprecation; /** * Chains multiple SQLLogger. * * @deprecated */ class LoggerChain implements SQLLogger { /** @var iterable<SQLLogger> */ private iterable $loggers; /** @param iterable<SQLLogger> $loggers */ public function __construct(iterable $loggers = []) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4967', 'LoggerChain is deprecated', ); $this->loggers = $loggers; } /** * {@inheritDoc} */ public function startQuery($sql, ?array $params = null, ?array $types = null) { foreach ($this->loggers as $logger) { $logger->startQuery($sql, $params, $types); } } /** * {@inheritDoc} */ public function stopQuery() { foreach ($this->loggers as $logger) { $logger->stopQuery(); } } } Logging/Driver.php 0000755 00000002525 00000000000 0010046 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Logging; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use Psr\Log\LoggerInterface; use SensitiveParameter; final class Driver extends AbstractDriverMiddleware { private LoggerInterface $logger; /** @internal This driver can be only instantiated by its middleware. */ public function __construct(DriverInterface $driver, LoggerInterface $logger) { parent::__construct($driver); $this->logger = $logger; } /** * {@inheritDoc} */ public function connect( #[SensitiveParameter] array $params ) { $this->logger->info('Connecting with parameters {params}', ['params' => $this->maskPassword($params)]); return new Connection( parent::connect($params), $this->logger, ); } /** * @param array<string,mixed> $params Connection parameters * * @return array<string,mixed> */ private function maskPassword( #[SensitiveParameter] array $params ): array { if (isset($params['password'])) { $params['password'] = '<redacted>'; } if (isset($params['url'])) { $params['url'] = '<redacted>'; } return $params; } } Portability/Result.php 0000755 00000003240 00000000000 0011000 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\Driver\Middleware\AbstractResultMiddleware; use Doctrine\DBAL\Driver\Result as ResultInterface; final class Result extends AbstractResultMiddleware { private Converter $converter; /** @internal The result can be only instantiated by the portability connection or statement. */ public function __construct(ResultInterface $result, Converter $converter) { parent::__construct($result); $this->converter = $converter; } /** * {@inheritDoc} */ public function fetchNumeric() { return $this->converter->convertNumeric( parent::fetchNumeric(), ); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->converter->convertAssociative( parent::fetchAssociative(), ); } /** * {@inheritDoc} */ public function fetchOne() { return $this->converter->convertOne( parent::fetchOne(), ); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return $this->converter->convertAllNumeric( parent::fetchAllNumeric(), ); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return $this->converter->convertAllAssociative( parent::fetchAllAssociative(), ); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return $this->converter->convertFirstColumn( parent::fetchFirstColumn(), ); } } Portability/Middleware.php 0000755 00000002207 00000000000 0011601 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\ColumnCase; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface; final class Middleware implements MiddlewareInterface { private int $mode; /** @var 0|ColumnCase::LOWER|ColumnCase::UPPER */ private int $case; /** * @param 0|ColumnCase::LOWER|ColumnCase::UPPER $case Determines how the column case will be treated. * 0: The case will be left as is in the database. * {@see ColumnCase::LOWER}: The case will be lowercased. * {@see ColumnCase::UPPER}: The case will be uppercased. */ public function __construct(int $mode, int $case) { $this->mode = $mode; $this->case = $case; } public function wrap(DriverInterface $driver): DriverInterface { if ($this->mode !== 0) { return new Driver($driver, $this->mode, $this->case); } return $driver; } } Portability/Converter.php 0000755 00000020641 00000000000 0011475 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Portability; use function array_change_key_case; use function array_map; use function array_reduce; use function is_string; use function rtrim; use const CASE_LOWER; use const CASE_UPPER; final class Converter { public const CASE_LOWER = CASE_LOWER; public const CASE_UPPER = CASE_UPPER; /** @var callable */ private $convertNumeric; /** @var callable */ private $convertAssociative; /** @var callable */ private $convertOne; /** @var callable */ private $convertAllNumeric; /** @var callable */ private $convertAllAssociative; /** @var callable */ private $convertFirstColumn; /** * @param bool $convertEmptyStringToNull Whether each empty string should * be converted to NULL * @param bool $rightTrimString Whether each string should right-trimmed * @param self::CASE_LOWER|self::CASE_UPPER|null $case Convert the case of the column names * (one of {@see self::CASE_LOWER} and * {@see self::CASE_UPPER}) */ public function __construct(bool $convertEmptyStringToNull, bool $rightTrimString, ?int $case) { $convertValue = $this->createConvertValue($convertEmptyStringToNull, $rightTrimString); $convertNumeric = $this->createConvertRow($convertValue, null); $convertAssociative = $this->createConvertRow($convertValue, $case); $this->convertNumeric = $this->createConvert($convertNumeric, [self::class, 'id']); $this->convertAssociative = $this->createConvert($convertAssociative, [self::class, 'id']); $this->convertOne = $this->createConvert($convertValue, [self::class, 'id']); $this->convertAllNumeric = $this->createConvertAll($convertNumeric, [self::class, 'id']); $this->convertAllAssociative = $this->createConvertAll($convertAssociative, [self::class, 'id']); $this->convertFirstColumn = $this->createConvertAll($convertValue, [self::class, 'id']); } /** * @param array<int,mixed>|false $row * * @return list<mixed>|false */ public function convertNumeric($row) { return ($this->convertNumeric)($row); } /** * @param array<string,mixed>|false $row * * @return array<string,mixed>|false */ public function convertAssociative($row) { return ($this->convertAssociative)($row); } /** * @param mixed|false $value * * @return mixed|false */ public function convertOne($value) { return ($this->convertOne)($value); } /** * @param list<list<mixed>> $data * * @return list<list<mixed>> */ public function convertAllNumeric(array $data): array { return ($this->convertAllNumeric)($data); } /** * @param list<array<string,mixed>> $data * * @return list<array<string,mixed>> */ public function convertAllAssociative(array $data): array { return ($this->convertAllAssociative)($data); } /** * @param list<mixed> $data * * @return list<mixed> */ public function convertFirstColumn(array $data): array { return ($this->convertFirstColumn)($data); } /** * @param T $value * * @return T * * @template T */ private static function id($value) { return $value; } /** * @param T $value * * @return T|null * * @template T */ private static function convertEmptyStringToNull($value) { if ($value === '') { return null; } return $value; } /** * @param T $value * * @return T|string * @psalm-return (T is string ? string : T) * * @template T */ private static function rightTrimString($value) { if (! is_string($value)) { return $value; } return rtrim($value); } /** * Creates a function that will convert each individual value retrieved from the database * * @param bool $convertEmptyStringToNull Whether each empty string should be converted to NULL * @param bool $rightTrimString Whether each string should right-trimmed * * @return callable|null The resulting function or NULL if no conversion is needed */ private function createConvertValue(bool $convertEmptyStringToNull, bool $rightTrimString): ?callable { $functions = []; if ($convertEmptyStringToNull) { $functions[] = [self::class, 'convertEmptyStringToNull']; } if ($rightTrimString) { $functions[] = [self::class, 'rightTrimString']; } return $this->compose(...$functions); } /** * Creates a function that will convert each array-row retrieved from the database * * @param callable|null $function The function that will convert each value * @param self::CASE_LOWER|self::CASE_UPPER|null $case Column name case * * @return callable|null The resulting function or NULL if no conversion is needed */ private function createConvertRow(?callable $function, ?int $case): ?callable { $functions = []; if ($function !== null) { $functions[] = $this->createMapper($function); } if ($case !== null) { $functions[] = static function (array $row) use ($case): array { return array_change_key_case($row, $case); }; } return $this->compose(...$functions); } /** * Creates a function that will be applied to the return value of Statement::fetch*() * or an identity function if no conversion is needed * * @param callable|null $function The function that will convert each tow * @param callable $id Identity function */ private function createConvert(?callable $function, callable $id): callable { if ($function === null) { return $id; } return /** * @param T $value * * @psalm-return (T is false ? false : T) * * @template T */ static function ($value) use ($function) { if ($value === false) { return false; } return $function($value); }; } /** * Creates a function that will be applied to the return value of Statement::fetchAll*() * or an identity function if no transformation is required * * @param callable|null $function The function that will transform each value * @param callable $id Identity function */ private function createConvertAll(?callable $function, callable $id): callable { if ($function === null) { return $id; } return $this->createMapper($function); } /** * Creates a function that maps each value of the array using the given function * * @param callable $function The function that maps each value of the array */ private function createMapper(callable $function): callable { return static function (array $array) use ($function): array { return array_map($function, $array); }; } /** * Creates a composition of the given set of functions * * @param callable(T):T ...$functions The functions to compose * * @return callable(T):T|null * * @template T */ private function compose(callable ...$functions): ?callable { return array_reduce($functions, static function (?callable $carry, callable $item): callable { if ($carry === null) { return $item; } return /** * @param T $value * * @return T * * @template T */ static function ($value) use ($carry, $item) { return $item($carry($value)); }; }); } } Portability/Connection.php 0000755 00000002250 00000000000 0011621 0 ustar 00 <?php namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; use Doctrine\DBAL\Driver\Result as DriverResult; use Doctrine\DBAL\Driver\Statement as DriverStatement; /** * Portability wrapper for a Connection. */ final class Connection extends AbstractConnectionMiddleware { public const PORTABILITY_ALL = 255; public const PORTABILITY_NONE = 0; public const PORTABILITY_RTRIM = 1; public const PORTABILITY_EMPTY_TO_NULL = 4; public const PORTABILITY_FIX_CASE = 8; private Converter $converter; public function __construct(ConnectionInterface $connection, Converter $converter) { parent::__construct($connection); $this->converter = $converter; } public function prepare(string $sql): DriverStatement { return new Statement( parent::prepare($sql), $this->converter, ); } public function query(string $sql): DriverResult { return new Result( parent::query($sql), $this->converter, ); } } Portability/Statement.php 0000755 00000001473 00000000000 0011474 0 ustar 00 <?php namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as DriverStatement; /** * Portability wrapper for a Statement. */ final class Statement extends AbstractStatementMiddleware { private Converter $converter; /** * Wraps <tt>Statement</tt> and applies portability measures. */ public function __construct(DriverStatement $stmt, Converter $converter) { parent::__construct($stmt); $this->converter = $converter; } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { return new Result( parent::execute($params), $this->converter, ); } } Portability/OptimizeFlags.php 0000755 00000002265 00000000000 0012305 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServerPlatform; final class OptimizeFlags { /** * Platform-specific portability flags that need to be excluded from the user-provided mode * since the platform already operates in this mode to avoid unnecessary conversion overhead. * * @var array<class-string, int> */ private static array $platforms = [ DB2Platform::class => 0, OraclePlatform::class => Connection::PORTABILITY_EMPTY_TO_NULL, PostgreSQLPlatform::class => 0, SqlitePlatform::class => 0, SQLServerPlatform::class => 0, ]; public function __invoke(AbstractPlatform $platform, int $flags): int { foreach (self::$platforms as $class => $mask) { if ($platform instanceof $class) { $flags &= ~$mask; break; } } return $flags; } } Portability/Driver.php 0000755 00000005213 00000000000 0010757 0 ustar 00 <?php namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\ColumnCase; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use LogicException; use PDO; use SensitiveParameter; use function method_exists; final class Driver extends AbstractDriverMiddleware { private int $mode; /** @var 0|ColumnCase::LOWER|ColumnCase::UPPER */ private int $case; /** * @param 0|ColumnCase::LOWER|ColumnCase::UPPER $case Determines how the column case will be treated. * 0: The case will be left as is in the database. * {@see ColumnCase::LOWER}: The case will be lowercased. * {@see ColumnCase::UPPER}: The case will be uppercased. */ public function __construct(DriverInterface $driver, int $mode, int $case) { parent::__construct($driver); $this->mode = $mode; $this->case = $case; } /** * {@inheritDoc} */ public function connect( #[SensitiveParameter] array $params ) { $connection = parent::connect($params); $portability = (new OptimizeFlags())( $this->getDatabasePlatform(), $this->mode, ); $case = null; if ($this->case !== 0 && ($portability & Connection::PORTABILITY_FIX_CASE) !== 0) { $nativeConnection = null; if (method_exists($connection, 'getNativeConnection')) { try { $nativeConnection = $connection->getNativeConnection(); } catch (LogicException $e) { } } if ($nativeConnection instanceof PDO) { $portability &= ~Connection::PORTABILITY_FIX_CASE; $nativeConnection->setAttribute( PDO::ATTR_CASE, $this->case === ColumnCase::LOWER ? PDO::CASE_LOWER : PDO::CASE_UPPER, ); } else { $case = $this->case === ColumnCase::LOWER ? Converter::CASE_LOWER : Converter::CASE_UPPER; } } $convertEmptyStringToNull = ($portability & Connection::PORTABILITY_EMPTY_TO_NULL) !== 0; $rightTrimString = ($portability & Connection::PORTABILITY_RTRIM) !== 0; if (! $convertEmptyStringToNull && ! $rightTrimString && $case === null) { return $connection; } return new Connection( $connection, new Converter($convertEmptyStringToNull, $rightTrimString, $case), ); } } Query/SelectQuery.php 0000755 00000004106 00000000000 0010574 0 ustar 00 <?php namespace Doctrine\DBAL\Query; final class SelectQuery { private bool $distinct; /** @var string[] */ private array $columns; /** @var string[] */ private array $from; private ?string $where; /** @var string[] */ private array $groupBy; private ?string $having; /** @var string[] */ private array $orderBy; private Limit $limit; private ?ForUpdate $forUpdate; /** * @internal This class should be instantiated only by {@link QueryBuilder}. * * @param string[] $columns * @param string[] $from * @param string[] $groupBy * @param string[] $orderBy */ public function __construct( bool $distinct, array $columns, array $from, ?string $where, array $groupBy, ?string $having, array $orderBy, Limit $limit, ?ForUpdate $forUpdate ) { $this->distinct = $distinct; $this->columns = $columns; $this->from = $from; $this->where = $where; $this->groupBy = $groupBy; $this->having = $having; $this->orderBy = $orderBy; $this->limit = $limit; $this->forUpdate = $forUpdate; } public function isDistinct(): bool { return $this->distinct; } /** @return string[] */ public function getColumns(): array { return $this->columns; } /** @return string[] */ public function getFrom(): array { return $this->from; } public function getWhere(): ?string { return $this->where; } /** @return string[] */ public function getGroupBy(): array { return $this->groupBy; } public function getHaving(): ?string { return $this->having; } /** @return string[] */ public function getOrderBy(): array { return $this->orderBy; } public function getLimit(): Limit { return $this->limit; } public function getForUpdate(): ?ForUpdate { return $this->forUpdate; } } Query/QueryBuilder.php 0000755 00000146212 00000000000 0010750 0 ustar 00 <?php namespace Doctrine\DBAL\Query; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\Expression\CompositeExpression; use Doctrine\DBAL\Query\Expression\ExpressionBuilder; use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode; use Doctrine\DBAL\Result; use Doctrine\DBAL\Statement; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_key_exists; use function array_keys; use function array_unshift; use function count; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function is_array; use function is_object; use function key; use function method_exists; use function strtoupper; use function substr; use function ucfirst; /** * QueryBuilder class is responsible to dynamically create SQL queries. * * Important: Verify that every feature you use will work with your database vendor. * SQL Query Builder does not attempt to validate the generated SQL at all. * * The query builder does no validation whatsoever if certain features even work with the * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements * even if some vendors such as MySQL support it. * * @method $this distinct(bool $distinct = true) Adds or removes DISTINCT to/from the query. */ class QueryBuilder { /** @deprecated */ public const SELECT = 0; /** @deprecated */ public const DELETE = 1; /** @deprecated */ public const UPDATE = 2; /** @deprecated */ public const INSERT = 3; /** @deprecated */ public const STATE_DIRTY = 0; /** @deprecated */ public const STATE_CLEAN = 1; /** * The DBAL Connection. */ private Connection $connection; /* * The default values of SQL parts collection */ private const SQL_PARTS_DEFAULTS = [ 'select' => [], 'distinct' => false, 'from' => [], 'join' => [], 'set' => [], 'where' => null, 'groupBy' => [], 'having' => null, 'orderBy' => [], 'values' => [], 'for_update' => null, ]; /** * The array of SQL parts collected. * * @var mixed[] */ private array $sqlParts = self::SQL_PARTS_DEFAULTS; /** * The complete SQL string for this query. */ private ?string $sql = null; /** * The query parameters. * * @var list<mixed>|array<string, mixed> */ private $params = []; /** * The parameter type map of this query. * * @var array<int, int|string|Type|null>|array<string, int|string|Type|null> */ private array $paramTypes = []; /** * The type of query this is. Can be select, update or delete. * * @psalm-var self::SELECT|self::DELETE|self::UPDATE|self::INSERT */ private int $type = self::SELECT; /** * The state of the query object. Can be dirty or clean. * * @psalm-var self::STATE_* */ private int $state = self::STATE_CLEAN; /** * The index of the first result to retrieve. */ private int $firstResult = 0; /** * The maximum number of results to retrieve or NULL to retrieve all results. */ private ?int $maxResults = null; /** * The counter of bound parameters used with {@see bindValue). */ private int $boundCounter = 0; /** * The query cache profile used for caching results. */ private ?QueryCacheProfile $resultCacheProfile = null; /** * Initializes a new <tt>QueryBuilder</tt>. * * @param Connection $connection The DBAL Connection. */ public function __construct(Connection $connection) { $this->connection = $connection; } /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. * This producer method is intended for convenient inline usage. Example: * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where($qb->expr()->eq('u.id', 1)); * </code> * * For more complex expression construction, consider storing the expression * builder object in a local variable. * * @return ExpressionBuilder */ public function expr() { return $this->connection->getExpressionBuilder(); } /** * Gets the type of the currently built query. * * @deprecated If necessary, track the type of the query being built outside of the builder. * * @return int */ public function getType() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5551', 'Relying on the type of the query being built is deprecated.' . ' If necessary, track the type of the query being built outside of the builder.', ); return $this->type; } /** * Gets the associated DBAL Connection for this query builder. * * @deprecated Use the connection used to instantiate the builder instead. * * @return Connection */ public function getConnection() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5780', '%s is deprecated. Use the connection used to instantiate the builder instead.', __METHOD__, ); return $this->connection; } /** * Gets the state of this query builder instance. * * @deprecated The builder state is an internal concern. * * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. * @psalm-return self::STATE_* */ public function getState() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5551', 'Relying on the query builder state is deprecated as it is an internal concern.', ); return $this->state; } /** * Prepares and executes an SQL query and returns the first row of the result * as an associative array. * * @return array<string, mixed>|false False is returned if no rows are found. * * @throws Exception */ public function fetchAssociative() { return $this->executeQuery()->fetchAssociative(); } /** * Prepares and executes an SQL query and returns the first row of the result * as a numerically indexed array. * * @return array<int, mixed>|false False is returned if no rows are found. * * @throws Exception */ public function fetchNumeric() { return $this->executeQuery()->fetchNumeric(); } /** * Prepares and executes an SQL query and returns the value of a single column * of the first row of the result. * * @return mixed|false False is returned if no rows are found. * * @throws Exception */ public function fetchOne() { return $this->executeQuery()->fetchOne(); } /** * Prepares and executes an SQL query and returns the result as an array of numeric arrays. * * @return array<int,array<int,mixed>> * * @throws Exception */ public function fetchAllNumeric(): array { return $this->executeQuery()->fetchAllNumeric(); } /** * Prepares and executes an SQL query and returns the result as an array of associative arrays. * * @return array<int,array<string,mixed>> * * @throws Exception */ public function fetchAllAssociative(): array { return $this->executeQuery()->fetchAllAssociative(); } /** * Prepares and executes an SQL query and returns the result as an associative array with the keys * mapped to the first column and the values mapped to the second column. * * @return array<mixed,mixed> * * @throws Exception */ public function fetchAllKeyValue(): array { return $this->executeQuery()->fetchAllKeyValue(); } /** * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped * to the first column and the values being an associative array representing the rest of the columns * and their values. * * @return array<mixed,array<string,mixed>> * * @throws Exception */ public function fetchAllAssociativeIndexed(): array { return $this->executeQuery()->fetchAllAssociativeIndexed(); } /** * Prepares and executes an SQL query and returns the result as an array of the first column values. * * @return array<int,mixed> * * @throws Exception */ public function fetchFirstColumn(): array { return $this->executeQuery()->fetchFirstColumn(); } /** * Executes an SQL query (SELECT) and returns a Result. * * @throws Exception */ public function executeQuery(): Result { return $this->connection->executeQuery( $this->getSQL(), $this->params, $this->paramTypes, $this->resultCacheProfile, ); } /** * Executes an SQL statement and returns the number of affected rows. * * Should be used for INSERT, UPDATE and DELETE * * @return int The number of affected rows. * * @throws Exception */ public function executeStatement(): int { return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes); } /** * Executes this query using the bound parameters and their types. * * @deprecated Use {@see executeQuery()} or {@see executeStatement()} instead. * * @return Result|int|string * * @throws Exception */ public function execute() { if ($this->type === self::SELECT) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4578', 'QueryBuilder::execute() is deprecated, use QueryBuilder::executeQuery() for SQL queries instead.', ); return $this->executeQuery(); } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4578', 'QueryBuilder::execute() is deprecated, use QueryBuilder::executeStatement() for SQL statements instead.', ); return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes); } /** * Gets the complete SQL string formed by the current specifications of this QueryBuilder. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * echo $qb->getSQL(); // SELECT u FROM User u * </code> * * @return string The SQL query string. */ public function getSQL() { if ($this->sql !== null && $this->state === self::STATE_CLEAN) { return $this->sql; } switch ($this->type) { case self::INSERT: $sql = $this->getSQLForInsert(); break; case self::DELETE: $sql = $this->getSQLForDelete(); break; case self::UPDATE: $sql = $this->getSQLForUpdate(); break; case self::SELECT: $sql = $this->getSQLForSelect(); break; } $this->state = self::STATE_CLEAN; $this->sql = $sql; return $sql; } /** * Sets a query parameter for the query being constructed. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where('u.id = :user_id') * ->setParameter('user_id', 1); * </code> * * @param int|string $key Parameter position or name * @param mixed $value Parameter value * @param int|string|Type|null $type Parameter type * * @return $this This QueryBuilder instance. */ public function setParameter($key, $value, $type = ParameterType::STRING) { if ($type !== null) { $this->paramTypes[$key] = $type; } else { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5550', 'Using NULL as prepared statement parameter type is deprecated.' . 'Omit or use ParameterType::STRING instead', ); } $this->params[$key] = $value; return $this; } /** * Sets a collection of query parameters for the query being constructed. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where('u.id = :user_id1 OR u.id = :user_id2') * ->setParameters(array( * 'user_id1' => 1, * 'user_id2' => 2 * )); * </code> * * @param list<mixed>|array<string, mixed> $params Parameters to set * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return $this This QueryBuilder instance. */ public function setParameters(array $params, array $types = []) { $this->paramTypes = $types; $this->params = $params; return $this; } /** * Gets all defined query parameters for the query being constructed indexed by parameter index or name. * * @return list<mixed>|array<string, mixed> The currently defined query parameters */ public function getParameters() { return $this->params; } /** * Gets a (previously set) query parameter of the query being constructed. * * @param mixed $key The key (index or name) of the bound parameter. * * @return mixed The value of the bound parameter. */ public function getParameter($key) { return $this->params[$key] ?? null; } /** * Gets all defined query parameter types for the query being constructed indexed by parameter index or name. * * @return array<int, int|string|Type|null>|array<string, int|string|Type|null> The currently defined * query parameter types */ public function getParameterTypes() { return $this->paramTypes; } /** * Gets a (previously set) query parameter type of the query being constructed. * * @param int|string $key The key of the bound parameter type * * @return int|string|Type The value of the bound parameter type */ public function getParameterType($key) { return $this->paramTypes[$key] ?? ParameterType::STRING; } /** * Sets the position of the first result to retrieve (the "offset"). * * @param int $firstResult The first result to return. * * @return $this This QueryBuilder instance. */ public function setFirstResult($firstResult) { $this->state = self::STATE_DIRTY; $this->firstResult = $firstResult; return $this; } /** * Gets the position of the first result the query object was set to retrieve (the "offset"). * * @return int The position of the first result. */ public function getFirstResult() { return $this->firstResult; } /** * Sets the maximum number of results to retrieve (the "limit"). * * @param int|null $maxResults The maximum number of results to retrieve or NULL to retrieve all results. * * @return $this This QueryBuilder instance. */ public function setMaxResults($maxResults) { $this->state = self::STATE_DIRTY; $this->maxResults = $maxResults; return $this; } /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if all results will be returned. * * @return int|null The maximum number of results. */ public function getMaxResults() { return $this->maxResults; } /** * Locks the queried rows for a subsequent update. * * @return $this */ public function forUpdate(int $conflictResolutionMode = ConflictResolutionMode::ORDINARY): self { $this->state = self::STATE_DIRTY; $this->sqlParts['for_update'] = new ForUpdate($conflictResolutionMode); return $this; } /** * Either appends to or replaces a single, generic query part. * * The available parts are: 'select', 'from', 'set', 'where', * 'groupBy', 'having' and 'orderBy'. * * @param string $sqlPartName * @param mixed $sqlPart * @param bool $append * * @return $this This QueryBuilder instance. */ public function add($sqlPartName, $sqlPart, $append = false) { $isArray = is_array($sqlPart); $isMultiple = is_array($this->sqlParts[$sqlPartName]); if ($isMultiple && ! $isArray) { $sqlPart = [$sqlPart]; } $this->state = self::STATE_DIRTY; if ($append) { if ( $sqlPartName === 'orderBy' || $sqlPartName === 'groupBy' || $sqlPartName === 'select' || $sqlPartName === 'set' ) { foreach ($sqlPart as $part) { $this->sqlParts[$sqlPartName][] = $part; } } elseif ($isArray && is_array($sqlPart[key($sqlPart)])) { $key = key($sqlPart); $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key]; } elseif ($isMultiple) { $this->sqlParts[$sqlPartName][] = $sqlPart; } else { $this->sqlParts[$sqlPartName] = $sqlPart; } return $this; } $this->sqlParts[$sqlPartName] = $sqlPart; return $this; } /** * Specifies an item that is to be returned in the query result. * Replaces any previously specified selections, if any. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id', 'p.id') * ->from('users', 'u') * ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id'); * </code> * * @param string|string[]|null $select The selection expression. USING AN ARRAY OR NULL IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function select($select = null/*, string ...$selects*/) { $this->type = self::SELECT; if ($select === null) { return $this; } if (is_array($select)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::select() is deprecated, ' . 'pass each value as an individual variadic argument instead.', ); } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', $selects); } /** * Adds or removes DISTINCT to/from the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id') * ->distinct() * ->from('users', 'u') * </code> * * @return $this This QueryBuilder instance. */ public function distinct(/* bool $distinct = true */): self { $this->sqlParts['distinct'] = func_num_args() < 1 || func_get_arg(0); $this->state = self::STATE_DIRTY; return $this; } /** * Adds an item that is to be returned in the query result. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id') * ->addSelect('p.id') * ->from('users', 'u') * ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id'); * </code> * * @param string|string[]|null $select The selection expression. USING AN ARRAY OR NULL IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function addSelect($select = null/*, string ...$selects*/) { $this->type = self::SELECT; if ($select === null) { return $this; } if (is_array($select)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::addSelect() is deprecated, ' . 'pass each value as an individual variadic argument instead.', ); } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', $selects, true); } /** * Turns the query being built into a bulk delete query that ranges over * a certain table. * * <code> * $qb = $conn->createQueryBuilder() * ->delete('users', 'u') * ->where('u.id = :user_id') * ->setParameter(':user_id', 1); * </code> * * @param string $delete The table whose rows are subject to the deletion. * @param string $alias The table alias used in the constructed query. * * @return $this This QueryBuilder instance. */ public function delete($delete = null, $alias = null) { $this->type = self::DELETE; if ($delete === null) { return $this; } return $this->add('from', [ 'table' => $delete, 'alias' => $alias, ]); } /** * Turns the query being built into a bulk update query that ranges over * a certain table * * <code> * $qb = $conn->createQueryBuilder() * ->update('counters', 'c') * ->set('c.value', 'c.value + 1') * ->where('c.id = ?'); * </code> * * @param string $update The table whose rows are subject to the update. * @param string $alias The table alias used in the constructed query. * * @return $this This QueryBuilder instance. */ public function update($update = null, $alias = null) { $this->type = self::UPDATE; if ($update === null) { return $this; } return $this->add('from', [ 'table' => $update, 'alias' => $alias, ]); } /** * Turns the query being built into an insert query that inserts into * a certain table * * <code> * $qb = $conn->createQueryBuilder() * ->insert('users') * ->values( * array( * 'name' => '?', * 'password' => '?' * ) * ); * </code> * * @param string $insert The table into which the rows should be inserted. * * @return $this This QueryBuilder instance. */ public function insert($insert = null) { $this->type = self::INSERT; if ($insert === null) { return $this; } return $this->add('from', ['table' => $insert]); } /** * Creates and adds a query root corresponding to the table identified by the * given alias, forming a cartesian product with any existing query roots. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id') * ->from('users', 'u') * </code> * * @param string $from The table. * @param string|null $alias The alias of the table. * * @return $this This QueryBuilder instance. */ public function from($from, $alias = null) { return $this->add('from', [ 'table' => $from, 'alias' => $alias, ], true); } /** * Creates and adds a join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function join($fromAlias, $join, $alias, $condition = null) { return $this->innerJoin($fromAlias, $join, $alias, $condition); } /** * Creates and adds a join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function innerJoin($fromAlias, $join, $alias, $condition = null) { return $this->add('join', [ $fromAlias => [ 'joinType' => 'inner', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition, ], ], true); } /** * Creates and adds a left join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function leftJoin($fromAlias, $join, $alias, $condition = null) { return $this->add('join', [ $fromAlias => [ 'joinType' => 'left', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition, ], ], true); } /** * Creates and adds a right join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function rightJoin($fromAlias, $join, $alias, $condition = null) { return $this->add('join', [ $fromAlias => [ 'joinType' => 'right', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition, ], ], true); } /** * Sets a new value for a column in a bulk update query. * * <code> * $qb = $conn->createQueryBuilder() * ->update('counters', 'c') * ->set('c.value', 'c.value + 1') * ->where('c.id = ?'); * </code> * * @param string $key The column to set. * @param string $value The value, expression, placeholder, etc. * * @return $this This QueryBuilder instance. */ public function set($key, $value) { return $this->add('set', $key . ' = ' . $value, true); } /** * Specifies one or more restrictions to the query result. * Replaces any previously specified restrictions, if any. * * <code> * $qb = $conn->createQueryBuilder() * ->select('c.value') * ->from('counters', 'c') * ->where('c.id = ?'); * * // You can optionally programmatically build and/or expressions * $qb = $conn->createQueryBuilder(); * * $or = $qb->expr()->orx(); * $or->add($qb->expr()->eq('c.id', 1)); * $or->add($qb->expr()->eq('c.id', 2)); * * $qb->update('counters', 'c') * ->set('c.value', 'c.value + 1') * ->where($or); * </code> * * @param mixed $predicates The restriction predicates. * * @return $this This QueryBuilder instance. */ public function where($predicates) { if (! (func_num_args() === 1 && $predicates instanceof CompositeExpression)) { $predicates = CompositeExpression::and(...func_get_args()); } return $this->add('where', $predicates); } /** * Adds one or more restrictions to the query results, forming a logical * conjunction with any previously specified restrictions. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where('u.username LIKE ?') * ->andWhere('u.is_active = 1'); * </code> * * @see where() * * @param mixed $where The query restrictions. * * @return $this This QueryBuilder instance. */ public function andWhere($where) { $args = func_get_args(); $where = $this->getQueryPart('where'); if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) { $where = $where->with(...$args); } else { array_unshift($args, $where); $where = CompositeExpression::and(...$args); } return $this->add('where', $where, true); } /** * Adds one or more restrictions to the query results, forming a logical * disjunction with any previously specified restrictions. * * <code> * $qb = $em->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->where('u.id = 1') * ->orWhere('u.id = 2'); * </code> * * @see where() * * @param mixed $where The WHERE statement. * * @return $this This QueryBuilder instance. */ public function orWhere($where) { $args = func_get_args(); $where = $this->getQueryPart('where'); if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) { $where = $where->with(...$args); } else { array_unshift($args, $where); $where = CompositeExpression::or(...$args); } return $this->add('where', $where, true); } /** * Specifies a grouping over the results of the query. * Replaces any previously specified groupings, if any. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->groupBy('u.id'); * </code> * * @param string|string[] $groupBy The grouping expression. USING AN ARRAY IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function groupBy($groupBy/*, string ...$groupBys*/) { if (is_array($groupBy) && count($groupBy) === 0) { return $this; } if (is_array($groupBy)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::groupBy() is deprecated, ' . 'pass each value as an individual variadic argument instead.', ); } $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); return $this->add('groupBy', $groupBy, false); } /** * Adds a grouping expression to the query. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->groupBy('u.lastLogin') * ->addGroupBy('u.createdAt'); * </code> * * @param string|string[] $groupBy The grouping expression. USING AN ARRAY IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function addGroupBy($groupBy/*, string ...$groupBys*/) { if (is_array($groupBy) && count($groupBy) === 0) { return $this; } if (is_array($groupBy)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::addGroupBy() is deprecated, ' . 'pass each value as an individual variadic argument instead.', ); } $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); return $this->add('groupBy', $groupBy, true); } /** * Sets a value for a column in an insert query. * * <code> * $qb = $conn->createQueryBuilder() * ->insert('users') * ->values( * array( * 'name' => '?' * ) * ) * ->setValue('password', '?'); * </code> * * @param string $column The column into which the value should be inserted. * @param string $value The value that should be inserted into the column. * * @return $this This QueryBuilder instance. */ public function setValue($column, $value) { $this->sqlParts['values'][$column] = $value; return $this; } /** * Specifies values for an insert query indexed by column names. * Replaces any previous values, if any. * * <code> * $qb = $conn->createQueryBuilder() * ->insert('users') * ->values( * array( * 'name' => '?', * 'password' => '?' * ) * ); * </code> * * @param mixed[] $values The values to specify for the insert query indexed by column names. * * @return $this This QueryBuilder instance. */ public function values(array $values) { return $this->add('values', $values); } /** * Specifies a restriction over the groups of the query. * Replaces any previous having restrictions, if any. * * @param mixed $having The restriction over the groups. * * @return $this This QueryBuilder instance. */ public function having($having) { if (! (func_num_args() === 1 && $having instanceof CompositeExpression)) { $having = CompositeExpression::and(...func_get_args()); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * conjunction with any existing having restrictions. * * @param mixed $having The restriction to append. * * @return $this This QueryBuilder instance. */ public function andHaving($having) { $args = func_get_args(); $having = $this->getQueryPart('having'); if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) { $having = $having->with(...$args); } else { array_unshift($args, $having); $having = CompositeExpression::and(...$args); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * disjunction with any existing having restrictions. * * @param mixed $having The restriction to add. * * @return $this This QueryBuilder instance. */ public function orHaving($having) { $args = func_get_args(); $having = $this->getQueryPart('having'); if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) { $having = $having->with(...$args); } else { array_unshift($args, $having); $having = CompositeExpression::or(...$args); } return $this->add('having', $having); } /** * Specifies an ordering for the query results. * Replaces any previously specified orderings, if any. * * @param string $sort The ordering expression. * @param string $order The ordering direction. * * @return $this This QueryBuilder instance. */ public function orderBy($sort, $order = null) { return $this->add('orderBy', $sort . ' ' . ($order ?? 'ASC'), false); } /** * Adds an ordering to the query results. * * @param string $sort The ordering expression. * @param string $order The ordering direction. * * @return $this This QueryBuilder instance. */ public function addOrderBy($sort, $order = null) { return $this->add('orderBy', $sort . ' ' . ($order ?? 'ASC'), true); } /** * Gets a query part by its name. * * @deprecated The query parts are implementation details and should not be relied upon. * * @param string $queryPartName * * @return mixed */ public function getQueryPart($queryPartName) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6179', 'Getting query parts is deprecated as they are implementation details.', ); return $this->sqlParts[$queryPartName]; } /** * Gets all query parts. * * @deprecated The query parts are implementation details and should not be relied upon. * * @return mixed[] */ public function getQueryParts() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6179', 'Getting query parts is deprecated as they are implementation details.', ); return $this->sqlParts; } /** * Resets SQL parts. * * @deprecated Use the dedicated reset*() methods instead. * * @param string[]|null $queryPartNames * * @return $this This QueryBuilder instance. */ public function resetQueryParts($queryPartNames = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6193', '%s() is deprecated, instead use dedicated reset methods for the parts that shall be reset.', __METHOD__, ); $queryPartNames ??= array_keys($this->sqlParts); foreach ($queryPartNames as $queryPartName) { $this->sqlParts[$queryPartName] = self::SQL_PARTS_DEFAULTS[$queryPartName]; } $this->state = self::STATE_DIRTY; return $this; } /** * Resets a single SQL part. * * @deprecated Use the dedicated reset*() methods instead. * * @param string $queryPartName * * @return $this This QueryBuilder instance. */ public function resetQueryPart($queryPartName) { if ($queryPartName === 'distinct') { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6193', 'Calling %s() with "distinct" is deprecated, call distinct(false) instead.', __METHOD__, ); return $this->distinct(false); } $newMethodName = 'reset' . ucfirst($queryPartName); if (array_key_exists($queryPartName, self::SQL_PARTS_DEFAULTS) && method_exists($this, $newMethodName)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6193', 'Calling %s() with "%s" is deprecated, call %s() instead.', __METHOD__, $queryPartName, $newMethodName, ); return $this->$newMethodName(); } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6193', 'Calling %s() with "%s" is deprecated without replacement.', __METHOD__, $queryPartName, $newMethodName, ); $this->sqlParts[$queryPartName] = self::SQL_PARTS_DEFAULTS[$queryPartName]; $this->state = self::STATE_DIRTY; return $this; } /** * Resets the WHERE conditions for the query. * * @return $this This QueryBuilder instance. */ public function resetWhere(): self { $this->sqlParts['where'] = self::SQL_PARTS_DEFAULTS['where']; $this->state = self::STATE_DIRTY; return $this; } /** * Resets the grouping for the query. * * @return $this This QueryBuilder instance. */ public function resetGroupBy(): self { $this->sqlParts['groupBy'] = self::SQL_PARTS_DEFAULTS['groupBy']; $this->state = self::STATE_DIRTY; return $this; } /** * Resets the HAVING conditions for the query. * * @return $this This QueryBuilder instance. */ public function resetHaving(): self { $this->sqlParts['having'] = self::SQL_PARTS_DEFAULTS['having']; $this->state = self::STATE_DIRTY; return $this; } /** * Resets the ordering for the query. * * @return $this This QueryBuilder instance. */ public function resetOrderBy(): self { $this->sqlParts['orderBy'] = self::SQL_PARTS_DEFAULTS['orderBy']; $this->state = self::STATE_DIRTY; return $this; } /** @throws Exception */ private function getSQLForSelect(): string { return $this->connection->getDatabasePlatform() ->createSelectSQLBuilder() ->buildSQL( new SelectQuery( $this->sqlParts['distinct'], $this->sqlParts['select'], $this->getFromClauses(), $this->sqlParts['where'], $this->sqlParts['groupBy'], $this->sqlParts['having'], $this->sqlParts['orderBy'], new Limit($this->maxResults, $this->firstResult), $this->sqlParts['for_update'], ), ); } /** * @return string[] * * @throws QueryException */ private function getFromClauses(): array { $fromClauses = []; $knownAliases = []; // Loop through all FROM clauses foreach ($this->sqlParts['from'] as $from) { if ($from['alias'] === null) { $tableSql = $from['table']; $tableReference = $from['table']; } else { $tableSql = $from['table'] . ' ' . $from['alias']; $tableReference = $from['alias']; } $knownAliases[$tableReference] = true; $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases); } $this->verifyAllAliasesAreKnown($knownAliases); return $fromClauses; } /** * @param array<string,true> $knownAliases * * @throws QueryException */ private function verifyAllAliasesAreKnown(array $knownAliases): void { foreach ($this->sqlParts['join'] as $fromAlias => $joins) { if (! isset($knownAliases[$fromAlias])) { throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases)); } } } /** * Converts this instance into an INSERT string in SQL. */ private function getSQLForInsert(): string { return 'INSERT INTO ' . $this->sqlParts['from']['table'] . ' (' . implode(', ', array_keys($this->sqlParts['values'])) . ')' . ' VALUES(' . implode(', ', $this->sqlParts['values']) . ')'; } /** * Converts this instance into an UPDATE string in SQL. */ private function getSQLForUpdate(): string { $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); return 'UPDATE ' . $table . ' SET ' . implode(', ', $this->sqlParts['set']) . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); } /** * Converts this instance into a DELETE string in SQL. */ private function getSQLForDelete(): string { $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); return 'DELETE FROM ' . $table . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); } /** * Gets a string representation of this QueryBuilder which corresponds to * the final SQL query being constructed. * * @return string The string representation of this QueryBuilder. */ public function __toString() { return $this->getSQL(); } /** * Creates a new named parameter and bind the value $value to it. * * This method provides a shortcut for {@see Statement::bindValue()} * when using prepared statements. * * The parameter $value specifies the value that you want to bind. If * $placeholder is not provided createNamedParameter() will automatically * create a placeholder for you. An automatic placeholder will be of the * name ':dcValue1', ':dcValue2' etc. * * Example: * <code> * $value = 2; * $q->eq( 'id', $q->createNamedParameter( $value ) ); * $stmt = $q->executeQuery(); // executed with 'id = 2' * </code> * * @link http://www.zetacomponents.org * * @param mixed $value * @param int|string|Type|null $type * @param string $placeHolder The name to bind with. The string must start with a colon ':'. * * @return string the placeholder name used. */ public function createNamedParameter($value, $type = ParameterType::STRING, $placeHolder = null) { if ($placeHolder === null) { $this->boundCounter++; $placeHolder = ':dcValue' . $this->boundCounter; } $this->setParameter(substr($placeHolder, 1), $value, $type); return $placeHolder; } /** * Creates a new positional parameter and bind the given value to it. * * Attention: If you are using positional parameters with the query builder you have * to be very careful to bind all parameters in the order they appear in the SQL * statement , otherwise they get bound in the wrong order which can lead to serious * bugs in your code. * * Example: * <code> * $qb = $conn->createQueryBuilder(); * $qb->select('u.*') * ->from('users', 'u') * ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING)) * ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING)) * </code> * * @param mixed $value * @param int|string|Type|null $type * * @return string */ public function createPositionalParameter($value, $type = ParameterType::STRING) { $this->setParameter($this->boundCounter, $value, $type); $this->boundCounter++; return '?'; } /** * @param string $fromAlias * @param array<string,true> $knownAliases * * @throws QueryException */ private function getSQLForJoins($fromAlias, array &$knownAliases): string { $sql = ''; if (isset($this->sqlParts['join'][$fromAlias])) { foreach ($this->sqlParts['join'][$fromAlias] as $join) { if (array_key_exists($join['joinAlias'], $knownAliases)) { throw QueryException::nonUniqueAlias((string) $join['joinAlias'], array_keys($knownAliases)); } $sql .= ' ' . strtoupper($join['joinType']) . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']; if ($join['joinCondition'] !== null) { $sql .= ' ON ' . $join['joinCondition']; } $knownAliases[$join['joinAlias']] = true; } foreach ($this->sqlParts['join'][$fromAlias] as $join) { $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases); } } return $sql; } /** * Deep clone of all expression objects in the SQL parts. * * @return void */ public function __clone() { foreach ($this->sqlParts as $part => $elements) { if (is_array($this->sqlParts[$part])) { foreach ($this->sqlParts[$part] as $idx => $element) { if (! is_object($element)) { continue; } $this->sqlParts[$part][$idx] = clone $element; } } elseif (is_object($elements)) { $this->sqlParts[$part] = clone $elements; } } foreach ($this->params as $name => $param) { if (! is_object($param)) { continue; } $this->params[$name] = clone $param; } } /** * Enables caching of the results of this query, for given amount of seconds * and optionally specified which key to use for the cache entry. * * @return $this */ public function enableResultCache(QueryCacheProfile $cacheProfile): self { $this->resultCacheProfile = $cacheProfile; return $this; } /** * Disables caching of the results of this query. * * @return $this */ public function disableResultCache(): self { $this->resultCacheProfile = null; return $this; } } Query/ForUpdate.php 0000755 00000000620 00000000000 0010215 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Query; /** @internal */ final class ForUpdate { private int $conflictResolutionMode; public function __construct(int $conflictResolutionMode) { $this->conflictResolutionMode = $conflictResolutionMode; } public function getConflictResolutionMode(): int { return $this->conflictResolutionMode; } } Query/ForUpdate/ConflictResolutionMode.php 0000755 00000000653 00000000000 0014655 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Query\ForUpdate; final class ConflictResolutionMode { /** * Wait for the row to be unlocked */ public const ORDINARY = 0; /** * Skip the row if it is locked */ public const SKIP_LOCKED = 1; /** * This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } } Query/QueryException.php 0000755 00000001767 00000000000 0011325 0 ustar 00 <?php namespace Doctrine\DBAL\Query; use Doctrine\DBAL\Exception; use function implode; /** @psalm-immutable */ class QueryException extends Exception { /** * @param string $alias * @param string[] $registeredAliases * * @return QueryException */ public static function unknownAlias($alias, $registeredAliases) { return new self("The given alias '" . $alias . "' is not part of " . 'any FROM or JOIN clause table. The currently registered ' . 'aliases are: ' . implode(', ', $registeredAliases) . '.'); } /** * @param string $alias * @param string[] $registeredAliases * * @return QueryException */ public static function nonUniqueAlias($alias, $registeredAliases) { return new self("The given alias '" . $alias . "' is not unique " . 'in FROM and JOIN clause table. The currently registered ' . 'aliases are: ' . implode(', ', $registeredAliases) . '.'); } } Query/Limit.php 0000755 00000001114 00000000000 0007401 0 ustar 00 <?php namespace Doctrine\DBAL\Query; final class Limit { private ?int $maxResults; private int $firstResult; public function __construct(?int $maxResults, int $firstResult) { $this->maxResults = $maxResults; $this->firstResult = $firstResult; } public function isDefined(): bool { return $this->maxResults !== null || $this->firstResult !== 0; } public function getMaxResults(): ?int { return $this->maxResults; } public function getFirstResult(): int { return $this->firstResult; } } Query/Expression/ExpressionBuilder.php 0000755 00000022612 00000000000 0014136 0 ustar 00 <?php namespace Doctrine\DBAL\Query\Expression; use Doctrine\DBAL\Connection; use Doctrine\Deprecations\Deprecation; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function sprintf; /** * ExpressionBuilder class is responsible to dynamically create SQL query parts. */ class ExpressionBuilder { public const EQ = '='; public const NEQ = '<>'; public const LT = '<'; public const LTE = '<='; public const GT = '>'; public const GTE = '>='; /** * The DBAL Connection. */ private Connection $connection; /** * Initializes a new <tt>ExpressionBuilder</tt>. * * @param Connection $connection The DBAL Connection. */ public function __construct(Connection $connection) { $this->connection = $connection; } /** * Creates a conjunction of the given expressions. * * @param string|CompositeExpression $expression * @param string|CompositeExpression ...$expressions */ public function and($expression, ...$expressions): CompositeExpression { return CompositeExpression::and($expression, ...$expressions); } /** * Creates a disjunction of the given expressions. * * @param string|CompositeExpression $expression * @param string|CompositeExpression ...$expressions */ public function or($expression, ...$expressions): CompositeExpression { return CompositeExpression::or($expression, ...$expressions); } /** * @deprecated Use `and()` instead. * * @param mixed $x Optional clause. Defaults = null, but requires * at least one defined when converting to string. * * @return CompositeExpression */ public function andX($x = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3851', 'ExpressionBuilder::andX() is deprecated, use ExpressionBuilder::and() instead.', ); return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); } /** * @deprecated Use `or()` instead. * * @param mixed $x Optional clause. Defaults = null, but requires * at least one defined when converting to string. * * @return CompositeExpression */ public function orX($x = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3851', 'ExpressionBuilder::orX() is deprecated, use ExpressionBuilder::or() instead.', ); return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args()); } /** * Creates a comparison expression. * * @param mixed $x The left expression. * @param string $operator One of the ExpressionBuilder::* constants. * @param mixed $y The right expression. * * @return string */ public function comparison($x, $operator, $y) { return $x . ' ' . $operator . ' ' . $y; } /** * Creates an equality comparison expression with the given arguments. * * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> = <right expr>. Example: * * [php] * // u.id = ? * $expr->eq('u.id', '?'); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function eq($x, $y) { return $this->comparison($x, self::EQ, $y); } /** * Creates a non equality comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> <> <right expr>. Example: * * [php] * // u.id <> 1 * $q->where($q->expr()->neq('u.id', '1')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function neq($x, $y) { return $this->comparison($x, self::NEQ, $y); } /** * Creates a lower-than comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> < <right expr>. Example: * * [php] * // u.id < ? * $q->where($q->expr()->lt('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function lt($x, $y) { return $this->comparison($x, self::LT, $y); } /** * Creates a lower-than-equal comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> <= <right expr>. Example: * * [php] * // u.id <= ? * $q->where($q->expr()->lte('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function lte($x, $y) { return $this->comparison($x, self::LTE, $y); } /** * Creates a greater-than comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> > <right expr>. Example: * * [php] * // u.id > ? * $q->where($q->expr()->gt('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function gt($x, $y) { return $this->comparison($x, self::GT, $y); } /** * Creates a greater-than-equal comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> >= <right expr>. Example: * * [php] * // u.id >= ? * $q->where($q->expr()->gte('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function gte($x, $y) { return $this->comparison($x, self::GTE, $y); } /** * Creates an IS NULL expression with the given arguments. * * @param string $x The expression to be restricted by IS NULL. * * @return string */ public function isNull($x) { return $x . ' IS NULL'; } /** * Creates an IS NOT NULL expression with the given arguments. * * @param string $x The expression to be restricted by IS NOT NULL. * * @return string */ public function isNotNull($x) { return $x . ' IS NOT NULL'; } /** * Creates a LIKE() comparison expression with the given arguments. * * @param string $x The expression to be inspected by the LIKE comparison * @param mixed $y The pattern to compare against * * @return string */ public function like($x, $y/*, ?string $escapeChar = null */) { return $this->comparison($x, 'LIKE', $y) . (func_num_args() >= 3 ? sprintf(' ESCAPE %s', func_get_arg(2)) : ''); } /** * Creates a NOT LIKE() comparison expression with the given arguments. * * @param string $x The expression to be inspected by the NOT LIKE comparison * @param mixed $y The pattern to compare against * * @return string */ public function notLike($x, $y/*, ?string $escapeChar = null */) { return $this->comparison($x, 'NOT LIKE', $y) . (func_num_args() >= 3 ? sprintf(' ESCAPE %s', func_get_arg(2)) : ''); } /** * Creates an IN () comparison expression with the given arguments. * * @param string $x The SQL expression to be matched against the set. * @param string|string[] $y The SQL expression or an array of SQL expressions representing the set. * * @return string */ public function in($x, $y) { return $this->comparison($x, 'IN', '(' . implode(', ', (array) $y) . ')'); } /** * Creates a NOT IN () comparison expression with the given arguments. * * @param string $x The SQL expression to be matched against the set. * @param string|string[] $y The SQL expression or an array of SQL expressions representing the set. * * @return string */ public function notIn($x, $y) { return $this->comparison($x, 'NOT IN', '(' . implode(', ', (array) $y) . ')'); } /** * Builds an SQL literal from a given input parameter. * * The usage of this method is discouraged. Use prepared statements * or {@see AbstractPlatform::quoteStringLiteral()} instead. * * @param mixed $input The parameter to be quoted. * @param int|null $type The type of the parameter. * * @return string */ public function literal($input, $type = null) { return $this->connection->quote($input, $type); } } Query/Expression/CompositeExpression.php 0000755 00000010456 00000000000 0014515 0 ustar 00 <?php namespace Doctrine\DBAL\Query\Expression; use Countable; use Doctrine\Deprecations\Deprecation; use ReturnTypeWillChange; use function array_merge; use function count; use function implode; /** * Composite expression is responsible to build a group of similar expression. */ class CompositeExpression implements Countable { /** * Constant that represents an AND composite expression. */ public const TYPE_AND = 'AND'; /** * Constant that represents an OR composite expression. */ public const TYPE_OR = 'OR'; /** * The instance type of composite expression. * * @var string */ private $type; /** * Each expression part of the composite expression. * * @var self[]|string[] */ private array $parts = []; /** * @internal Use the and() / or() factory methods. * * @param string $type Instance type of composite expression. * @param self[]|string[] $parts Composition of expressions to be joined on composite expression. */ public function __construct($type, array $parts = []) { $this->type = $type; $this->addMultiple($parts); Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3864', 'Do not use CompositeExpression constructor directly, use static and() and or() factory methods.', ); } /** * @param self|string $part * @param self|string ...$parts */ public static function and($part, ...$parts): self { return new self(self::TYPE_AND, array_merge([$part], $parts)); } /** * @param self|string $part * @param self|string ...$parts */ public static function or($part, ...$parts): self { return new self(self::TYPE_OR, array_merge([$part], $parts)); } /** * Adds multiple parts to composite expression. * * @deprecated This class will be made immutable. Use with() instead. * * @param self[]|string[] $parts * * @return CompositeExpression */ public function addMultiple(array $parts = []) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3844', 'CompositeExpression::addMultiple() is deprecated, use CompositeExpression::with() instead.', ); foreach ($parts as $part) { $this->add($part); } return $this; } /** * Adds an expression to composite expression. * * @deprecated This class will be made immutable. Use with() instead. * * @param mixed $part * * @return CompositeExpression */ public function add($part) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3844', 'CompositeExpression::add() is deprecated, use CompositeExpression::with() instead.', ); if ($part === null) { return $this; } if ($part instanceof self && count($part) === 0) { return $this; } $this->parts[] = $part; return $this; } /** * Returns a new CompositeExpression with the given parts added. * * @param self|string $part * @param self|string ...$parts */ public function with($part, ...$parts): self { $that = clone $this; $that->parts = array_merge($that->parts, [$part], $parts); return $that; } /** * Retrieves the amount of expressions on composite expression. * * @return int * @psalm-return int<0, max> */ #[ReturnTypeWillChange] public function count() { return count($this->parts); } /** * Retrieves the string representation of this composite expression. * * @return string */ public function __toString() { if ($this->count() === 1) { return (string) $this->parts[0]; } return '(' . implode(') ' . $this->type . ' (', $this->parts) . ')'; } /** * Returns the type of this composite expression (AND/OR). * * @return string */ public function getType() { return $this->type; } } ArrayParameterType.php 0000755 00000002204 00000000000 0011000 0 ustar 00 <?php namespace Doctrine\DBAL; final class ArrayParameterType { /** * Represents an array of ints to be expanded by Doctrine SQL parsing. */ public const INTEGER = ParameterType::INTEGER + Connection::ARRAY_PARAM_OFFSET; /** * Represents an array of strings to be expanded by Doctrine SQL parsing. */ public const STRING = ParameterType::STRING + Connection::ARRAY_PARAM_OFFSET; /** * Represents an array of ascii strings to be expanded by Doctrine SQL parsing. */ public const ASCII = ParameterType::ASCII + Connection::ARRAY_PARAM_OFFSET; /** * Represents an array of ascii strings to be expanded by Doctrine SQL parsing. */ public const BINARY = ParameterType::BINARY + Connection::ARRAY_PARAM_OFFSET; /** * @internal * * @psalm-param self::* $type * * @psalm-return ParameterType::INTEGER|ParameterType::STRING|ParameterType::ASCII|ParameterType::BINARY */ public static function toElementParameterType(int $type): int { return $type - Connection::ARRAY_PARAM_OFFSET; } private function __construct() { } } Schema/SQLServerSchemaManager.php 0000755 00000045466 00000000000 0012702 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\SQLServer; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_change_key_case; use function assert; use function count; use function explode; use function implode; use function is_string; use function preg_match; use function sprintf; use function str_replace; use function strpos; use function strtok; use const CASE_LOWER; /** * SQL Server Schema Manager. * * @extends AbstractSchemaManager<SQLServerPlatform> */ class SQLServerSchemaManager extends AbstractSchemaManager { private ?string $databaseCollation = null; /** * {@inheritDoc} */ public function listTableNames() { return $this->doListTableNames(); } /** * {@inheritDoc} */ public function listTables() { return $this->doListTables(); } /** * {@inheritDoc} * * @deprecated Use {@see introspectTable()} instead. */ public function listTableDetails($name) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5595', '%s is deprecated. Use introspectTable() instead.', __METHOD__, ); return $this->doListTableDetails($name); } /** * {@inheritDoc} */ public function listTableColumns($table, $database = null) { return $this->doListTableColumns($table, $database); } /** * {@inheritDoc} */ public function listTableIndexes($table) { return $this->doListTableIndexes($table); } /** * {@inheritDoc} */ public function listTableForeignKeys($table, $database = null) { return $this->doListTableForeignKeys($table, $database); } /** * {@inheritDoc} */ public function listSchemaNames(): array { return $this->_conn->fetchFirstColumn( <<<'SQL' SELECT name FROM sys.schemas WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys') SQL, ); } /** * {@inheritDoc} */ protected function _getPortableSequenceDefinition($sequence) { return new Sequence($sequence['name'], (int) $sequence['increment'], (int) $sequence['start_value']); } /** * {@inheritDoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $dbType = strtok($tableColumn['type'], '(), '); assert(is_string($dbType)); $fixed = null; $length = (int) $tableColumn['length']; $default = $tableColumn['default']; if (! isset($tableColumn['name'])) { $tableColumn['name'] = ''; } if ($default !== null) { $default = $this->parseDefaultExpression($default); } switch ($dbType) { case 'nchar': case 'ntext': // Unicode data requires 2 bytes per character $length /= 2; break; case 'nvarchar': if ($length === -1) { break; } // Unicode data requires 2 bytes per character $length /= 2; break; case 'varchar': // TEXT type is returned as VARCHAR(MAX) with a length of -1 if ($length === -1) { $dbType = 'text'; } break; case 'varbinary': if ($length === -1) { $dbType = 'blob'; } break; } if ($dbType === 'char' || $dbType === 'nchar' || $dbType === 'binary') { $fixed = true; } $type = $this->_platform->getDoctrineTypeMapping($dbType); $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); $options = [ 'unsigned' => false, 'fixed' => (bool) $fixed, 'default' => $default, 'notnull' => (bool) $tableColumn['notnull'], 'scale' => $tableColumn['scale'], 'precision' => $tableColumn['precision'], 'autoincrement' => (bool) $tableColumn['autoincrement'], 'comment' => $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, ]; if ($length !== 0 && ($type === 'text' || $type === 'string' || $type === 'binary')) { $options['length'] = $length; } $column = new Column($tableColumn['name'], Type::getType($type), $options); if (isset($tableColumn['collation']) && $tableColumn['collation'] !== 'NULL') { $column->setPlatformOption('collation', $tableColumn['collation']); } return $column; } private function parseDefaultExpression(string $value): ?string { while (preg_match('/^\((.*)\)$/s', $value, $matches)) { $value = $matches[1]; } if ($value === 'NULL') { return null; } if (preg_match('/^\'(.*)\'$/s', $value, $matches) === 1) { $value = str_replace("''", "'", $matches[1]); } if ($value === 'getdate()') { return $this->_platform->getCurrentTimestampSQL(); } return $value; } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $foreignKeys = []; foreach ($tableForeignKeys as $tableForeignKey) { $name = $tableForeignKey['ForeignKey']; if (! isset($foreignKeys[$name])) { $foreignKeys[$name] = [ 'local_columns' => [$tableForeignKey['ColumnName']], 'foreign_table' => $tableForeignKey['ReferenceTableName'], 'foreign_columns' => [$tableForeignKey['ReferenceColumnName']], 'name' => $name, 'options' => [ 'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']), 'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc']), ], ]; } else { $foreignKeys[$name]['local_columns'][] = $tableForeignKey['ColumnName']; $foreignKeys[$name]['foreign_columns'][] = $tableForeignKey['ReferenceColumnName']; } } return parent::_getPortableTableForeignKeysList($foreignKeys); } /** * {@inheritDoc} */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { foreach ($tableIndexes as &$tableIndex) { $tableIndex['non_unique'] = (bool) $tableIndex['non_unique']; $tableIndex['primary'] = (bool) $tableIndex['primary']; $tableIndex['flags'] = $tableIndex['flags'] ? [$tableIndex['flags']] : null; } return parent::_getPortableTableIndexesList($tableIndexes, $tableName); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { return new ForeignKeyConstraint( $tableForeignKey['local_columns'], $tableForeignKey['foreign_table'], $tableForeignKey['foreign_columns'], $tableForeignKey['name'], $tableForeignKey['options'], ); } /** * {@inheritDoc} */ protected function _getPortableTableDefinition($table) { if ($table['schema_name'] !== 'dbo') { return $table['schema_name'] . '.' . $table['table_name']; } return $table['table_name']; } /** * {@inheritDoc} */ protected function _getPortableDatabaseDefinition($database) { return $database['name']; } /** * {@inheritDoc} * * @deprecated Use {@see listSchemaNames()} instead. */ protected function getPortableNamespaceDefinition(array $namespace) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'SQLServerSchemaManager::getPortableNamespaceDefinition() is deprecated,' . ' use SQLServerSchemaManager::listSchemaNames() instead.', ); return $namespace['name']; } /** * {@inheritDoc} */ protected function _getPortableViewDefinition($view) { // @todo return new View($view['name'], $view['definition']); } /** * {@inheritDoc} */ public function alterTable(TableDiff $tableDiff) { $droppedColumns = $tableDiff->getDroppedColumns(); if (count($droppedColumns) > 0) { $tableName = ($tableDiff->getOldTable() ?? $tableDiff->getName($this->_platform))->getName(); foreach ($droppedColumns as $col) { foreach ($this->getColumnConstraints($tableName, $col->getName()) as $constraint) { $this->_conn->executeStatement( sprintf( 'ALTER TABLE %s DROP CONSTRAINT %s', $tableName, $constraint, ), ); } } } parent::alterTable($tableDiff); } /** * Returns the names of the constraints for a given column. * * @return iterable<string> * * @throws Exception */ private function getColumnConstraints(string $table, string $column): iterable { return $this->_conn->iterateColumn( <<<'SQL' SELECT o.name FROM sys.objects o INNER JOIN sys.objects t ON t.object_id = o.parent_object_id AND t.type = 'U' INNER JOIN sys.default_constraints dc ON dc.object_id = o.object_id INNER JOIN sys.columns c ON c.column_id = dc.parent_column_id AND c.object_id = t.object_id WHERE t.name = ? AND c.name = ? SQL , [$table, $column], ); } /** @throws Exception */ public function createComparator(): Comparator { return new SQLServer\Comparator($this->_platform, $this->getDatabaseCollation()); } /** @throws Exception */ private function getDatabaseCollation(): string { if ($this->databaseCollation === null) { $databaseCollation = $this->_conn->fetchOne( 'SELECT collation_name FROM sys.databases WHERE name = ' . $this->_platform->getCurrentDatabaseExpression(), ); // a database is always selected, even if omitted in the connection parameters assert(is_string($databaseCollation)); $this->databaseCollation = $databaseCollation; } return $this->databaseCollation; } protected function selectTableNames(string $databaseName): Result { // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams $sql = <<<'SQL' SELECT name AS table_name, SCHEMA_NAME(schema_id) AS schema_name FROM sys.objects WHERE type = 'U' AND name != 'sysdiagrams' ORDER BY name SQL; return $this->_conn->executeQuery($sql); } protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' obj.name AS table_name, scm.name AS schema_name,'; } $sql .= <<<'SQL' col.name, type.name AS type, col.max_length AS length, ~col.is_nullable AS notnull, def.definition AS [default], col.scale, col.precision, col.is_identity AS autoincrement, col.collation_name AS collation, -- CAST avoids driver error for sql_variant type CAST(prop.value AS NVARCHAR(MAX)) AS comment FROM sys.columns AS col JOIN sys.types AS type ON col.user_type_id = type.user_type_id JOIN sys.objects AS obj ON col.object_id = obj.object_id JOIN sys.schemas AS scm ON obj.schema_id = scm.schema_id LEFT JOIN sys.default_constraints def ON col.default_object_id = def.object_id AND col.object_id = def.parent_object_id LEFT JOIN sys.extended_properties AS prop ON obj.object_id = prop.major_id AND col.column_id = prop.minor_id AND prop.name = 'MS_Description' SQL; // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams $conditions = ["obj.type = 'U'", "obj.name != 'sysdiagrams'"]; $params = []; if ($tableName !== null) { $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'obj.name'); } $sql .= ' WHERE ' . implode(' AND ', $conditions); return $this->_conn->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' tbl.name AS table_name, scm.name AS schema_name,'; } $sql .= <<<'SQL' idx.name AS key_name, col.name AS column_name, ~idx.is_unique AS non_unique, idx.is_primary_key AS [primary], CASE idx.type WHEN '1' THEN 'clustered' WHEN '2' THEN 'nonclustered' ELSE NULL END AS flags FROM sys.tables AS tbl JOIN sys.schemas AS scm ON tbl.schema_id = scm.schema_id JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id SQL; $conditions = []; $params = []; if ($tableName !== null) { $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'tbl.name'); $sql .= ' WHERE ' . implode(' AND ', $conditions); } $sql .= ' ORDER BY idx.index_id, idxcol.key_ordinal'; return $this->_conn->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' OBJECT_NAME (f.parent_object_id) AS table_name, SCHEMA_NAME(f.schema_id) AS schema_name,'; } $sql .= <<<'SQL' f.name AS ForeignKey, SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, OBJECT_NAME (f.parent_object_id) AS TableName, COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, f.delete_referential_action_desc, f.update_referential_action_desc FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id ON f.OBJECT_ID = fc.constraint_object_id SQL; $conditions = []; $params = []; if ($tableName !== null) { $conditions[] = $this->getTableWhereClause( $tableName, 'SCHEMA_NAME(f.schema_id)', 'OBJECT_NAME(f.parent_object_id)', ); $sql .= ' WHERE ' . implode(' AND ', $conditions); } $sql .= ' ORDER BY fc.constraint_column_id'; return $this->_conn->executeQuery($sql, $params); } /** * {@inheritDoc} */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { $sql = <<<'SQL' SELECT tbl.name, p.value AS [table_comment] FROM sys.tables AS tbl INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1 SQL; $conditions = ["SCHEMA_NAME(tbl.schema_id) = N'dbo'", "p.name = N'MS_Description'"]; $params = []; if ($tableName !== null) { $conditions[] = "tbl.name = N'" . $tableName . "'"; } $sql .= ' WHERE ' . implode(' AND ', $conditions); /** @var array<string,array<string,mixed>> $metadata */ $metadata = $this->_conn->executeQuery($sql, $params) ->fetchAllAssociativeIndexed(); $tableOptions = []; foreach ($metadata as $table => $data) { $data = array_change_key_case($data, CASE_LOWER); $tableOptions[$table] = [ 'comment' => $data['table_comment'], ]; } return $tableOptions; } /** * Returns the where clause to filter schema and table name in a query. * * @param string $table The full qualified name of the table. * @param string $schemaColumn The name of the column to compare the schema to in the where clause. * @param string $tableColumn The name of the column to compare the table to in the where clause. */ private function getTableWhereClause($table, $schemaColumn, $tableColumn): string { if (strpos($table, '.') !== false) { [$schema, $table] = explode('.', $table); $schema = $this->_platform->quoteStringLiteral($schema); $table = $this->_platform->quoteStringLiteral($table); } else { $schema = 'SCHEMA_NAME()'; $table = $this->_platform->quoteStringLiteral($table); } return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); } } Schema/Exception/IndexNameInvalid.php 0000755 00000000710 00000000000 0013534 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class IndexNameInvalid extends SchemaException { public static function new(string $indexName): self { return new self( sprintf('Invalid index name "%s" given, has to be [a-zA-Z0-9_].', $indexName), self::INDEX_INVALID_NAME, ); } } Schema/Exception/ColumnDoesNotExist.php 0000755 00000000736 00000000000 0014133 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class ColumnDoesNotExist extends SchemaException { public static function new(string $columnName, string $table): self { return new self( sprintf('There is no column with name "%s" on table "%s".', $columnName, $table), self::COLUMN_DOESNT_EXIST, ); } } Schema/Exception/ColumnAlreadyExists.php 0000755 00000000746 00000000000 0014325 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class ColumnAlreadyExists extends SchemaException { public static function new(string $tableName, string $columnName): self { return new self( sprintf('The column "%s" on table "%s" already exists.', $columnName, $tableName), self::COLUMN_ALREADY_EXISTS, ); } } Schema/Exception/InvalidTableName.php 0000755 00000000567 00000000000 0013526 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class InvalidTableName extends SchemaException { public static function new(string $tableName): self { return new self(sprintf('Invalid table name specified "%s".', $tableName)); } } Schema/Exception/IndexAlreadyExists.php 0000755 00000000747 00000000000 0014140 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class IndexAlreadyExists extends SchemaException { public static function new(string $indexName, string $table): self { return new self( sprintf('An index with name "%s" was already defined on table "%s".', $indexName, $table), self::INDEX_ALREADY_EXISTS, ); } } Schema/Exception/UnknownColumnOption.php 0000755 00000000615 00000000000 0014367 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class UnknownColumnOption extends SchemaException { public static function new(string $name): self { return new self( sprintf('The "%s" column option is not supported.', $name), ); } } Schema/Exception/UniqueConstraintDoesNotExist.php 0000755 00000001007 00000000000 0016201 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class UniqueConstraintDoesNotExist extends SchemaException { public static function new(string $constraintName, string $table): self { return new self( sprintf('There exists no unique constraint with the name "%s" on table "%s".', $constraintName, $table), self::CONSTRAINT_DOESNT_EXIST, ); } } Schema/Exception/NamedForeignKeyRequired.php 0000755 00000001624 00000000000 0015072 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Table; use function implode; use function sprintf; /** @psalm-immutable */ final class NamedForeignKeyRequired extends SchemaException { public static function new(Table $localTable, ForeignKeyConstraint $foreignKey): self { return new self( sprintf( 'The performed schema operation on "%s" requires a named foreign key, ' . 'but the given foreign key from (%s) onto foreign table "%s" (%s) is currently unnamed.', $localTable->getName(), implode(', ', $foreignKey->getColumns()), $foreignKey->getForeignTableName(), implode(', ', $foreignKey->getForeignColumns()), ), ); } } Schema/Exception/ForeignKeyDoesNotExist.php 0000755 00000000773 00000000000 0014741 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class ForeignKeyDoesNotExist extends SchemaException { public static function new(string $foreignKeyName, string $table): self { return new self( sprintf('There exists no foreign key with the name "%s" on table "%s".', $foreignKeyName, $table), self::FOREIGNKEY_DOESNT_EXIST, ); } } Schema/Exception/SequenceDoesNotExist.php 0000755 00000000713 00000000000 0014441 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class SequenceDoesNotExist extends SchemaException { public static function new(string $sequenceName): self { return new self( sprintf('There exists no sequence with the name "%s".', $sequenceName), self::SEQUENCE_DOENST_EXIST, ); } } Schema/Exception/IndexDoesNotExist.php 0000755 00000000722 00000000000 0013740 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class IndexDoesNotExist extends SchemaException { public static function new(string $indexName, string $table): self { return new self( sprintf('Index "%s" does not exist on table "%s".', $indexName, $table), self::INDEX_DOESNT_EXIST, ); } } Schema/Exception/NamespaceAlreadyExists.php 0000755 00000000722 00000000000 0014756 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class NamespaceAlreadyExists extends SchemaException { public static function new(string $namespaceName): self { return new self( sprintf('The namespace with name "%s" already exists.', $namespaceName), self::NAMESPACE_ALREADY_EXISTS, ); } } Schema/Exception/SequenceAlreadyExists.php 0000755 00000000703 00000000000 0014631 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class SequenceAlreadyExists extends SchemaException { public static function new(string $sequenceName): self { return new self( sprintf('The sequence "%s" already exists.', $sequenceName), self::SEQUENCE_ALREADY_EXISTS, ); } } Schema/Exception/TableDoesNotExist.php 0000755 00000000702 00000000000 0013716 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class TableDoesNotExist extends SchemaException { public static function new(string $tableName): self { return new self( sprintf('There is no table with name "%s" in the schema.', $tableName), self::TABLE_DOESNT_EXIST, ); } } Schema/Exception/TableAlreadyExists.php 0000755 00000000676 00000000000 0014121 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class TableAlreadyExists extends SchemaException { public static function new(string $tableName): self { return new self( sprintf('The table with name "%s" already exists.', $tableName), self::TABLE_ALREADY_EXISTS, ); } } Schema/SchemaManagerFactory.php 0000755 00000000575 00000000000 0012453 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Connection; /** * Creates a schema manager for the given connection. * * This interface is an extension point for applications that need to override schema managers. */ interface SchemaManagerFactory { public function createSchemaManager(Connection $connection): AbstractSchemaManager; } Schema/Visitor/Visitor.php 0000755 00000002011 00000000000 0011511 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; /** * Schema Visitor used for Validation or Generation purposes. * * @deprecated */ interface Visitor { /** * @return void * * @throws SchemaException */ public function acceptSchema(Schema $schema); /** @return void */ public function acceptTable(Table $table); /** @return void */ public function acceptColumn(Table $table, Column $column); /** * @return void * * @throws SchemaException */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint); /** @return void */ public function acceptIndex(Table $table, Index $index); /** @return void */ public function acceptSequence(Sequence $sequence); } Schema/Visitor/Graphviz.php 0000755 00000010645 00000000000 0011660 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Table; use function current; use function file_put_contents; use function in_array; use function strtolower; /** * Create a Graphviz output of a Schema. * * @deprecated */ class Graphviz extends AbstractVisitor { private string $output = ''; /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { $this->output .= $this->createNodeRelation( $fkConstraint->getLocalTableName() . ':col' . current($fkConstraint->getLocalColumns()) . ':se', $fkConstraint->getForeignTableName() . ':col' . current($fkConstraint->getForeignColumns()) . ':se', [ 'dir' => 'back', 'arrowtail' => 'dot', 'arrowhead' => 'normal', ], ); } /** * {@inheritDoc} */ public function acceptSchema(Schema $schema) { $this->output = 'digraph "' . $schema->getName() . '" {' . "\n"; $this->output .= 'splines = true;' . "\n"; $this->output .= 'overlap = false;' . "\n"; $this->output .= 'outputorder=edgesfirst;' . "\n"; $this->output .= 'mindist = 0.6;' . "\n"; $this->output .= 'sep = .2;' . "\n"; } /** * {@inheritDoc} */ public function acceptTable(Table $table) { $this->output .= $this->createNode( $table->getName(), [ 'label' => $this->createTableLabel($table), 'shape' => 'plaintext', ], ); } private function createTableLabel(Table $table): string { // Start the table $label = '<<TABLE CELLSPACING="0" BORDER="1" ALIGN="LEFT">'; // The title $label .= '<TR><TD BORDER="1" COLSPAN="3" ALIGN="CENTER" BGCOLOR="#fcaf3e">' . '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="12">' . $table->getName() . '</FONT></TD></TR>'; // The attributes block foreach ($table->getColumns() as $column) { $columnLabel = $column->getName(); $label .= '<TR>' . '<TD BORDER="0" ALIGN="LEFT" BGCOLOR="#eeeeec">' . '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="12">' . $columnLabel . '</FONT>' . '</TD>' . '<TD BORDER="0" ALIGN="LEFT" BGCOLOR="#eeeeec">' . '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="10">' . strtolower($column->getType()->getName()) . '</FONT>' . '</TD>' . '<TD BORDER="0" ALIGN="RIGHT" BGCOLOR="#eeeeec" PORT="col' . $column->getName() . '">'; $primaryKey = $table->getPrimaryKey(); if ($primaryKey !== null && in_array($column->getName(), $primaryKey->getColumns(), true)) { $label .= "\xe2\x9c\xb7"; } $label .= '</TD></TR>'; } // End the table $label .= '</TABLE>>'; return $label; } /** * @param string $name * @param string[] $options */ private function createNode($name, $options): string { $node = $name . ' ['; foreach ($options as $key => $value) { $node .= $key . '=' . $value . ' '; } $node .= "]\n"; return $node; } /** * @param string $node1 * @param string $node2 * @param string[] $options */ private function createNodeRelation($node1, $node2, $options): string { $relation = $node1 . ' -> ' . $node2 . ' ['; foreach ($options as $key => $value) { $relation .= $key . '=' . $value . ' '; } $relation .= "]\n"; return $relation; } /** * Get Graphviz Output * * @return string */ public function getOutput() { return $this->output . '}'; } /** * Writes dot language output to a file. This should usually be a *.dot file. * * You have to convert the output into a viewable format. For example use "neato" on linux systems * and execute: * * neato -Tpng -o er.png er.dot * * @param string $filename * * @return void */ public function write($filename) { file_put_contents($filename, $this->getOutput()); } } Schema/Visitor/CreateSchemaSqlCollector.php 0000755 00000005244 00000000000 0014740 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\Deprecations\Deprecation; use function array_merge; /** @deprecated Use {@link CreateSchemaObjectsSQLBuilder} instead. */ class CreateSchemaSqlCollector extends AbstractVisitor { /** @var string[] */ private array $createNamespaceQueries = []; /** @var string[] */ private array $createTableQueries = []; /** @var string[] */ private array $createSequenceQueries = []; /** @var string[] */ private array $createFkConstraintQueries = []; private AbstractPlatform $platform; public function __construct(AbstractPlatform $platform) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5416', 'CreateSchemaSqlCollector is deprecated. Use CreateSchemaObjectsSQLBuilder instead.', ); $this->platform = $platform; } /** * {@inheritDoc} */ public function acceptNamespace($namespaceName) { if (! $this->platform->supportsSchemas()) { return; } $this->createNamespaceQueries[] = $this->platform->getCreateSchemaSQL($namespaceName); } /** * {@inheritDoc} */ public function acceptTable(Table $table) { $this->createTableQueries = array_merge($this->createTableQueries, $this->platform->getCreateTableSQL($table)); } /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { if (! $this->platform->supportsForeignKeyConstraints()) { return; } $this->createFkConstraintQueries[] = $this->platform->getCreateForeignKeySQL($fkConstraint, $localTable); } /** * {@inheritDoc} */ public function acceptSequence(Sequence $sequence) { $this->createSequenceQueries[] = $this->platform->getCreateSequenceSQL($sequence); } /** @return void */ public function resetQueries() { $this->createNamespaceQueries = []; $this->createTableQueries = []; $this->createSequenceQueries = []; $this->createFkConstraintQueries = []; } /** * Gets all queries collected so far. * * @return string[] */ public function getQueries() { return array_merge( $this->createNamespaceQueries, $this->createSequenceQueries, $this->createTableQueries, $this->createFkConstraintQueries, ); } } Schema/Visitor/NamespaceVisitor.php 0000755 00000000556 00000000000 0013342 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; /** * Visitor that can visit schema namespaces. * * @deprecated */ interface NamespaceVisitor { /** * Accepts a schema namespace name. * * @param string $namespaceName The schema namespace name to accept. * * @return void */ public function acceptNamespace($namespaceName); } Schema/Visitor/DropSchemaSqlCollector.php 0000755 00000005653 00000000000 0014445 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\Deprecations\Deprecation; use SplObjectStorage; use function assert; use function strlen; /** * Gathers SQL statements that allow to completely drop the current schema. * * @deprecated Use {@link DropSchemaObjectsSQLBuilder} instead. */ class DropSchemaSqlCollector extends AbstractVisitor { private SplObjectStorage $constraints; private SplObjectStorage $sequences; private SplObjectStorage $tables; private AbstractPlatform $platform; public function __construct(AbstractPlatform $platform) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5416', 'DropSchemaSqlCollector is deprecated. Use DropSchemaObjectsSQLBuilder instead.', ); $this->platform = $platform; $this->initializeQueries(); } /** * {@inheritDoc} */ public function acceptTable(Table $table) { $this->tables->attach($table); } /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { if (strlen($fkConstraint->getName()) === 0) { throw SchemaException::namedForeignKeyRequired($localTable, $fkConstraint); } $this->constraints->attach($fkConstraint, $localTable); } /** * {@inheritDoc} */ public function acceptSequence(Sequence $sequence) { $this->sequences->attach($sequence); } /** @return void */ public function clearQueries() { $this->initializeQueries(); } /** @return string[] */ public function getQueries() { $sql = []; foreach ($this->constraints as $fkConstraint) { assert($fkConstraint instanceof ForeignKeyConstraint); $localTable = $this->constraints[$fkConstraint]; $sql[] = $this->platform->getDropForeignKeySQL( $fkConstraint->getQuotedName($this->platform), $localTable->getQuotedName($this->platform), ); } foreach ($this->sequences as $sequence) { assert($sequence instanceof Sequence); $sql[] = $this->platform->getDropSequenceSQL($sequence->getQuotedName($this->platform)); } foreach ($this->tables as $table) { assert($table instanceof Table); $sql[] = $this->platform->getDropTableSQL($table->getQuotedName($this->platform)); } return $sql; } private function initializeQueries(): void { $this->constraints = new SplObjectStorage(); $this->sequences = new SplObjectStorage(); $this->tables = new SplObjectStorage(); } } Schema/Visitor/RemoveNamespacedAssets.php 0000755 00000005372 00000000000 0014470 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\Deprecations\Deprecation; /** * Removes assets from a schema that are not in the default namespace. * * Some databases such as MySQL support cross databases joins, but don't * allow to call DDLs to a database from another connected database. * Before a schema is serialized into SQL this visitor can cleanup schemas with * non default namespaces. * * This visitor filters all these non-default namespaced tables and sequences * and removes them from the Schema instance. * * @deprecated Do not use namespaces if the target database platform doesn't support them. */ class RemoveNamespacedAssets extends AbstractVisitor { private ?Schema $schema = null; public function __construct() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5432', 'RemoveNamespacedAssets is deprecated. Do not use namespaces' . " if the target database platform doesn't support them.", ); } /** * {@inheritDoc} */ public function acceptSchema(Schema $schema) { $this->schema = $schema; } /** * {@inheritDoc} */ public function acceptTable(Table $table) { if ($this->schema === null) { return; } if ($table->isInDefaultNamespace($this->schema->getName())) { return; } $this->schema->dropTable($table->getName()); } /** * {@inheritDoc} */ public function acceptSequence(Sequence $sequence) { if ($this->schema === null) { return; } if ($sequence->isInDefaultNamespace($this->schema->getName())) { return; } $this->schema->dropSequence($sequence->getName()); } /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { if ($this->schema === null) { return; } // The table may already be deleted in a previous // RemoveNamespacedAssets#acceptTable call. Removing Foreign keys that // point to nowhere. if (! $this->schema->hasTable($fkConstraint->getForeignTableName())) { $localTable->removeForeignKey($fkConstraint->getName()); return; } $foreignTable = $this->schema->getTable($fkConstraint->getForeignTableName()); if ($foreignTable->isInDefaultNamespace($this->schema->getName())) { return; } $localTable->removeForeignKey($fkConstraint->getName()); } } Schema/Visitor/AbstractVisitor.php 0000755 00000001671 00000000000 0013210 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; /** * Abstract Visitor with empty methods for easy extension. * * @deprecated */ class AbstractVisitor implements Visitor, NamespaceVisitor { public function acceptSchema(Schema $schema) { } /** * {@inheritDoc} */ public function acceptNamespace($namespaceName) { } public function acceptTable(Table $table) { } public function acceptColumn(Table $table, Column $column) { } public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { } public function acceptIndex(Table $table, Index $index) { } public function acceptSequence(Sequence $sequence) { } } Schema/DB2SchemaManager.php 0000755 00000031046 00000000000 0011410 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use function array_change_key_case; use function implode; use function preg_match; use function str_replace; use function strpos; use function strtolower; use function strtoupper; use function substr; use const CASE_LOWER; /** * IBM Db2 Schema Manager. * * @extends AbstractSchemaManager<DB2Platform> */ class DB2SchemaManager extends AbstractSchemaManager { /** * {@inheritDoc} */ public function listTableNames() { return $this->doListTableNames(); } /** * {@inheritDoc} */ public function listTables() { return $this->doListTables(); } /** * {@inheritDoc} * * @deprecated Use {@see introspectTable()} instead. */ public function listTableDetails($name) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5595', '%s is deprecated. Use introspectTable() instead.', __METHOD__, ); return $this->doListTableDetails($name); } /** * {@inheritDoc} */ public function listTableColumns($table, $database = null) { return $this->doListTableColumns($table, $database); } /** * {@inheritDoc} */ public function listTableIndexes($table) { return $this->doListTableIndexes($table); } /** * {@inheritDoc} */ public function listTableForeignKeys($table, $database = null) { return $this->doListTableForeignKeys($table, $database); } /** * {@inheritDoc} * * @throws Exception */ protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); $length = null; $fixed = null; $scale = false; $precision = false; $default = null; if ($tableColumn['default'] !== null && $tableColumn['default'] !== 'NULL') { $default = $tableColumn['default']; if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) { $default = str_replace("''", "'", $matches[1]); } } $type = $this->_platform->getDoctrineTypeMapping($tableColumn['typename']); if (isset($tableColumn['comment'])) { $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); } switch (strtolower($tableColumn['typename'])) { case 'varchar': if ($tableColumn['codepage'] === 0) { $type = Types::BINARY; } $length = $tableColumn['length']; $fixed = false; break; case 'character': if ($tableColumn['codepage'] === 0) { $type = Types::BINARY; } $length = $tableColumn['length']; $fixed = true; break; case 'clob': $length = $tableColumn['length']; break; case 'decimal': case 'double': case 'real': $scale = $tableColumn['scale']; $precision = $tableColumn['length']; break; } $options = [ 'length' => $length, 'fixed' => (bool) $fixed, 'default' => $default, 'autoincrement' => (bool) $tableColumn['autoincrement'], 'notnull' => $tableColumn['nulls'] === 'N', 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, ]; if ($scale !== null && $precision !== null) { $options['scale'] = $scale; $options['precision'] = $precision; } return new Column($tableColumn['colname'], Type::getType($type), $options); } /** * {@inheritDoc} */ protected function _getPortableTableDefinition($table) { $table = array_change_key_case($table, CASE_LOWER); return $table['name']; } /** * {@inheritDoc} */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { foreach ($tableIndexes as &$tableIndexRow) { $tableIndexRow = array_change_key_case($tableIndexRow, CASE_LOWER); $tableIndexRow['primary'] = (bool) $tableIndexRow['primary']; } return parent::_getPortableTableIndexesList($tableIndexes, $tableName); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { return new ForeignKeyConstraint( $tableForeignKey['local_columns'], $tableForeignKey['foreign_table'], $tableForeignKey['foreign_columns'], $tableForeignKey['name'], $tableForeignKey['options'], ); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $foreignKeys = []; foreach ($tableForeignKeys as $tableForeignKey) { $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); if (! isset($foreignKeys[$tableForeignKey['index_name']])) { $foreignKeys[$tableForeignKey['index_name']] = [ 'local_columns' => [$tableForeignKey['local_column']], 'foreign_table' => $tableForeignKey['foreign_table'], 'foreign_columns' => [$tableForeignKey['foreign_column']], 'name' => $tableForeignKey['index_name'], 'options' => [ 'onUpdate' => $tableForeignKey['on_update'], 'onDelete' => $tableForeignKey['on_delete'], ], ]; } else { $foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column']; $foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column']; } } return parent::_getPortableTableForeignKeysList($foreignKeys); } /** * @param string $def * * @return string|null */ protected function _getPortableForeignKeyRuleDef($def) { if ($def === 'C') { return 'CASCADE'; } if ($def === 'N') { return 'SET NULL'; } return null; } /** * {@inheritDoc} */ protected function _getPortableViewDefinition($view) { $view = array_change_key_case($view, CASE_LOWER); $sql = ''; $pos = strpos($view['text'], ' AS '); if ($pos !== false) { $sql = substr($view['text'], $pos + 4); } return new View($view['name'], $sql); } protected function normalizeName(string $name): string { $identifier = new Identifier($name); return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name); } protected function selectTableNames(string $databaseName): Result { $sql = <<<'SQL' SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T' AND CREATOR = ? SQL; return $this->_conn->executeQuery($sql, [$databaseName]); } protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' C.TABNAME AS NAME,'; } $sql .= <<<'SQL' C.COLNAME, C.TYPENAME, C.CODEPAGE, C.NULLS, C.LENGTH, C.SCALE, C.REMARKS AS COMMENT, CASE WHEN C.GENERATED = 'D' THEN 1 ELSE 0 END AS AUTOINCREMENT, C.DEFAULT FROM SYSCAT.COLUMNS C JOIN SYSCAT.TABLES AS T ON T.TABSCHEMA = C.TABSCHEMA AND T.TABNAME = C.TABNAME SQL; $conditions = ['C.TABSCHEMA = ?', "T.TYPE = 'T'"]; $params = [$databaseName]; if ($tableName !== null) { $conditions[] = 'C.TABNAME = ?'; $params[] = $tableName; } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.TABNAME, C.COLNO'; return $this->_conn->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' IDX.TABNAME AS NAME,'; } $sql .= <<<'SQL' IDX.INDNAME AS KEY_NAME, IDXCOL.COLNAME AS COLUMN_NAME, CASE WHEN IDX.UNIQUERULE = 'P' THEN 1 ELSE 0 END AS PRIMARY, CASE WHEN IDX.UNIQUERULE = 'D' THEN 1 ELSE 0 END AS NON_UNIQUE FROM SYSCAT.INDEXES AS IDX JOIN SYSCAT.TABLES AS T ON IDX.TABSCHEMA = T.TABSCHEMA AND IDX.TABNAME = T.TABNAME JOIN SYSCAT.INDEXCOLUSE AS IDXCOL ON IDX.INDSCHEMA = IDXCOL.INDSCHEMA AND IDX.INDNAME = IDXCOL.INDNAME SQL; $conditions = ['IDX.TABSCHEMA = ?', "T.TYPE = 'T'"]; $params = [$databaseName]; if ($tableName !== null) { $conditions[] = 'IDX.TABNAME = ?'; $params[] = $tableName; } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IDX.INDNAME, IDXCOL.COLSEQ'; return $this->_conn->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' R.TABNAME AS NAME,'; } $sql .= <<<'SQL' FKCOL.COLNAME AS LOCAL_COLUMN, R.REFTABNAME AS FOREIGN_TABLE, PKCOL.COLNAME AS FOREIGN_COLUMN, R.CONSTNAME AS INDEX_NAME, CASE WHEN R.UPDATERULE = 'R' THEN 'RESTRICT' END AS ON_UPDATE, CASE WHEN R.DELETERULE = 'C' THEN 'CASCADE' WHEN R.DELETERULE = 'N' THEN 'SET NULL' WHEN R.DELETERULE = 'R' THEN 'RESTRICT' END AS ON_DELETE FROM SYSCAT.REFERENCES AS R JOIN SYSCAT.TABLES AS T ON T.TABSCHEMA = R.TABSCHEMA AND T.TABNAME = R.TABNAME JOIN SYSCAT.KEYCOLUSE AS FKCOL ON FKCOL.CONSTNAME = R.CONSTNAME AND FKCOL.TABSCHEMA = R.TABSCHEMA AND FKCOL.TABNAME = R.TABNAME JOIN SYSCAT.KEYCOLUSE AS PKCOL ON PKCOL.CONSTNAME = R.REFKEYNAME AND PKCOL.TABSCHEMA = R.REFTABSCHEMA AND PKCOL.TABNAME = R.REFTABNAME AND PKCOL.COLSEQ = FKCOL.COLSEQ SQL; $conditions = ['R.TABSCHEMA = ?', "T.TYPE = 'T'"]; $params = [$databaseName]; if ($tableName !== null) { $conditions[] = 'R.TABNAME = ?'; $params[] = $tableName; } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY R.CONSTNAME, FKCOL.COLSEQ'; return $this->_conn->executeQuery($sql, $params); } /** * {@inheritDoc} */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { $sql = 'SELECT NAME, REMARKS'; $conditions = []; $params = []; if ($tableName !== null) { $conditions[] = 'NAME = ?'; $params[] = $tableName; } $sql .= ' FROM SYSIBM.SYSTABLES'; if ($conditions !== []) { $sql .= ' WHERE ' . implode(' AND ', $conditions); } /** @var array<string,array<string,mixed>> $metadata */ $metadata = $this->_conn->executeQuery($sql, $params) ->fetchAllAssociativeIndexed(); $tableOptions = []; foreach ($metadata as $table => $data) { $data = array_change_key_case($data, CASE_LOWER); $tableOptions[$table] = ['comment' => $data['remarks']]; } return $tableOptions; } } Schema/SqliteSchemaManager.php 0000755 00000055005 00000000000 0012303 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\SQLite; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\StringType; use Doctrine\DBAL\Types\TextType; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_change_key_case; use function array_map; use function array_merge; use function count; use function explode; use function file_exists; use function implode; use function preg_match; use function preg_match_all; use function preg_quote; use function preg_replace; use function rtrim; use function str_replace; use function strcasecmp; use function strpos; use function strtolower; use function trim; use function unlink; use function usort; use const CASE_LOWER; /** * Sqlite SchemaManager. * * @extends AbstractSchemaManager<SqlitePlatform> */ class SqliteSchemaManager extends AbstractSchemaManager { /** * {@inheritDoc} */ public function listTableNames() { return $this->doListTableNames(); } /** * {@inheritDoc} */ public function listTables() { return $this->doListTables(); } /** * {@inheritDoc} * * @deprecated Use {@see introspectTable()} instead. */ public function listTableDetails($name) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5595', '%s is deprecated. Use introspectTable() instead.', __METHOD__, ); return $this->doListTableDetails($name); } /** * {@inheritDoc} */ public function listTableColumns($table, $database = null) { return $this->doListTableColumns($table, $database); } /** * {@inheritDoc} */ public function listTableIndexes($table) { return $this->doListTableIndexes($table); } /** * {@inheritDoc} */ protected function fetchForeignKeyColumnsByTable(string $databaseName): array { $columnsByTable = parent::fetchForeignKeyColumnsByTable($databaseName); if (count($columnsByTable) > 0) { foreach ($columnsByTable as $table => $columns) { $columnsByTable[$table] = $this->addDetailsToTableForeignKeyColumns($table, $columns); } } return $columnsByTable; } /** * {@inheritDoc} * * @deprecated Delete the database file using the filesystem. */ public function dropDatabase($database) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4963', 'SqliteSchemaManager::dropDatabase() is deprecated. Delete the database file using the filesystem.', ); if (! file_exists($database)) { return; } unlink($database); } /** * {@inheritDoc} * * @deprecated The engine will create the database file automatically. */ public function createDatabase($database) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4963', 'SqliteSchemaManager::createDatabase() is deprecated.' . ' The engine will create the database file automatically.', ); $params = $this->_conn->getParams(); $params['path'] = $database; unset($params['memory']); $conn = DriverManager::getConnection($params); $conn->connect(); $conn->close(); } /** * {@inheritDoc} */ public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) { if (! $table instanceof Table) { $table = $this->listTableDetails($table); } $this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [$foreignKey])); } /** * {@inheritDoc} * * @deprecated Use {@see dropForeignKey()} and {@see createForeignKey()} instead. */ public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'SqliteSchemaManager::dropAndCreateForeignKey() is deprecated.' . ' Use SqliteSchemaManager::dropForeignKey() and SqliteSchemaManager::createForeignKey() instead.', ); if (! $table instanceof Table) { $table = $this->listTableDetails($table); } $this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [], [$foreignKey])); } /** * {@inheritDoc} */ public function dropForeignKey($foreignKey, $table) { if (! $table instanceof Table) { $table = $this->listTableDetails($table); } $this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [], [], [$foreignKey])); } /** * {@inheritDoc} */ public function listTableForeignKeys($table, $database = null) { $table = $this->normalizeName($table); $columns = $this->selectForeignKeyColumns($database ?? 'main', $table) ->fetchAllAssociative(); if (count($columns) > 0) { $columns = $this->addDetailsToTableForeignKeyColumns($table, $columns); } return $this->_getPortableTableForeignKeysList($columns); } /** * {@inheritDoc} */ protected function _getPortableTableDefinition($table) { return $table['table_name']; } /** * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { $indexBuffer = []; // fetch primary $indexArray = $this->_conn->fetchAllAssociative('SELECT * FROM PRAGMA_TABLE_INFO (?)', [$tableName]); usort( $indexArray, /** * @param array<string,mixed> $a * @param array<string,mixed> $b */ static function (array $a, array $b): int { if ($a['pk'] === $b['pk']) { return $a['cid'] - $b['cid']; } return $a['pk'] - $b['pk']; }, ); foreach ($indexArray as $indexColumnRow) { if ($indexColumnRow['pk'] === 0 || $indexColumnRow['pk'] === '0') { continue; } $indexBuffer[] = [ 'key_name' => 'primary', 'primary' => true, 'non_unique' => false, 'column_name' => $indexColumnRow['name'], ]; } // fetch regular indexes foreach ($tableIndexes as $tableIndex) { // Ignore indexes with reserved names, e.g. autoindexes if (strpos($tableIndex['name'], 'sqlite_') === 0) { continue; } $keyName = $tableIndex['name']; $idx = []; $idx['key_name'] = $keyName; $idx['primary'] = false; $idx['non_unique'] = ! $tableIndex['unique']; $indexArray = $this->_conn->fetchAllAssociative('SELECT * FROM PRAGMA_INDEX_INFO (?)', [$keyName]); foreach ($indexArray as $indexColumnRow) { $idx['column_name'] = $indexColumnRow['name']; $indexBuffer[] = $idx; } } return parent::_getPortableTableIndexesList($indexBuffer, $tableName); } /** * {@inheritDoc} */ protected function _getPortableTableColumnList($table, $database, $tableColumns) { $list = parent::_getPortableTableColumnList($table, $database, $tableColumns); // find column with autoincrement $autoincrementColumn = null; $autoincrementCount = 0; foreach ($tableColumns as $tableColumn) { if ($tableColumn['pk'] === 0 || $tableColumn['pk'] === '0') { continue; } $autoincrementCount++; if ($autoincrementColumn !== null || strtolower($tableColumn['type']) !== 'integer') { continue; } $autoincrementColumn = $tableColumn['name']; } if ($autoincrementCount === 1 && $autoincrementColumn !== null) { foreach ($list as $column) { if ($autoincrementColumn !== $column->getName()) { continue; } $column->setAutoincrement(true); } } // inspect column collation and comments $createSql = $this->getCreateTableSQL($table); foreach ($list as $columnName => $column) { $type = $column->getType(); if ($type instanceof StringType || $type instanceof TextType) { $column->setPlatformOption( 'collation', $this->parseColumnCollationFromSQL($columnName, $createSql) ?? 'BINARY', ); } $comment = $this->parseColumnCommentFromSQL($columnName, $createSql); if ($comment === null) { continue; } $type = $this->extractDoctrineTypeFromComment($comment, ''); if ($type !== '') { $column->setType(Type::getType($type)); $comment = $this->removeDoctrineTypeFromComment($comment, $type); } $column->setComment($comment); } return $list; } /** * {@inheritDoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $parts = explode('(', $tableColumn['type']); $tableColumn['type'] = trim($parts[0]); if (isset($parts[1])) { $length = trim($parts[1], ')'); $tableColumn['length'] = $length; } $dbType = strtolower($tableColumn['type']); $length = $tableColumn['length'] ?? null; $unsigned = false; if (strpos($dbType, ' unsigned') !== false) { $dbType = str_replace(' unsigned', '', $dbType); $unsigned = true; } $fixed = false; $type = $this->_platform->getDoctrineTypeMapping($dbType); $default = $tableColumn['dflt_value']; if ($default === 'NULL') { $default = null; } if ($default !== null) { // SQLite returns the default value as a literal expression, so we need to parse it if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) { $default = str_replace("''", "'", $matches[1]); } } $notnull = (bool) $tableColumn['notnull']; if (! isset($tableColumn['name'])) { $tableColumn['name'] = ''; } $precision = null; $scale = null; switch ($dbType) { case 'char': $fixed = true; break; case 'float': case 'double': case 'real': case 'decimal': case 'numeric': if (isset($tableColumn['length'])) { if (strpos($tableColumn['length'], ',') === false) { $tableColumn['length'] .= ',0'; } [$precision, $scale] = array_map('trim', explode(',', $tableColumn['length'])); } $length = null; break; } $options = [ 'length' => $length, 'unsigned' => $unsigned, 'fixed' => $fixed, 'notnull' => $notnull, 'default' => $default, 'precision' => $precision, 'scale' => $scale, ]; return new Column($tableColumn['name'], Type::getType($type), $options); } /** * {@inheritDoc} */ protected function _getPortableViewDefinition($view) { return new View($view['name'], $view['sql']); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $list = []; foreach ($tableForeignKeys as $value) { $value = array_change_key_case($value, CASE_LOWER); $id = $value['id']; if (! isset($list[$id])) { if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') { $value['on_delete'] = null; } if (! isset($value['on_update']) || $value['on_update'] === 'RESTRICT') { $value['on_update'] = null; } $list[$id] = [ 'name' => $value['constraint_name'], 'local' => [], 'foreign' => [], 'foreignTable' => $value['table'], 'onDelete' => $value['on_delete'], 'onUpdate' => $value['on_update'], 'deferrable' => $value['deferrable'], 'deferred' => $value['deferred'], ]; } $list[$id]['local'][] = $value['from']; if ($value['to'] === null) { // Inferring a shorthand form for the foreign key constraint, where the "to" field is empty. // @see https://www.sqlite.org/foreignkeys.html#fk_indexes. $foreignTableIndexes = $this->_getPortableTableIndexesList([], $value['table']); if (! isset($foreignTableIndexes['primary'])) { continue; } $list[$id]['foreign'] = [...$list[$id]['foreign'], ...$foreignTableIndexes['primary']->getColumns()]; continue; } $list[$id]['foreign'][] = $value['to']; } return parent::_getPortableTableForeignKeysList($list); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint { return new ForeignKeyConstraint( $tableForeignKey['local'], $tableForeignKey['foreignTable'], $tableForeignKey['foreign'], $tableForeignKey['name'], [ 'onDelete' => $tableForeignKey['onDelete'], 'onUpdate' => $tableForeignKey['onUpdate'], 'deferrable' => $tableForeignKey['deferrable'], 'deferred' => $tableForeignKey['deferred'], ], ); } private function parseColumnCollationFromSQL(string $column, string $sql): ?string { $pattern = '{(?:\W' . preg_quote($column) . '\W|\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is'; if (preg_match($pattern, $sql, $match) !== 1) { return null; } return $match[1]; } private function parseTableCommentFromSQL(string $table, string $sql): ?string { $pattern = '/\s* # Allow whitespace characters at start of line CREATE\sTABLE # Match "CREATE TABLE" (?:\W"' . preg_quote($this->_platform->quoteSingleIdentifier($table), '/') . '"\W|\W' . preg_quote($table, '/') . '\W) # Match table name (quoted and unquoted) ( # Start capture (?:\s*--[^\n]*\n?)+ # Capture anything that starts with whitespaces followed by -- until the end of the line(s) )/ix'; if (preg_match($pattern, $sql, $match) !== 1) { return null; } $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n")); return $comment === '' ? null : $comment; } private function parseColumnCommentFromSQL(string $column, string $sql): ?string { $pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) . '\W|\W' . preg_quote($column) . '\W)(?:\([^)]*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i'; if (preg_match($pattern, $sql, $match) !== 1) { return null; } $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n")); return $comment === '' ? null : $comment; } /** @throws Exception */ private function getCreateTableSQL(string $table): string { $sql = $this->_conn->fetchOne( <<<'SQL' SELECT sql FROM ( SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master ) WHERE type = 'table' AND name = ? SQL , [$table], ); if ($sql !== false) { return $sql; } return ''; } /** * @param list<array<string,mixed>> $columns * * @return list<array<string,mixed>> * * @throws Exception */ private function addDetailsToTableForeignKeyColumns(string $table, array $columns): array { $foreignKeyDetails = $this->getForeignKeyDetails($table); $foreignKeyCount = count($foreignKeyDetails); foreach ($columns as $i => $column) { // SQLite identifies foreign keys in reverse order of appearance in SQL $columns[$i] = array_merge($column, $foreignKeyDetails[$foreignKeyCount - $column['id'] - 1]); } return $columns; } /** * @param string $table * * @return list<array<string, mixed>> * * @throws Exception */ private function getForeignKeyDetails($table) { $createSql = $this->getCreateTableSQL($table); if ( preg_match_all( '# (?:CONSTRAINT\s+(\S+)\s+)? (?:FOREIGN\s+KEY[^)]+\)\s*)? REFERENCES\s+\S+\s*(?:\([^)]+\))? (?: [^,]*? (NOT\s+DEFERRABLE|DEFERRABLE) (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))? )?#isx', $createSql, $match, ) === 0 ) { return []; } $names = $match[1]; $deferrable = $match[2]; $deferred = $match[3]; $details = []; for ($i = 0, $count = count($match[0]); $i < $count; $i++) { $details[] = [ 'constraint_name' => isset($names[$i]) && $names[$i] !== '' ? $names[$i] : null, 'deferrable' => isset($deferrable[$i]) && strcasecmp($deferrable[$i], 'deferrable') === 0, 'deferred' => isset($deferred[$i]) && strcasecmp($deferred[$i], 'deferred') === 0, ]; } return $details; } public function createComparator(): Comparator { return new SQLite\Comparator($this->_platform); } /** * {@inheritDoc} * * @deprecated */ public function getSchemaSearchPaths() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4821', 'SqliteSchemaManager::getSchemaSearchPaths() is deprecated.', ); // SQLite does not support schemas or databases return []; } protected function selectTableNames(string $databaseName): Result { $sql = <<<'SQL' SELECT name AS table_name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' UNION ALL SELECT name FROM sqlite_temp_master WHERE type = 'table' ORDER BY name SQL; return $this->_conn->executeQuery($sql); } protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { $sql = <<<'SQL' SELECT t.name AS table_name, c.* FROM sqlite_master t JOIN pragma_table_info(t.name) c SQL; $conditions = [ "t.type = 'table'", "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", ]; $params = []; if ($tableName !== null) { $conditions[] = 't.name = ?'; $params[] = str_replace('.', '__', $tableName); } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, c.cid'; return $this->_conn->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { $sql = <<<'SQL' SELECT t.name AS table_name, i.* FROM sqlite_master t JOIN pragma_index_list(t.name) i SQL; $conditions = [ "t.type = 'table'", "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", ]; $params = []; if ($tableName !== null) { $conditions[] = 't.name = ?'; $params[] = str_replace('.', '__', $tableName); } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, i.seq'; return $this->_conn->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { $sql = <<<'SQL' SELECT t.name AS table_name, p.* FROM sqlite_master t JOIN pragma_foreign_key_list(t.name) p ON p."seq" != '-1' SQL; $conditions = [ "t.type = 'table'", "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", ]; $params = []; if ($tableName !== null) { $conditions[] = 't.name = ?'; $params[] = str_replace('.', '__', $tableName); } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, p.id DESC, p.seq'; return $this->_conn->executeQuery($sql, $params); } /** * {@inheritDoc} */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { if ($tableName === null) { $tables = $this->listTableNames(); } else { $tables = [$tableName]; } $tableOptions = []; foreach ($tables as $table) { $comment = $this->parseTableCommentFromSQL($table, $this->getCreateTableSQL($table)); if ($comment === null) { continue; } $tableOptions[$table]['comment'] = $comment; } return $tableOptions; } } Schema/View.php 0000755 00000000675 00000000000 0007343 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; /** * Representation of a Database View. */ class View extends AbstractAsset { /** @var string */ private $sql; /** * @param string $name * @param string $sql */ public function __construct($name, $sql) { $this->_setName($name); $this->sql = $sql; } /** @return string */ public function getSql() { return $this->sql; } } Schema/Identifier.php 0000755 00000001232 00000000000 0010501 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; /** * An abstraction class for an asset identifier. * * Wraps identifier names like column names in indexes / foreign keys * in an abstract class for proper quotation capabilities. */ class Identifier extends AbstractAsset { /** * @param string $identifier Identifier name to wrap. * @param bool $quote Whether to force quoting the given identifier. */ public function __construct($identifier, $quote = false) { $this->_setName($identifier); if (! $quote || $this->_quoted) { return; } $this->_setName('"' . $this->getName() . '"'); } } Schema/UniqueConstraint.php 0000755 00000006307 00000000000 0011742 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use function array_keys; use function array_map; use function strtolower; /** * Class for a unique constraint. */ class UniqueConstraint extends AbstractAsset implements Constraint { /** * Asset identifier instances of the column names the unique constraint is associated with. * array($columnName => Identifier) * * @var Identifier[] */ protected $columns = []; /** * Platform specific flags. * array($flagName => true) * * @var true[] */ protected $flags = []; /** * Platform specific options. * * @var mixed[] */ private array $options; /** * @param string[] $columns * @param string[] $flags * @param mixed[] $options */ public function __construct(string $name, array $columns, array $flags = [], array $options = []) { $this->_setName($name); $this->options = $options; foreach ($columns as $column) { $this->addColumn($column); } foreach ($flags as $flag) { $this->addFlag($flag); } } /** * {@inheritDoc} */ public function getColumns() { return array_keys($this->columns); } /** * {@inheritDoc} */ public function getQuotedColumns(AbstractPlatform $platform) { $columns = []; foreach ($this->columns as $column) { $columns[] = $column->getQuotedName($platform); } return $columns; } /** @return string[] */ public function getUnquotedColumns(): array { return array_map([$this, 'trimQuotes'], $this->getColumns()); } /** * Returns platform specific flags for unique constraint. * * @return string[] */ public function getFlags(): array { return array_keys($this->flags); } /** * Adds flag for a unique constraint that translates to platform specific handling. * * @return $this * * @example $uniqueConstraint->addFlag('CLUSTERED') */ public function addFlag(string $flag): UniqueConstraint { $this->flags[strtolower($flag)] = true; return $this; } /** * Does this unique constraint have a specific flag? */ public function hasFlag(string $flag): bool { return isset($this->flags[strtolower($flag)]); } /** * Removes a flag. */ public function removeFlag(string $flag): void { unset($this->flags[strtolower($flag)]); } /** * Does this unique constraint have a specific option? */ public function hasOption(string $name): bool { return isset($this->options[strtolower($name)]); } /** @return mixed */ public function getOption(string $name) { return $this->options[strtolower($name)]; } /** @return mixed[] */ public function getOptions(): array { return $this->options; } /** * Adds a new column to the unique constraint. */ protected function addColumn(string $column): void { $this->columns[$column] = new Identifier($column); } } Schema/AbstractSchemaManager.php 0000755 00000144225 00000000000 0012610 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs; use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\DatabaseRequired; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Result; use Doctrine\Deprecations\Deprecation; use Throwable; use function array_filter; use function array_intersect; use function array_map; use function array_values; use function assert; use function call_user_func_array; use function count; use function func_get_args; use function is_callable; use function is_string; use function preg_match; use function str_replace; use function strtolower; /** * Base class for schema managers. Schema managers are used to inspect and/or * modify the database schema/structure. * * @template-covariant T of AbstractPlatform */ abstract class AbstractSchemaManager { /** * Holds instance of the Doctrine connection for this schema manager. * * @var Connection */ protected $_conn; /** * Holds instance of the database platform used for this schema manager. * * @var T */ protected $_platform; /** @param T $platform */ public function __construct(Connection $connection, AbstractPlatform $platform) { $this->_conn = $connection; $this->_platform = $platform; } /** * Returns the associated platform. * * @deprecated Use {@link Connection::getDatabasePlatform()} instead. * * @return T */ public function getDatabasePlatform() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5387', 'AbstractSchemaManager::getDatabasePlatform() is deprecated.' . ' Use Connection::getDatabasePlatform() instead.', ); return $this->_platform; } /** * Tries any method on the schema manager. Normally a method throws an * exception when your DBMS doesn't support it or if an error occurs. * This method allows you to try and method on your SchemaManager * instance and will return false if it does not work or is not supported. * * <code> * $result = $sm->tryMethod('dropView', 'view_name'); * </code> * * @deprecated * * @return mixed */ public function tryMethod() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::tryMethod() is deprecated.', ); $args = func_get_args(); $method = $args[0]; unset($args[0]); $args = array_values($args); $callback = [$this, $method]; assert(is_callable($callback)); try { return call_user_func_array($callback, $args); } catch (Throwable $e) { return false; } } /** * Lists the available databases for this connection. * * @return string[] * * @throws Exception */ public function listDatabases() { $sql = $this->_platform->getListDatabasesSQL(); $databases = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableDatabasesList($databases); } /** * Returns a list of all namespaces in the current database. * * @deprecated Use {@see listSchemaNames()} instead. * * @return string[] * * @throws Exception */ public function listNamespaceNames() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'AbstractSchemaManager::listNamespaceNames() is deprecated,' . ' use AbstractSchemaManager::listSchemaNames() instead.', ); $sql = $this->_platform->getListNamespacesSQL(); $namespaces = $this->_conn->fetchAllAssociative($sql); return $this->getPortableNamespacesList($namespaces); } /** * Returns a list of the names of all schemata in the current database. * * @return list<string> * * @throws Exception */ public function listSchemaNames(): array { throw Exception::notSupported(__METHOD__); } /** * Lists the available sequences for this connection. * * @param string|null $database * * @return Sequence[] * * @throws Exception */ public function listSequences($database = null) { if ($database === null) { $database = $this->getDatabase(__METHOD__); } else { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5284', 'Passing $database to AbstractSchemaManager::listSequences() is deprecated.', ); } $sql = $this->_platform->getListSequencesSQL($database); $sequences = $this->_conn->fetchAllAssociative($sql); return $this->filterAssetNames($this->_getPortableSequencesList($sequences)); } /** * Lists the columns for a given table. * * In contrast to other libraries and to the old version of Doctrine, * this column definition does try to contain the 'primary' column for * the reason that it is not portable across different RDBMS. Use * {@see listTableIndexes($tableName)} to retrieve the primary key * of a table. Where a RDBMS specifies more details, these are held * in the platformDetails array. * * @param string $table The name of the table. * @param string|null $database * * @return Column[] * * @throws Exception */ public function listTableColumns($table, $database = null) { if ($database === null) { $database = $this->getDatabase(__METHOD__); } else { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5284', 'Passing $database to AbstractSchemaManager::listTableColumns() is deprecated.', ); } $sql = $this->_platform->getListTableColumnsSQL($table, $database); $tableColumns = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableTableColumnList($table, $database, $tableColumns); } /** * @param string $table * @param string|null $database * * @return Column[] * * @throws Exception */ protected function doListTableColumns($table, $database = null): array { if ($database === null) { $database = $this->getDatabase(__METHOD__); } else { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5284', 'Passing $database to AbstractSchemaManager::doListTableColumns() is deprecated.', ); } return $this->_getPortableTableColumnList( $table, $database, $this->selectTableColumns($database, $this->normalizeName($table)) ->fetchAllAssociative(), ); } /** * Lists the indexes for a given table returning an array of Index instances. * * Keys of the portable indexes list are all lower-cased. * * @param string $table The name of the table. * * @return Index[] * * @throws Exception */ public function listTableIndexes($table) { $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase()); $tableIndexes = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableTableIndexesList($tableIndexes, $table); } /** * @param string $table * * @return Index[] * * @throws Exception */ protected function doListTableIndexes($table): array { $database = $this->getDatabase(__METHOD__); $table = $this->normalizeName($table); return $this->_getPortableTableIndexesList( $this->selectIndexColumns( $database, $table, )->fetchAllAssociative(), $table, ); } /** * Returns true if all the given tables exist. * * The usage of a string $tableNames is deprecated. Pass a one-element array instead. * * @param string|string[] $names * * @return bool * * @throws Exception */ public function tablesExist($names) { if (is_string($names)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'The usage of a string $tableNames in AbstractSchemaManager::tablesExist() is deprecated. ' . 'Pass a one-element array instead.', ); } $names = array_map('strtolower', (array) $names); return count($names) === count(array_intersect($names, array_map('strtolower', $this->listTableNames()))); } /** * Returns a list of all tables in the current database. * * @return string[] * * @throws Exception */ public function listTableNames() { $sql = $this->_platform->getListTablesSQL(); $tables = $this->_conn->fetchAllAssociative($sql); $tableNames = $this->_getPortableTablesList($tables); return $this->filterAssetNames($tableNames); } /** * @return list<string> * * @throws Exception */ protected function doListTableNames(): array { $database = $this->getDatabase(__METHOD__); return $this->filterAssetNames( $this->_getPortableTablesList( $this->selectTableNames($database) ->fetchAllAssociative(), ), ); } /** * Filters asset names if they are configured to return only a subset of all * the found elements. * * @param mixed[] $assetNames * * @return mixed[] */ protected function filterAssetNames($assetNames) { $filter = $this->_conn->getConfiguration()->getSchemaAssetsFilter(); if ($filter === null) { return $assetNames; } return array_values(array_filter($assetNames, $filter)); } /** * Lists the tables for this connection. * * @return list<Table> * * @throws Exception */ public function listTables() { $tableNames = $this->listTableNames(); $tables = []; foreach ($tableNames as $tableName) { $tables[] = $this->introspectTable($tableName); } return $tables; } /** * @return list<Table> * * @throws Exception */ protected function doListTables(): array { $database = $this->getDatabase(__METHOD__); $tableColumnsByTable = $this->fetchTableColumnsByTable($database); $indexColumnsByTable = $this->fetchIndexColumnsByTable($database); $foreignKeyColumnsByTable = $this->fetchForeignKeyColumnsByTable($database); $tableOptionsByTable = $this->fetchTableOptionsByTable($database); $filter = $this->_conn->getConfiguration()->getSchemaAssetsFilter(); $tables = []; foreach ($tableColumnsByTable as $tableName => $tableColumns) { if ($filter !== null && ! $filter($tableName)) { continue; } $tables[] = new Table( $tableName, $this->_getPortableTableColumnList($tableName, $database, $tableColumns), $this->_getPortableTableIndexesList($indexColumnsByTable[$tableName] ?? [], $tableName), [], $this->_getPortableTableForeignKeysList($foreignKeyColumnsByTable[$tableName] ?? []), $tableOptionsByTable[$tableName] ?? [], ); } return $tables; } /** * @deprecated Use {@see introspectTable()} instead. * * @param string $name * * @return Table * * @throws Exception */ public function listTableDetails($name) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5595', '%s is deprecated. Use introspectTable() instead.', __METHOD__, ); $columns = $this->listTableColumns($name); $foreignKeys = []; if ($this->_platform->supportsForeignKeyConstraints()) { $foreignKeys = $this->listTableForeignKeys($name); } $indexes = $this->listTableIndexes($name); return new Table($name, $columns, $indexes, [], $foreignKeys); } /** * @param string $name * * @throws Exception */ protected function doListTableDetails($name): Table { $database = $this->getDatabase(__METHOD__); $normalizedName = $this->normalizeName($name); $tableOptionsByTable = $this->fetchTableOptionsByTable($database, $normalizedName); if ($this->_platform->supportsForeignKeyConstraints()) { $foreignKeys = $this->listTableForeignKeys($name); } else { $foreignKeys = []; } return new Table( $name, $this->listTableColumns($name, $database), $this->listTableIndexes($name), [], $foreignKeys, $tableOptionsByTable[$normalizedName] ?? [], ); } /** * An extension point for those platforms where case sensitivity of the object name depends on whether it's quoted. * * Such platforms should convert a possibly quoted name into a value of the corresponding case. */ protected function normalizeName(string $name): string { $identifier = new Identifier($name); return $identifier->getName(); } /** * Selects names of tables in the specified database. * * @throws Exception * * @abstract */ protected function selectTableNames(string $databaseName): Result { throw Exception::notSupported(__METHOD__); } /** * Selects definitions of table columns in the specified database. If the table name is specified, narrows down * the selection to this table. * * @throws Exception * * @abstract */ protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { throw Exception::notSupported(__METHOD__); } /** * Selects definitions of index columns in the specified database. If the table name is specified, narrows down * the selection to this table. * * @throws Exception */ protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { throw Exception::notSupported(__METHOD__); } /** * Selects definitions of foreign key columns in the specified database. If the table name is specified, * narrows down the selection to this table. * * @throws Exception */ protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { throw Exception::notSupported(__METHOD__); } /** * Fetches definitions of table columns in the specified database and returns them grouped by table name. * * @return array<string,list<array<string,mixed>>> * * @throws Exception */ protected function fetchTableColumnsByTable(string $databaseName): array { return $this->fetchAllAssociativeGrouped($this->selectTableColumns($databaseName)); } /** * Fetches definitions of index columns in the specified database and returns them grouped by table name. * * @return array<string,list<array<string,mixed>>> * * @throws Exception */ protected function fetchIndexColumnsByTable(string $databaseName): array { return $this->fetchAllAssociativeGrouped($this->selectIndexColumns($databaseName)); } /** * Fetches definitions of foreign key columns in the specified database and returns them grouped by table name. * * @return array<string, list<array<string, mixed>>> * * @throws Exception */ protected function fetchForeignKeyColumnsByTable(string $databaseName): array { if (! $this->_platform->supportsForeignKeyConstraints()) { return []; } return $this->fetchAllAssociativeGrouped( $this->selectForeignKeyColumns($databaseName), ); } /** * Fetches table options for the tables in the specified database and returns them grouped by table name. * If the table name is specified, narrows down the selection to this table. * * @return array<string,array<string,mixed>> * * @throws Exception */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { throw Exception::notSupported(__METHOD__); } /** * Introspects the table with the given name. * * @throws Exception */ public function introspectTable(string $name): Table { $table = $this->listTableDetails($name); if ($table->getColumns() === []) { throw SchemaException::tableDoesNotExist($name); } return $table; } /** * Lists the views this connection has. * * @return View[] * * @throws Exception */ public function listViews() { $database = $this->_conn->getDatabase(); $sql = $this->_platform->getListViewsSQL($database); $views = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableViewsList($views); } /** * Lists the foreign keys for the given table. * * @param string $table The name of the table. * @param string|null $database * * @return ForeignKeyConstraint[] * * @throws Exception */ public function listTableForeignKeys($table, $database = null) { if ($database === null) { $database = $this->getDatabase(__METHOD__); } else { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5284', 'Passing $database to AbstractSchemaManager::listTableForeignKeys() is deprecated.', ); } $sql = $this->_platform->getListTableForeignKeysSQL($table, $database); $tableForeignKeys = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableTableForeignKeysList($tableForeignKeys); } /** * @param string $table * @param string|null $database * * @return ForeignKeyConstraint[] * * @throws Exception */ protected function doListTableForeignKeys($table, $database = null): array { if ($database === null) { $database = $this->getDatabase(__METHOD__); } else { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5284', 'Passing $database to AbstractSchemaManager::listTableForeignKeys() is deprecated.', ); } return $this->_getPortableTableForeignKeysList( $this->selectForeignKeyColumns( $database, $this->normalizeName($table), )->fetchAllAssociative(), ); } /* drop*() Methods */ /** * Drops a database. * * NOTE: You can not drop the database this SchemaManager is currently connected to. * * @param string $database The name of the database to drop. * * @return void * * @throws Exception */ public function dropDatabase($database) { $this->_conn->executeStatement( $this->_platform->getDropDatabaseSQL($database), ); } /** * Drops a schema. * * @throws Exception */ public function dropSchema(string $schemaName): void { $this->_conn->executeStatement( $this->_platform->getDropSchemaSQL($schemaName), ); } /** * Drops the given table. * * @param string $name The name of the table to drop. * * @return void * * @throws Exception */ public function dropTable($name) { $this->_conn->executeStatement( $this->_platform->getDropTableSQL($name), ); } /** * Drops the index from the given table. * * @param Index|string $index The name of the index. * @param Table|string $table The name of the table. * * @return void * * @throws Exception */ public function dropIndex($index, $table) { if ($index instanceof Index) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $index = $index->getQuotedName($this->_platform); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as an Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this->_platform); } $this->_conn->executeStatement( $this->_platform->getDropIndexSQL($index, $table), ); } /** * Drops the constraint from the given table. * * @deprecated Use {@see dropIndex()}, {@see dropForeignKey()} or {@see dropUniqueConstraint()} instead. * * @param Table|string $table The name of the table. * * @return void * * @throws Exception */ public function dropConstraint(Constraint $constraint, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this->_platform); } $this->_conn->executeStatement($this->_platform->getDropConstraintSQL( $constraint->getQuotedName($this->_platform), $table, )); } /** * Drops a foreign key from a table. * * @param ForeignKeyConstraint|string $foreignKey The name of the foreign key. * @param Table|string $table The name of the table with the foreign key. * * @return void * * @throws Exception */ public function dropForeignKey($foreignKey, $table) { if ($foreignKey instanceof ForeignKeyConstraint) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $foreignKey as a ForeignKeyConstraint object to %s is deprecated.' . ' Pass it as a quoted name instead.', __METHOD__, ); $foreignKey = $foreignKey->getQuotedName($this->_platform); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this->_platform); } $this->_conn->executeStatement( $this->_platform->getDropForeignKeySQL($foreignKey, $table), ); } /** * Drops a sequence with a given name. * * @param string $name The name of the sequence to drop. * * @return void * * @throws Exception */ public function dropSequence($name) { $this->_conn->executeStatement( $this->_platform->getDropSequenceSQL($name), ); } /** * Drops the unique constraint from the given table. * * @throws Exception */ public function dropUniqueConstraint(string $name, string $tableName): void { $this->_conn->executeStatement( $this->_platform->getDropUniqueConstraintSQL($name, $tableName), ); } /** * Drops a view. * * @param string $name The name of the view. * * @return void * * @throws Exception */ public function dropView($name) { $this->_conn->executeStatement( $this->_platform->getDropViewSQL($name), ); } /* create*() Methods */ /** @throws Exception */ public function createSchemaObjects(Schema $schema): void { $this->_execSql($schema->toSql($this->_platform)); } /** * Creates a new database. * * @param string $database The name of the database to create. * * @return void * * @throws Exception */ public function createDatabase($database) { $this->_conn->executeStatement( $this->_platform->getCreateDatabaseSQL($database), ); } /** * Creates a new table. * * @return void * * @throws Exception */ public function createTable(Table $table) { $createFlags = AbstractPlatform::CREATE_INDEXES | AbstractPlatform::CREATE_FOREIGNKEYS; $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags)); } /** * Creates a new sequence. * * @param Sequence $sequence * * @return void * * @throws Exception */ public function createSequence($sequence) { $this->_conn->executeStatement( $this->_platform->getCreateSequenceSQL($sequence), ); } /** * Creates a constraint on a table. * * @deprecated Use {@see createIndex()}, {@see createForeignKey()} or {@see createUniqueConstraint()} instead. * * @param Table|string $table * * @return void * * @throws Exception */ public function createConstraint(Constraint $constraint, $table) { $this->_conn->executeStatement( $this->_platform->getCreateConstraintSQL($constraint, $table), ); } /** * Creates a new index on a table. * * @param Table|string $table The name of the table on which the index is to be created. * * @return void * * @throws Exception */ public function createIndex(Index $index, $table) { $this->_conn->executeStatement( $this->_platform->getCreateIndexSQL($index, $table), ); } /** * Creates a new foreign key. * * @param ForeignKeyConstraint $foreignKey The ForeignKey instance. * @param Table|string $table The name of the table on which the foreign key is to be created. * * @return void * * @throws Exception */ public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) { $this->_conn->executeStatement( $this->_platform->getCreateForeignKeySQL($foreignKey, $table), ); } /** * Creates a unique constraint on a table. * * @throws Exception */ public function createUniqueConstraint(UniqueConstraint $uniqueConstraint, string $tableName): void { $this->_conn->executeStatement( $this->_platform->getCreateUniqueConstraintSQL($uniqueConstraint, $tableName), ); } /** * Creates a new view. * * @return void * * @throws Exception */ public function createView(View $view) { $this->_conn->executeStatement( $this->_platform->getCreateViewSQL( $view->getQuotedName($this->_platform), $view->getSql(), ), ); } /* dropAndCreate*() Methods */ /** @throws Exception */ public function dropSchemaObjects(Schema $schema): void { $this->_execSql($schema->toDropSql($this->_platform)); } /** * Drops and creates a constraint. * * @deprecated Use {@see dropIndex()} and {@see createIndex()}, * {@see dropForeignKey()} and {@see createForeignKey()} * or {@see dropUniqueConstraint()} and {@see createUniqueConstraint()} instead. * * @see dropConstraint() * @see createConstraint() * * @param Table|string $table * * @return void * * @throws Exception */ public function dropAndCreateConstraint(Constraint $constraint, $table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateConstraint() is deprecated.' . ' Use AbstractSchemaManager::dropIndex() and AbstractSchemaManager::createIndex(),' . ' AbstractSchemaManager::dropForeignKey() and AbstractSchemaManager::createForeignKey()' . ' or AbstractSchemaManager::dropUniqueConstraint()' . ' and AbstractSchemaManager::createUniqueConstraint() instead.', ); $this->tryMethod('dropConstraint', $constraint, $table); $this->createConstraint($constraint, $table); } /** * Drops and creates a new index on a table. * * @deprecated Use {@see dropIndex()} and {@see createIndex()} instead. * * @param Table|string $table The name of the table on which the index is to be created. * * @return void * * @throws Exception */ public function dropAndCreateIndex(Index $index, $table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateIndex() is deprecated.' . ' Use AbstractSchemaManager::dropIndex() and AbstractSchemaManager::createIndex() instead.', ); $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table); $this->createIndex($index, $table); } /** * Drops and creates a new foreign key. * * @deprecated Use {@see dropForeignKey()} and {@see createForeignKey()} instead. * * @param ForeignKeyConstraint $foreignKey An associative array that defines properties * of the foreign key to be created. * @param Table|string $table The name of the table on which the foreign key is to be created. * * @return void * * @throws Exception */ public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateForeignKey() is deprecated.' . ' Use AbstractSchemaManager::dropForeignKey() and AbstractSchemaManager::createForeignKey() instead.', ); $this->tryMethod('dropForeignKey', $foreignKey, $table); $this->createForeignKey($foreignKey, $table); } /** * Drops and create a new sequence. * * @deprecated Use {@see dropSequence()} and {@see createSequence()} instead. * * @return void * * @throws Exception */ public function dropAndCreateSequence(Sequence $sequence) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateSequence() is deprecated.' . ' Use AbstractSchemaManager::dropSequence() and AbstractSchemaManager::createSequence() instead.', ); $this->tryMethod('dropSequence', $sequence->getQuotedName($this->_platform)); $this->createSequence($sequence); } /** * Drops and creates a new table. * * @deprecated Use {@see dropTable()} and {@see createTable()} instead. * * @return void * * @throws Exception */ public function dropAndCreateTable(Table $table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateTable() is deprecated.' . ' Use AbstractSchemaManager::dropTable() and AbstractSchemaManager::createTable() instead.', ); $this->tryMethod('dropTable', $table->getQuotedName($this->_platform)); $this->createTable($table); } /** * Drops and creates a new database. * * @deprecated Use {@see dropDatabase()} and {@see createDatabase()} instead. * * @param string $database The name of the database to create. * * @return void * * @throws Exception */ public function dropAndCreateDatabase($database) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateDatabase() is deprecated.' . ' Use AbstractSchemaManager::dropDatabase() and AbstractSchemaManager::createDatabase() instead.', ); $this->tryMethod('dropDatabase', $database); $this->createDatabase($database); } /** * Drops and creates a new view. * * @deprecated Use {@see dropView()} and {@see createView()} instead. * * @return void * * @throws Exception */ public function dropAndCreateView(View $view) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateView() is deprecated.' . ' Use AbstractSchemaManager::dropView() and AbstractSchemaManager::createView() instead.', ); $this->tryMethod('dropView', $view->getQuotedName($this->_platform)); $this->createView($view); } /** * Alters an existing schema. * * @throws Exception */ public function alterSchema(SchemaDiff $schemaDiff): void { $this->_execSql($this->_platform->getAlterSchemaSQL($schemaDiff)); } /** * Migrates an existing schema to a new schema. * * @throws Exception */ public function migrateSchema(Schema $toSchema): void { $schemaDiff = $this->createComparator() ->compareSchemas($this->introspectSchema(), $toSchema); $this->alterSchema($schemaDiff); } /* alterTable() Methods */ /** * Alters an existing tables schema. * * @return void * * @throws Exception */ public function alterTable(TableDiff $tableDiff) { $this->_execSql($this->_platform->getAlterTableSQL($tableDiff)); } /** * Renames a given table to another name. * * @param string $name The current name of the table. * @param string $newName The new name of the table. * * @return void * * @throws Exception */ public function renameTable($name, $newName) { $this->_execSql($this->_platform->getRenameTableSQL($name, $newName)); } /** * Methods for filtering return values of list*() methods to convert * the native DBMS data definition to a portable Doctrine definition */ /** * @param mixed[] $databases * * @return string[] */ protected function _getPortableDatabasesList($databases) { $list = []; foreach ($databases as $value) { $list[] = $this->_getPortableDatabaseDefinition($value); } return $list; } /** * Converts a list of namespace names from the native DBMS data definition to a portable Doctrine definition. * * @deprecated Use {@see listSchemaNames()} instead. * * @param array<int, array<string, mixed>> $namespaces The list of namespace names * in the native DBMS data definition. * * @return string[] */ protected function getPortableNamespacesList(array $namespaces) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'AbstractSchemaManager::getPortableNamespacesList() is deprecated,' . ' use AbstractSchemaManager::listSchemaNames() instead.', ); $namespacesList = []; foreach ($namespaces as $namespace) { $namespacesList[] = $this->getPortableNamespaceDefinition($namespace); } return $namespacesList; } /** * @param mixed $database * * @return mixed */ protected function _getPortableDatabaseDefinition($database) { return $database; } /** * Converts a namespace definition from the native DBMS data definition to a portable Doctrine definition. * * @deprecated Use {@see listSchemaNames()} instead. * * @param array<string, mixed> $namespace The native DBMS namespace definition. * * @return mixed */ protected function getPortableNamespaceDefinition(array $namespace) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'AbstractSchemaManager::getPortableNamespaceDefinition() is deprecated,' . ' use AbstractSchemaManager::listSchemaNames() instead.', ); return $namespace; } /** * @param mixed[][] $sequences * * @return Sequence[] * * @throws Exception */ protected function _getPortableSequencesList($sequences) { $list = []; foreach ($sequences as $value) { $list[] = $this->_getPortableSequenceDefinition($value); } return $list; } /** * @param mixed[] $sequence * * @return Sequence * * @throws Exception */ protected function _getPortableSequenceDefinition($sequence) { throw Exception::notSupported('Sequences'); } /** * Independent of the database the keys of the column list result are lowercased. * * The name of the created column instance however is kept in its case. * * @param string $table The name of the table. * @param string $database * @param mixed[][] $tableColumns * * @return Column[] * * @throws Exception */ protected function _getPortableTableColumnList($table, $database, $tableColumns) { $eventManager = $this->_platform->getEventManager(); $list = []; foreach ($tableColumns as $tableColumn) { $column = null; $defaultPrevented = false; if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaColumnDefinition)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated. Use a custom schema manager instead.', Events::onSchemaColumnDefinition, ); $eventArgs = new SchemaColumnDefinitionEventArgs($tableColumn, $table, $database, $this->_conn); $eventManager->dispatchEvent(Events::onSchemaColumnDefinition, $eventArgs); $defaultPrevented = $eventArgs->isDefaultPrevented(); $column = $eventArgs->getColumn(); } if (! $defaultPrevented) { $column = $this->_getPortableTableColumnDefinition($tableColumn); } if ($column === null) { continue; } $name = strtolower($column->getQuotedName($this->_platform)); $list[$name] = $column; } return $list; } /** * Gets Table Column Definition. * * @param mixed[] $tableColumn * * @return Column * * @throws Exception */ abstract protected function _getPortableTableColumnDefinition($tableColumn); /** * Aggregates and groups the index results according to the required data result. * * @param mixed[][] $tableIndexes * @param string|null $tableName * * @return Index[] * * @throws Exception */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { $result = []; foreach ($tableIndexes as $tableIndex) { $indexName = $keyName = $tableIndex['key_name']; if ($tableIndex['primary']) { $keyName = 'primary'; } $keyName = strtolower($keyName); if (! isset($result[$keyName])) { $options = [ 'lengths' => [], ]; if (isset($tableIndex['where'])) { $options['where'] = $tableIndex['where']; } $result[$keyName] = [ 'name' => $indexName, 'columns' => [], 'unique' => ! $tableIndex['non_unique'], 'primary' => $tableIndex['primary'], 'flags' => $tableIndex['flags'] ?? [], 'options' => $options, ]; } $result[$keyName]['columns'][] = $tableIndex['column_name']; $result[$keyName]['options']['lengths'][] = $tableIndex['length'] ?? null; } $eventManager = $this->_platform->getEventManager(); $indexes = []; foreach ($result as $indexKey => $data) { $index = null; $defaultPrevented = false; if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated. Use a custom schema manager instead.', Events::onSchemaColumnDefinition, ); $eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn); $eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs); $defaultPrevented = $eventArgs->isDefaultPrevented(); $index = $eventArgs->getIndex(); } if (! $defaultPrevented) { $index = new Index( $data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags'], $data['options'], ); } if ($index === null) { continue; } $indexes[$indexKey] = $index; } return $indexes; } /** * @param mixed[][] $tables * * @return string[] */ protected function _getPortableTablesList($tables) { $list = []; foreach ($tables as $value) { $list[] = $this->_getPortableTableDefinition($value); } return $list; } /** * @param mixed $table * * @return string */ protected function _getPortableTableDefinition($table) { return $table; } /** * @param mixed[][] $views * * @return View[] */ protected function _getPortableViewsList($views) { $list = []; foreach ($views as $value) { $view = $this->_getPortableViewDefinition($value); if ($view === false) { continue; } $viewName = strtolower($view->getQuotedName($this->_platform)); $list[$viewName] = $view; } return $list; } /** * @param mixed[] $view * * @return View|false */ protected function _getPortableViewDefinition($view) { return false; } /** * @param mixed[][] $tableForeignKeys * * @return ForeignKeyConstraint[] */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $list = []; foreach ($tableForeignKeys as $value) { $list[] = $this->_getPortableTableForeignKeyDefinition($value); } return $list; } /** * @param mixed $tableForeignKey * * @return ForeignKeyConstraint * * @abstract */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { return $tableForeignKey; } /** * @internal * * @param string[]|string $sql * * @return void * * @throws Exception */ protected function _execSql($sql) { foreach ((array) $sql as $query) { $this->_conn->executeStatement($query); } } /** * Creates a schema instance for the current database. * * @deprecated Use {@link introspectSchema()} instead. * * @return Schema * * @throws Exception */ public function createSchema() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5613', '%s is deprecated. Use introspectSchema() instead.', __METHOD__, ); $schemaNames = []; if ($this->_platform->supportsSchemas()) { $schemaNames = $this->listNamespaceNames(); } $sequences = []; if ($this->_platform->supportsSequences()) { $sequences = $this->listSequences(); } $tables = $this->listTables(); return new Schema($tables, $sequences, $this->createSchemaConfig(), $schemaNames); } /** * Returns a {@see Schema} instance representing the current database schema. * * @throws Exception */ public function introspectSchema(): Schema { return $this->createSchema(); } /** * Creates the configuration for this schema. * * @return SchemaConfig * * @throws Exception */ public function createSchemaConfig() { $schemaConfig = new SchemaConfig(); $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength()); $searchPaths = $this->getSchemaSearchPaths(); if (isset($searchPaths[0])) { $schemaConfig->setName($searchPaths[0]); } $params = $this->_conn->getParams(); if (! isset($params['defaultTableOptions'])) { $params['defaultTableOptions'] = []; } if (! isset($params['defaultTableOptions']['charset']) && isset($params['charset'])) { $params['defaultTableOptions']['charset'] = $params['charset']; } $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']); return $schemaConfig; } /** * The search path for namespaces in the currently connected database. * * The first entry is usually the default namespace in the Schema. All * further namespaces contain tables/sequences which can also be addressed * with a short, not full-qualified name. * * For databases that don't support subschema/namespaces this method * returns the name of the currently connected database. * * @deprecated * * @return string[] * * @throws Exception */ public function getSchemaSearchPaths() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4821', 'AbstractSchemaManager::getSchemaSearchPaths() is deprecated.', ); $database = $this->_conn->getDatabase(); if ($database !== null) { return [$database]; } return []; } /** * Given a table comment this method tries to extract a typehint for Doctrine Type, or returns * the type given as default. * * @internal This method should be only used from within the AbstractSchemaManager class hierarchy. * * @param string|null $comment * @param string $currentType * * @return string */ public function extractDoctrineTypeFromComment($comment, $currentType) { if ($this->_conn->getConfiguration()->getDisableTypeComments()) { return $currentType; } if ($comment !== null && preg_match('(\(DC2Type:(((?!\)).)+)\))', $comment, $match) === 1) { return $match[1]; } return $currentType; } /** * @internal This method should be only used from within the AbstractSchemaManager class hierarchy. * * @param string|null $comment * @param string|null $type * * @return string|null */ public function removeDoctrineTypeFromComment($comment, $type) { if ($this->_conn->getConfiguration()->getDisableTypeComments()) { return $comment; } if ($comment === null) { return null; } return str_replace('(DC2Type:' . $type . ')', '', $comment); } /** @throws Exception */ private function getDatabase(string $methodName): string { $database = $this->_conn->getDatabase(); if ($database === null) { throw DatabaseRequired::new($methodName); } return $database; } public function createComparator(): Comparator { return new Comparator($this->_platform); } /** * @return array<string,list<array<string,mixed>>> * * @throws Exception */ private function fetchAllAssociativeGrouped(Result $result): array { $data = []; foreach ($result->fetchAllAssociative() as $row) { $tableName = $this->_getPortableTableDefinition($row); $data[$tableName][] = $row; } return $data; } } Schema/Constraint.php 0000755 00000002055 00000000000 0010547 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Marker interface for constraints. * * @deprecated Use {@see ForeignKeyConstraint}, {@see Index} or {@see UniqueConstraint} instead. */ interface Constraint { /** @return string */ public function getName(); /** @return string */ public function getQuotedName(AbstractPlatform $platform); /** * Returns the names of the referencing table columns * the constraint is associated with. * * @return string[] */ public function getColumns(); /** * Returns the quoted representation of the column names * the constraint is associated with. * * But only if they were defined with one or a column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedColumns(AbstractPlatform $platform); } Schema/Column.php 0000755 00000022616 00000000000 0007665 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Schema\Exception\UnknownColumnOption; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_merge; use function is_numeric; use function method_exists; /** * Object representation of a database column. */ class Column extends AbstractAsset { /** @var Type */ protected $_type; /** @var int|null */ protected $_length; /** @var int */ protected $_precision = 10; /** @var int */ protected $_scale = 0; /** @var bool */ protected $_unsigned = false; /** @var bool */ protected $_fixed = false; /** @var bool */ protected $_notnull = true; /** @var mixed */ protected $_default; /** @var bool */ protected $_autoincrement = false; /** @var mixed[] */ protected $_platformOptions = []; /** @var string|null */ protected $_columnDefinition; /** @var string|null */ protected $_comment; /** * @deprecated Use {@link $_platformOptions} instead * * @var mixed[] */ protected $_customSchemaOptions = []; /** * Creates a new Column. * * @param string $name * @param mixed[] $options * * @throws SchemaException */ public function __construct($name, Type $type, array $options = []) { $this->_setName($name); $this->setType($type); $this->setOptions($options); } /** * @param mixed[] $options * * @return Column * * @throws SchemaException */ public function setOptions(array $options) { foreach ($options as $name => $value) { $method = 'set' . $name; if (! method_exists($this, $method)) { throw UnknownColumnOption::new($name); } $this->$method($value); } return $this; } /** @return Column */ public function setType(Type $type) { $this->_type = $type; return $this; } /** * @param int|null $length * * @return Column */ public function setLength($length) { if ($length !== null) { $this->_length = (int) $length; } else { $this->_length = null; } return $this; } /** * @param int $precision * * @return Column */ public function setPrecision($precision) { if (! is_numeric($precision)) { $precision = 10; // defaults to 10 when no valid precision is given. } $this->_precision = (int) $precision; return $this; } /** * @param int $scale * * @return Column */ public function setScale($scale) { if (! is_numeric($scale)) { $scale = 0; } $this->_scale = (int) $scale; return $this; } /** * @param bool $unsigned * * @return Column */ public function setUnsigned($unsigned) { $this->_unsigned = (bool) $unsigned; return $this; } /** * @param bool $fixed * * @return Column */ public function setFixed($fixed) { $this->_fixed = (bool) $fixed; return $this; } /** * @param bool $notnull * * @return Column */ public function setNotnull($notnull) { $this->_notnull = (bool) $notnull; return $this; } /** * @param mixed $default * * @return Column */ public function setDefault($default) { $this->_default = $default; return $this; } /** * @param mixed[] $platformOptions * * @return Column */ public function setPlatformOptions(array $platformOptions) { $this->_platformOptions = $platformOptions; return $this; } /** * @param string $name * @param mixed $value * * @return Column */ public function setPlatformOption($name, $value) { $this->_platformOptions[$name] = $value; return $this; } /** * @param string|null $value * * @return Column */ public function setColumnDefinition($value) { $this->_columnDefinition = $value; return $this; } /** @return Type */ public function getType() { return $this->_type; } /** @return int|null */ public function getLength() { return $this->_length; } /** @return int */ public function getPrecision() { return $this->_precision; } /** @return int */ public function getScale() { return $this->_scale; } /** @return bool */ public function getUnsigned() { return $this->_unsigned; } /** @return bool */ public function getFixed() { return $this->_fixed; } /** @return bool */ public function getNotnull() { return $this->_notnull; } /** @return mixed */ public function getDefault() { return $this->_default; } /** @return mixed[] */ public function getPlatformOptions() { return $this->_platformOptions; } /** * @param string $name * * @return bool */ public function hasPlatformOption($name) { return isset($this->_platformOptions[$name]); } /** * @param string $name * * @return mixed */ public function getPlatformOption($name) { return $this->_platformOptions[$name]; } /** @return string|null */ public function getColumnDefinition() { return $this->_columnDefinition; } /** @return bool */ public function getAutoincrement() { return $this->_autoincrement; } /** * @param bool $flag * * @return Column */ public function setAutoincrement($flag) { $this->_autoincrement = $flag; return $this; } /** * @param string|null $comment * * @return Column */ public function setComment($comment) { $this->_comment = $comment; return $this; } /** @return string|null */ public function getComment() { return $this->_comment; } /** * @deprecated Use {@link setPlatformOption()} instead * * @param string $name * @param mixed $value * * @return Column */ public function setCustomSchemaOption($name, $value) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5476', 'Column::setCustomSchemaOption() is deprecated. Use setPlatformOption() instead.', ); $this->_customSchemaOptions[$name] = $value; return $this; } /** * @deprecated Use {@link hasPlatformOption()} instead * * @param string $name * * @return bool */ public function hasCustomSchemaOption($name) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5476', 'Column::hasCustomSchemaOption() is deprecated. Use hasPlatformOption() instead.', ); return isset($this->_customSchemaOptions[$name]); } /** * @deprecated Use {@link getPlatformOption()} instead * * @param string $name * * @return mixed */ public function getCustomSchemaOption($name) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5476', 'Column::getCustomSchemaOption() is deprecated. Use getPlatformOption() instead.', ); return $this->_customSchemaOptions[$name]; } /** * @deprecated Use {@link setPlatformOptions()} instead * * @param mixed[] $customSchemaOptions * * @return Column */ public function setCustomSchemaOptions(array $customSchemaOptions) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5476', 'Column::setCustomSchemaOptions() is deprecated. Use setPlatformOptions() instead.', ); $this->_customSchemaOptions = $customSchemaOptions; return $this; } /** * @deprecated Use {@link getPlatformOptions()} instead * * @return mixed[] */ public function getCustomSchemaOptions() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5476', 'Column::getCustomSchemaOptions() is deprecated. Use getPlatformOptions() instead.', ); return $this->_customSchemaOptions; } /** @return mixed[] */ public function toArray() { return array_merge([ 'name' => $this->_name, 'type' => $this->_type, 'default' => $this->_default, 'notnull' => $this->_notnull, 'length' => $this->_length, 'precision' => $this->_precision, 'scale' => $this->_scale, 'fixed' => $this->_fixed, 'unsigned' => $this->_unsigned, 'autoincrement' => $this->_autoincrement, 'columnDefinition' => $this->_columnDefinition, 'comment' => $this->_comment, ], $this->_platformOptions, $this->_customSchemaOptions); } } Schema/DefaultSchemaManagerFactory.php 0000755 00000001102 00000000000 0013743 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; /** * A schema manager factory that returns the default schema manager for the given platform. */ final class DefaultSchemaManagerFactory implements SchemaManagerFactory { /** @throws Exception If the platform does not support creating schema managers yet. */ public function createSchemaManager(Connection $connection): AbstractSchemaManager { return $connection->getDatabasePlatform()->createSchemaManager($connection); } } Schema/SchemaException.php 0000755 00000012647 00000000000 0011512 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\Exception\ColumnAlreadyExists; use Doctrine\DBAL\Schema\Exception\ColumnDoesNotExist; use Doctrine\DBAL\Schema\Exception\ForeignKeyDoesNotExist; use Doctrine\DBAL\Schema\Exception\IndexAlreadyExists; use Doctrine\DBAL\Schema\Exception\IndexDoesNotExist; use Doctrine\DBAL\Schema\Exception\IndexNameInvalid; use Doctrine\DBAL\Schema\Exception\NamedForeignKeyRequired; use Doctrine\DBAL\Schema\Exception\NamespaceAlreadyExists; use Doctrine\DBAL\Schema\Exception\SequenceAlreadyExists; use Doctrine\DBAL\Schema\Exception\SequenceDoesNotExist; use Doctrine\DBAL\Schema\Exception\TableAlreadyExists; use Doctrine\DBAL\Schema\Exception\TableDoesNotExist; use Doctrine\DBAL\Schema\Exception\UniqueConstraintDoesNotExist; use function sprintf; /** @psalm-immutable */ class SchemaException extends Exception { /** @deprecated Use {@see TableDoesNotExist} instead. */ public const TABLE_DOESNT_EXIST = 10; /** @deprecated Use {@see TableAlreadyExists} instead. */ public const TABLE_ALREADY_EXISTS = 20; /** @deprecated Use {@see ColumnDoesNotExist} instead. */ public const COLUMN_DOESNT_EXIST = 30; /** @deprecated Use {@see ColumnAlreadyExists} instead. */ public const COLUMN_ALREADY_EXISTS = 40; /** @deprecated Use {@see IndexDoesNotExist} instead. */ public const INDEX_DOESNT_EXIST = 50; /** @deprecated Use {@see IndexAlreadyExists} instead. */ public const INDEX_ALREADY_EXISTS = 60; /** @deprecated Use {@see SequenceDoesNotExist} instead. */ public const SEQUENCE_DOENST_EXIST = 70; /** @deprecated Use {@see SequenceAlreadyExists} instead. */ public const SEQUENCE_ALREADY_EXISTS = 80; /** @deprecated Use {@see IndexNameInvalid} instead. */ public const INDEX_INVALID_NAME = 90; /** @deprecated Use {@see ForeignKeyDoesNotExist} instead. */ public const FOREIGNKEY_DOESNT_EXIST = 100; /** @deprecated Use {@see UniqueConstraintDoesNotExist} instead. */ public const CONSTRAINT_DOESNT_EXIST = 110; /** @deprecated Use {@see NamespaceAlreadyExists} instead. */ public const NAMESPACE_ALREADY_EXISTS = 120; /** * @param string $tableName * * @return SchemaException */ public static function tableDoesNotExist($tableName) { return TableDoesNotExist::new($tableName); } /** * @param string $indexName * * @return SchemaException */ public static function indexNameInvalid($indexName) { return IndexNameInvalid::new($indexName); } /** * @param string $indexName * @param string $table * * @return SchemaException */ public static function indexDoesNotExist($indexName, $table) { return IndexDoesNotExist::new($indexName, $table); } /** * @param string $indexName * @param string $table * * @return SchemaException */ public static function indexAlreadyExists($indexName, $table) { return IndexAlreadyExists::new($indexName, $table); } /** * @param string $columnName * @param string $table * * @return SchemaException */ public static function columnDoesNotExist($columnName, $table) { return ColumnDoesNotExist::new($columnName, $table); } /** * @param string $namespaceName * * @return SchemaException */ public static function namespaceAlreadyExists($namespaceName) { return NamespaceAlreadyExists::new($namespaceName); } /** * @param string $tableName * * @return SchemaException */ public static function tableAlreadyExists($tableName) { return TableAlreadyExists::new($tableName); } /** * @param string $tableName * @param string $columnName * * @return SchemaException */ public static function columnAlreadyExists($tableName, $columnName) { return ColumnAlreadyExists::new($tableName, $columnName); } /** * @param string $name * * @return SchemaException */ public static function sequenceAlreadyExists($name) { return SequenceAlreadyExists::new($name); } /** * @param string $name * * @return SchemaException */ public static function sequenceDoesNotExist($name) { return SequenceDoesNotExist::new($name); } /** * @param string $constraintName * @param string $table * * @return SchemaException */ public static function uniqueConstraintDoesNotExist($constraintName, $table) { return UniqueConstraintDoesNotExist::new($constraintName, $table); } /** * @param string $fkName * @param string $table * * @return SchemaException */ public static function foreignKeyDoesNotExist($fkName, $table) { return ForeignKeyDoesNotExist::new($fkName, $table); } /** @return SchemaException */ public static function namedForeignKeyRequired(Table $localTable, ForeignKeyConstraint $foreignKey) { return NamedForeignKeyRequired::new($localTable, $foreignKey); } /** * @param string $changeName * * @return SchemaException */ public static function alterTableChangeNotSupported($changeName) { return new self( sprintf("Alter table change not supported, given '%s'", $changeName), ); } } Schema/Index.php 0000755 00000021345 00000000000 0007475 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use InvalidArgumentException; use function array_filter; use function array_keys; use function array_map; use function array_search; use function array_shift; use function count; use function strtolower; class Index extends AbstractAsset implements Constraint { /** * Asset identifier instances of the column names the index is associated with. * array($columnName => Identifier) * * @var Identifier[] */ protected $_columns = []; /** @var bool */ protected $_isUnique = false; /** @var bool */ protected $_isPrimary = false; /** * Platform specific flags for indexes. * array($flagName => true) * * @var true[] */ protected $_flags = []; /** * Platform specific options * * @todo $_flags should eventually be refactored into options * @var mixed[] */ private array $options = []; /** * @param string $name * @param string[] $columns * @param bool $isUnique * @param bool $isPrimary * @param string[] $flags * @param mixed[] $options */ public function __construct( $name, array $columns, $isUnique = false, $isPrimary = false, array $flags = [], array $options = [] ) { $isUnique = $isUnique || $isPrimary; $this->_setName($name); $this->_isUnique = $isUnique; $this->_isPrimary = $isPrimary; $this->options = $options; foreach ($columns as $column) { $this->_addColumn($column); } foreach ($flags as $flag) { $this->addFlag($flag); } } /** @throws InvalidArgumentException */ protected function _addColumn(string $column): void { $this->_columns[$column] = new Identifier($column); } /** * {@inheritDoc} */ public function getColumns() { return array_keys($this->_columns); } /** * {@inheritDoc} */ public function getQuotedColumns(AbstractPlatform $platform) { $subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths') ? $this->getOption('lengths') : []; $columns = []; foreach ($this->_columns as $column) { $length = array_shift($subParts); $quotedColumn = $column->getQuotedName($platform); if ($length !== null) { $quotedColumn .= '(' . $length . ')'; } $columns[] = $quotedColumn; } return $columns; } /** @return string[] */ public function getUnquotedColumns() { return array_map([$this, 'trimQuotes'], $this->getColumns()); } /** * Is the index neither unique nor primary key? * * @return bool */ public function isSimpleIndex() { return ! $this->_isPrimary && ! $this->_isUnique; } /** @return bool */ public function isUnique() { return $this->_isUnique; } /** @return bool */ public function isPrimary() { return $this->_isPrimary; } /** * @param string $name * @param int $pos * * @return bool */ public function hasColumnAtPosition($name, $pos = 0) { $name = $this->trimQuotes(strtolower($name)); $indexColumns = array_map('strtolower', $this->getUnquotedColumns()); return array_search($name, $indexColumns, true) === $pos; } /** * Checks if this index exactly spans the given column names in the correct order. * * @param string[] $columnNames * * @return bool */ public function spansColumns(array $columnNames) { $columns = $this->getColumns(); $numberOfColumns = count($columns); $sameColumns = true; for ($i = 0; $i < $numberOfColumns; $i++) { if ( isset($columnNames[$i]) && $this->trimQuotes(strtolower($columns[$i])) === $this->trimQuotes(strtolower($columnNames[$i])) ) { continue; } $sameColumns = false; } return $sameColumns; } /** * Keeping misspelled function name for backwards compatibility * * @deprecated Use {@see isFulfilledBy()} instead. * * @return bool */ public function isFullfilledBy(Index $other) { return $this->isFulfilledBy($other); } /** * Checks if the other index already fulfills all the indexing and constraint needs of the current one. */ public function isFulfilledBy(Index $other): bool { // allow the other index to be equally large only. It being larger is an option // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo) if (count($other->getColumns()) !== count($this->getColumns())) { return false; } // Check if columns are the same, and even in the same order $sameColumns = $this->spansColumns($other->getColumns()); if ($sameColumns) { if (! $this->samePartialIndex($other)) { return false; } if (! $this->hasSameColumnLengths($other)) { return false; } if (! $this->isUnique() && ! $this->isPrimary()) { // this is a special case: If the current key is neither primary or unique, any unique or // primary key will always have the same effect for the index and there cannot be any constraint // overlaps. This means a primary or unique index can always fulfill the requirements of just an // index that has no constraints. return true; } if ($other->isPrimary() !== $this->isPrimary()) { return false; } return $other->isUnique() === $this->isUnique(); } return false; } /** * Detects if the other index is a non-unique, non primary index that can be overwritten by this one. * * @return bool */ public function overrules(Index $other) { if ($other->isPrimary()) { return false; } if ($this->isSimpleIndex() && $other->isUnique()) { return false; } return $this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique()) && $this->samePartialIndex($other); } /** * Returns platform specific flags for indexes. * * @return string[] */ public function getFlags() { return array_keys($this->_flags); } /** * Adds Flag for an index that translates to platform specific handling. * * @param string $flag * * @return Index * * @example $index->addFlag('CLUSTERED') */ public function addFlag($flag) { $this->_flags[strtolower($flag)] = true; return $this; } /** * Does this index have a specific flag? * * @param string $flag * * @return bool */ public function hasFlag($flag) { return isset($this->_flags[strtolower($flag)]); } /** * Removes a flag. * * @param string $flag * * @return void */ public function removeFlag($flag) { unset($this->_flags[strtolower($flag)]); } /** * @param string $name * * @return bool */ public function hasOption($name) { return isset($this->options[strtolower($name)]); } /** * @param string $name * * @return mixed */ public function getOption($name) { return $this->options[strtolower($name)]; } /** @return mixed[] */ public function getOptions() { return $this->options; } /** * Return whether the two indexes have the same partial index */ private function samePartialIndex(Index $other): bool { if ( $this->hasOption('where') && $other->hasOption('where') && $this->getOption('where') === $other->getOption('where') ) { return true; } return ! $this->hasOption('where') && ! $other->hasOption('where'); } /** * Returns whether the index has the same column lengths as the other */ private function hasSameColumnLengths(self $other): bool { $filter = static function (?int $length): bool { return $length !== null; }; return array_filter($this->options['lengths'] ?? [], $filter) === array_filter($other->options['lengths'] ?? [], $filter); } } Schema/AbstractAsset.php 0000755 00000014073 00000000000 0011171 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function array_map; use function crc32; use function dechex; use function explode; use function implode; use function str_replace; use function strpos; use function strtolower; use function strtoupper; use function substr; /** * The abstract asset allows to reset the name of all assets without publishing this to the public userland. * * This encapsulation hack is necessary to keep a consistent state of the database schema. Say we have a list of tables * array($tableName => Table($tableName)); if you want to rename the table, you have to make sure this does not get * recreated during schema migration. */ abstract class AbstractAsset { /** @var string */ protected $_name = ''; /** * Namespace of the asset. If none isset the default namespace is assumed. * * @var string|null */ protected $_namespace; /** @var bool */ protected $_quoted = false; /** * Sets the name of this asset. * * @param string $name * * @return void */ protected function _setName($name) { if ($this->isIdentifierQuoted($name)) { $this->_quoted = true; $name = $this->trimQuotes($name); } if (strpos($name, '.') !== false) { $parts = explode('.', $name); $this->_namespace = $parts[0]; $name = $parts[1]; } $this->_name = $name; } /** * Is this asset in the default namespace? * * @param string $defaultNamespaceName * * @return bool */ public function isInDefaultNamespace($defaultNamespaceName) { return $this->_namespace === $defaultNamespaceName || $this->_namespace === null; } /** * Gets the namespace name of this asset. * * If NULL is returned this means the default namespace is used. * * @return string|null */ public function getNamespaceName() { return $this->_namespace; } /** * The shortest name is stripped of the default namespace. All other * namespaced elements are returned as full-qualified names. * * @param string|null $defaultNamespaceName * * @return string */ public function getShortestName($defaultNamespaceName) { $shortestName = $this->getName(); if ($this->_namespace === $defaultNamespaceName) { $shortestName = $this->_name; } return strtolower($shortestName); } /** * The normalized name is full-qualified and lower-cased. Lower-casing is * actually wrong, but we have to do it to keep our sanity. If you are * using database objects that only differentiate in the casing (FOO vs * Foo) then you will NOT be able to use Doctrine Schema abstraction. * * Every non-namespaced element is prefixed with the default namespace * name which is passed as argument to this method. * * @deprecated Use {@see getNamespaceName()} and {@see getName()} instead. * * @param string $defaultNamespaceName * * @return string */ public function getFullQualifiedName($defaultNamespaceName) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4814', 'AbstractAsset::getFullQualifiedName() is deprecated.' . ' Use AbstractAsset::getNamespaceName() and ::getName() instead.', ); $name = $this->getName(); if ($this->_namespace === null) { $name = $defaultNamespaceName . '.' . $name; } return strtolower($name); } /** * Checks if this asset's name is quoted. * * @return bool */ public function isQuoted() { return $this->_quoted; } /** * Checks if this identifier is quoted. * * @param string $identifier * * @return bool */ protected function isIdentifierQuoted($identifier) { return isset($identifier[0]) && ($identifier[0] === '`' || $identifier[0] === '"' || $identifier[0] === '['); } /** * Trim quotes from the identifier. * * @param string $identifier * * @return string */ protected function trimQuotes($identifier) { return str_replace(['`', '"', '[', ']'], '', $identifier); } /** * Returns the name of this schema asset. * * @return string */ public function getName() { if ($this->_namespace !== null) { return $this->_namespace . '.' . $this->_name; } return $this->_name; } /** * Gets the quoted representation of this asset but only if it was defined with one. Otherwise * return the plain unquoted value as inserted. * * @return string */ public function getQuotedName(AbstractPlatform $platform) { $keywords = $platform->getReservedKeywordsList(); $parts = explode('.', $this->getName()); foreach ($parts as $k => $v) { $parts[$k] = $this->_quoted || $keywords->isKeyword($v) ? $platform->quoteIdentifier($v) : $v; } return implode('.', $parts); } /** * Generates an identifier from a list of column names obeying a certain string length. * * This is especially important for Oracle, since it does not allow identifiers larger than 30 chars, * however building idents automatically for foreign keys, composite keys or such can easily create * very long names. * * @param string[] $columnNames * @param string $prefix * @param int $maxSize * * @return string */ protected function _generateIdentifierName($columnNames, $prefix = '', $maxSize = 30) { $hash = implode('', array_map(static function ($column): string { return dechex(crc32($column)); }, $columnNames)); return strtoupper(substr($prefix . '_' . $hash, 0, $maxSize)); } } Schema/SchemaDiff.php 0000755 00000017036 00000000000 0010421 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function array_filter; use function array_merge; use function count; /** * Differences between two schemas. * * The object contains the operations to change the schema stored in $fromSchema * to a target schema. */ class SchemaDiff { /** * @deprecated * * @var Schema|null */ public $fromSchema; /** * All added namespaces. * * @internal Use {@link getCreatedSchemas()} instead. * * @var string[] */ public $newNamespaces = []; /** * All removed namespaces. * * @internal Use {@link getDroppedSchemas()} instead. * * @var string[] */ public $removedNamespaces = []; /** * All added tables. * * @internal Use {@link getCreatedTables()} instead. * * @var Table[] */ public $newTables = []; /** * All changed tables. * * @internal Use {@link getAlteredTables()} instead. * * @var TableDiff[] */ public $changedTables = []; /** * All removed tables. * * @internal Use {@link getDroppedTables()} instead. * * @var Table[] */ public $removedTables = []; /** * @internal Use {@link getCreatedSequences()} instead. * * @var Sequence[] */ public $newSequences = []; /** * @internal Use {@link getAlteredSequences()} instead. * * @var Sequence[] */ public $changedSequences = []; /** * @internal Use {@link getDroppedSequences()} instead. * * @var Sequence[] */ public $removedSequences = []; /** * @deprecated * * @var ForeignKeyConstraint[] */ public $orphanedForeignKeys = []; /** * Constructs an SchemaDiff object. * * @internal The diff can be only instantiated by a {@see Comparator}. * * @param Table[] $newTables * @param TableDiff[] $changedTables * @param Table[] $removedTables * @param array<string> $createdSchemas * @param array<string> $droppedSchemas * @param array<Sequence> $createdSequences * @param array<Sequence> $alteredSequences * @param array<Sequence> $droppedSequences */ public function __construct( $newTables = [], $changedTables = [], $removedTables = [], ?Schema $fromSchema = null, $createdSchemas = [], $droppedSchemas = [], $createdSequences = [], $alteredSequences = [], $droppedSequences = [] ) { $this->newTables = $newTables; $this->changedTables = array_filter($changedTables, static function (TableDiff $diff): bool { return ! $diff->isEmpty(); }); $this->removedTables = $removedTables; $this->fromSchema = $fromSchema; $this->newNamespaces = $createdSchemas; $this->removedNamespaces = $droppedSchemas; $this->newSequences = $createdSequences; $this->changedSequences = $alteredSequences; $this->removedSequences = $droppedSequences; } /** @return array<string> */ public function getCreatedSchemas(): array { return $this->newNamespaces; } /** @return array<string> */ public function getDroppedSchemas(): array { return $this->removedNamespaces; } /** @return array<Table> */ public function getCreatedTables(): array { return $this->newTables; } /** @return array<TableDiff> */ public function getAlteredTables(): array { return $this->changedTables; } /** @return array<Table> */ public function getDroppedTables(): array { return $this->removedTables; } /** @return array<Sequence> */ public function getCreatedSequences(): array { return $this->newSequences; } /** @return array<Sequence> */ public function getAlteredSequences(): array { return $this->changedSequences; } /** @return array<Sequence> */ public function getDroppedSequences(): array { return $this->removedSequences; } /** * Returns whether the diff is empty (contains no changes). */ public function isEmpty(): bool { return count($this->newNamespaces) === 0 && count($this->removedNamespaces) === 0 && count($this->newTables) === 0 && count($this->changedTables) === 0 && count($this->removedTables) === 0 && count($this->newSequences) === 0 && count($this->changedSequences) === 0 && count($this->removedSequences) === 0; } /** * The to save sql mode ensures that the following things don't happen: * * 1. Tables are deleted * 2. Sequences are deleted * 3. Foreign Keys which reference tables that would otherwise be deleted. * * This way it is ensured that assets are deleted which might not be relevant to the metadata schema at all. * * @deprecated * * @return list<string> */ public function toSaveSql(AbstractPlatform $platform) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5766', '%s is deprecated.', __METHOD__, ); return $this->_toSql($platform, true); } /** * @deprecated Use {@link AbstractPlatform::getAlterSchemaSQL()} instead. * * @return list<string> */ public function toSql(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5766', '%s is deprecated. Use AbstractPlatform::getAlterSchemaSQL() instead.', __METHOD__, ); return $this->_toSql($platform, false); } /** * @param bool $saveMode * * @return list<string> */ protected function _toSql(AbstractPlatform $platform, $saveMode = false) { $sql = []; if ($platform->supportsSchemas()) { foreach ($this->getCreatedSchemas() as $schema) { $sql[] = $platform->getCreateSchemaSQL($schema); } } if ($platform->supportsForeignKeyConstraints() && $saveMode === false) { foreach ($this->orphanedForeignKeys as $orphanedForeignKey) { $sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTable()); } } if ($platform->supportsSequences() === true) { foreach ($this->getAlteredSequences() as $sequence) { $sql[] = $platform->getAlterSequenceSQL($sequence); } if ($saveMode === false) { foreach ($this->getDroppedSequences() as $sequence) { $sql[] = $platform->getDropSequenceSQL($sequence); } } foreach ($this->getCreatedSequences() as $sequence) { $sql[] = $platform->getCreateSequenceSQL($sequence); } } $sql = array_merge($sql, $platform->getCreateTablesSQL($this->getCreatedTables())); if ($saveMode === false) { $sql = array_merge($sql, $platform->getDropTablesSQL($this->getDroppedTables())); } foreach ($this->getAlteredTables() as $tableDiff) { $sql = array_merge($sql, $platform->getAlterTableSQL($tableDiff)); } return $sql; } } Schema/PostgreSQLSchemaManager.php 0000755 00000056115 00000000000 0013050 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\JsonType; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use function array_change_key_case; use function array_filter; use function array_map; use function array_merge; use function array_shift; use function assert; use function explode; use function get_class; use function implode; use function in_array; use function preg_match; use function preg_replace; use function sprintf; use function str_replace; use function strpos; use function strtolower; use function trim; use const CASE_LOWER; /** * PostgreSQL Schema Manager. * * @extends AbstractSchemaManager<PostgreSQLPlatform> */ class PostgreSQLSchemaManager extends AbstractSchemaManager { /** @var string[]|null */ private ?array $existingSchemaPaths = null; /** * {@inheritDoc} */ public function listTableNames() { return $this->doListTableNames(); } /** * {@inheritDoc} */ public function listTables() { return $this->doListTables(); } /** * {@inheritDoc} * * @deprecated Use {@see introspectTable()} instead. */ public function listTableDetails($name) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5595', '%s is deprecated. Use introspectTable() instead.', __METHOD__, ); return $this->doListTableDetails($name); } /** * {@inheritDoc} */ public function listTableColumns($table, $database = null) { return $this->doListTableColumns($table, $database); } /** * {@inheritDoc} */ public function listTableIndexes($table) { return $this->doListTableIndexes($table); } /** * {@inheritDoc} */ public function listTableForeignKeys($table, $database = null) { return $this->doListTableForeignKeys($table, $database); } /** * Gets all the existing schema names. * * @deprecated Use {@see listSchemaNames()} instead. * * @return string[] * * @throws Exception */ public function getSchemaNames() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'PostgreSQLSchemaManager::getSchemaNames() is deprecated,' . ' use PostgreSQLSchemaManager::listSchemaNames() instead.', ); return $this->listNamespaceNames(); } /** * {@inheritDoc} */ public function listSchemaNames(): array { return $this->_conn->fetchFirstColumn( <<<'SQL' SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT LIKE 'pg\_%' AND schema_name != 'information_schema' SQL, ); } /** * {@inheritDoc} * * @deprecated */ public function getSchemaSearchPaths() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4821', 'PostgreSQLSchemaManager::getSchemaSearchPaths() is deprecated.', ); $params = $this->_conn->getParams(); $searchPaths = $this->_conn->fetchOne('SHOW search_path'); assert($searchPaths !== false); $schema = explode(',', $searchPaths); if (isset($params['user'])) { $schema = str_replace('"$user"', $params['user'], $schema); } return array_map('trim', $schema); } /** * Gets names of all existing schemas in the current users search path. * * This is a PostgreSQL only function. * * @internal The method should be only used from within the PostgreSQLSchemaManager class hierarchy. * * @return string[] * * @throws Exception */ public function getExistingSchemaSearchPaths() { if ($this->existingSchemaPaths === null) { $this->determineExistingSchemaSearchPaths(); } assert($this->existingSchemaPaths !== null); return $this->existingSchemaPaths; } /** * Returns the name of the current schema. * * @return string|null * * @throws Exception */ protected function getCurrentSchema() { $schemas = $this->getExistingSchemaSearchPaths(); return array_shift($schemas); } /** * Sets or resets the order of the existing schemas in the current search path of the user. * * This is a PostgreSQL only function. * * @internal The method should be only used from within the PostgreSQLSchemaManager class hierarchy. * * @return void * * @throws Exception */ public function determineExistingSchemaSearchPaths() { $names = $this->listSchemaNames(); $paths = $this->getSchemaSearchPaths(); $this->existingSchemaPaths = array_filter($paths, static function ($v) use ($names): bool { return in_array($v, $names, true); }); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { $onUpdate = null; $onDelete = null; if ( preg_match( '(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match, ) === 1 ) { $onUpdate = $match[1]; } if ( preg_match( '(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match, ) === 1 ) { $onDelete = $match[1]; } $result = preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values); assert($result === 1); // PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get // the idea to trim them here. $localColumns = array_map('trim', explode(',', $values[1])); $foreignColumns = array_map('trim', explode(',', $values[3])); $foreignTable = $values[2]; return new ForeignKeyConstraint( $localColumns, $foreignTable, $foreignColumns, $tableForeignKey['conname'], ['onUpdate' => $onUpdate, 'onDelete' => $onDelete], ); } /** * {@inheritDoc} */ protected function _getPortableViewDefinition($view) { return new View($view['schemaname'] . '.' . $view['viewname'], $view['definition']); } /** * {@inheritDoc} */ protected function _getPortableTableDefinition($table) { $currentSchema = $this->getCurrentSchema(); if ($table['schema_name'] === $currentSchema) { return $table['table_name']; } return $table['schema_name'] . '.' . $table['table_name']; } /** * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { $buffer = []; foreach ($tableIndexes as $row) { $colNumbers = array_map('intval', explode(' ', $row['indkey'])); $columnNameSql = sprintf( 'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC', $row['indrelid'], implode(' ,', $colNumbers), ); $indexColumns = $this->_conn->fetchAllAssociative($columnNameSql); // required for getting the order of the columns right. foreach ($colNumbers as $colNum) { foreach ($indexColumns as $colRow) { if ($colNum !== $colRow['attnum']) { continue; } $buffer[] = [ 'key_name' => $row['relname'], 'column_name' => trim($colRow['attname']), 'non_unique' => ! $row['indisunique'], 'primary' => $row['indisprimary'], 'where' => $row['where'], ]; } } } return parent::_getPortableTableIndexesList($buffer, $tableName); } /** * {@inheritDoc} */ protected function _getPortableDatabaseDefinition($database) { return $database['datname']; } /** * {@inheritDoc} * * @deprecated Use {@see listSchemaNames()} instead. */ protected function getPortableNamespaceDefinition(array $namespace) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'PostgreSQLSchemaManager::getPortableNamespaceDefinition() is deprecated,' . ' use PostgreSQLSchemaManager::listSchemaNames() instead.', ); return $namespace['nspname']; } /** * {@inheritDoc} */ protected function _getPortableSequenceDefinition($sequence) { if ($sequence['schemaname'] !== 'public') { $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname']; } else { $sequenceName = $sequence['relname']; } return new Sequence($sequenceName, (int) $sequence['increment_by'], (int) $sequence['min_value']); } /** * {@inheritDoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); if (strtolower($tableColumn['type']) === 'varchar' || strtolower($tableColumn['type']) === 'bpchar') { // get length from varchar definition $length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']); $tableColumn['length'] = $length; } $matches = []; $autoincrement = false; if ( $tableColumn['default'] !== null && preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches) === 1 ) { $tableColumn['sequence'] = $matches[1]; $tableColumn['default'] = null; $autoincrement = true; } if ($tableColumn['default'] !== null) { if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches) === 1) { $tableColumn['default'] = $matches[1]; } elseif (preg_match('/^NULL::/', $tableColumn['default']) === 1) { $tableColumn['default'] = null; } } $length = $tableColumn['length'] ?? null; if ($length === '-1' && isset($tableColumn['atttypmod'])) { $length = $tableColumn['atttypmod'] - 4; } if ((int) $length <= 0) { $length = null; } $fixed = null; if (! isset($tableColumn['name'])) { $tableColumn['name'] = ''; } $precision = null; $scale = null; $jsonb = null; $dbType = strtolower($tableColumn['type']); if ( $tableColumn['domain_type'] !== null && $tableColumn['domain_type'] !== '' && ! $this->_platform->hasDoctrineTypeMappingFor($tableColumn['type']) ) { $dbType = strtolower($tableColumn['domain_type']); $tableColumn['complete_type'] = $tableColumn['domain_complete_type']; } $type = $this->_platform->getDoctrineTypeMapping($dbType); $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); switch ($dbType) { case 'smallint': case 'int2': $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); $length = null; break; case 'int': case 'int4': case 'integer': $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); $length = null; break; case 'bigint': case 'int8': $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); $length = null; break; case 'bool': case 'boolean': if ($tableColumn['default'] === 'true') { $tableColumn['default'] = true; } if ($tableColumn['default'] === 'false') { $tableColumn['default'] = false; } $length = null; break; case 'json': case 'text': case '_varchar': case 'varchar': $tableColumn['default'] = $this->parseDefaultExpression($tableColumn['default']); $fixed = false; break; case 'interval': $fixed = false; break; case 'char': case 'bpchar': $fixed = true; break; case 'float': case 'float4': case 'float8': case 'double': case 'double precision': case 'real': case 'decimal': case 'money': case 'numeric': $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); if ( preg_match( '([A-Za-z]+\(([0-9]+),([0-9]+)\))', $tableColumn['complete_type'], $match, ) === 1 ) { $precision = $match[1]; $scale = $match[2]; $length = null; } break; case 'year': $length = null; break; // PostgreSQL 9.4+ only case 'jsonb': $jsonb = true; break; } if ( $tableColumn['default'] !== null && preg_match( "('([^']+)'::)", $tableColumn['default'], $match, ) === 1 ) { $tableColumn['default'] = $match[1]; } $options = [ 'length' => $length, 'notnull' => (bool) $tableColumn['isnotnull'], 'default' => $tableColumn['default'], 'precision' => $precision, 'scale' => $scale, 'fixed' => $fixed, 'autoincrement' => $autoincrement, 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, ]; $column = new Column($tableColumn['field'], Type::getType($type), $options); if (! empty($tableColumn['collation'])) { $column->setPlatformOption('collation', $tableColumn['collation']); } if ($column->getType()->getName() === Types::JSON) { if (! $column->getType() instanceof JsonType) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5049', <<<'DEPRECATION' %s not extending %s while being named %s is deprecated, and will lead to jsonb never to being used in 4.0., DEPRECATION, get_class($column->getType()), JsonType::class, Types::JSON, ); } $column->setPlatformOption('jsonb', $jsonb); } return $column; } /** * PostgreSQL 9.4 puts parentheses around negative numeric default values that need to be stripped eventually. * * @param mixed $defaultValue * * @return mixed */ private function fixVersion94NegativeNumericDefaultValue($defaultValue) { if ($defaultValue !== null && strpos($defaultValue, '(') === 0) { return trim($defaultValue, '()'); } return $defaultValue; } /** * Parses a default value expression as given by PostgreSQL */ private function parseDefaultExpression(?string $default): ?string { if ($default === null) { return $default; } return str_replace("''", "'", $default); } protected function selectTableNames(string $databaseName): Result { $sql = <<<'SQL' SELECT quote_ident(table_name) AS table_name, table_schema AS schema_name FROM information_schema.tables WHERE table_catalog = ? AND table_schema NOT LIKE 'pg\_%' AND table_schema != 'information_schema' AND table_name != 'geometry_columns' AND table_name != 'spatial_ref_sys' AND table_type = 'BASE TABLE' SQL; return $this->_conn->executeQuery($sql, [$databaseName]); } protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' c.relname AS table_name, n.nspname AS schema_name,'; } $sql .= sprintf(<<<'SQL' a.attnum, quote_ident(a.attname) AS field, t.typname AS type, format_type(a.atttypid, a.atttypmod) AS complete_type, (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation, (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type, (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type, a.attnotnull AS isnotnull, (SELECT 't' FROM pg_index WHERE c.oid = pg_index.indrelid AND pg_index.indkey[0] = a.attnum AND pg_index.indisprimary = 't' ) AS pri, (%s) AS default, (SELECT pg_description.description FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid ) AS comment FROM pg_attribute a INNER JOIN pg_class c ON c.oid = a.attrelid INNER JOIN pg_type t ON t.oid = a.atttypid INNER JOIN pg_namespace n ON n.oid = c.relnamespace LEFT JOIN pg_depend d ON d.objid = c.oid AND d.deptype = 'e' AND d.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') SQL, $this->_platform->getDefaultColumnValueSQLSnippet()); $conditions = array_merge([ 'a.attnum > 0', "c.relkind = 'r'", 'd.refobjid IS NULL', ], $this->buildQueryConditions($tableName)); $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY a.attnum'; return $this->_conn->executeQuery($sql); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,'; } $sql .= <<<'SQL' quote_ident(ic.relname) AS relname, i.indisunique, i.indisprimary, i.indkey, i.indrelid, pg_get_expr(indpred, indrelid) AS "where" FROM pg_index i JOIN pg_class AS tc ON tc.oid = i.indrelid JOIN pg_namespace tn ON tn.oid = tc.relnamespace JOIN pg_class AS ic ON ic.oid = i.indexrelid WHERE ic.oid IN ( SELECT indexrelid FROM pg_index i, pg_class c, pg_namespace n SQL; $conditions = array_merge([ 'c.oid = i.indrelid', 'c.relnamespace = n.oid', ], $this->buildQueryConditions($tableName)); $sql .= ' WHERE ' . implode(' AND ', $conditions) . ')'; return $this->_conn->executeQuery($sql); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,'; } $sql .= <<<'SQL' quote_ident(r.conname) as conname, pg_get_constraintdef(r.oid, true) as condef FROM pg_constraint r JOIN pg_class AS tc ON tc.oid = r.conrelid JOIN pg_namespace tn ON tn.oid = tc.relnamespace WHERE r.conrelid IN ( SELECT c.oid FROM pg_class c, pg_namespace n SQL; $conditions = array_merge(['n.oid = c.relnamespace'], $this->buildQueryConditions($tableName)); $sql .= ' WHERE ' . implode(' AND ', $conditions) . ") AND r.contype = 'f'"; return $this->_conn->executeQuery($sql); } /** * {@inheritDoc} */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { $sql = <<<'SQL' SELECT c.relname, CASE c.relpersistence WHEN 'u' THEN true ELSE false END as unlogged, obj_description(c.oid, 'pg_class') AS comment FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace SQL; $conditions = array_merge(["c.relkind = 'r'"], $this->buildQueryConditions($tableName)); $sql .= ' WHERE ' . implode(' AND ', $conditions); return $this->_conn->fetchAllAssociativeIndexed($sql); } /** * @param string|null $tableName * * @return list<string> */ private function buildQueryConditions($tableName): array { $conditions = []; if ($tableName !== null) { if (strpos($tableName, '.') !== false) { [$schemaName, $tableName] = explode('.', $tableName); $conditions[] = 'n.nspname = ' . $this->_platform->quoteStringLiteral($schemaName); } else { $conditions[] = 'n.nspname = ANY(current_schemas(false))'; } $identifier = new Identifier($tableName); $conditions[] = 'c.relname = ' . $this->_platform->quoteStringLiteral($identifier->getName()); } $conditions[] = "n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')"; return $conditions; } } Schema/TableDiff.php 0000755 00000023161 00000000000 0010244 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function array_filter; use function array_values; use function count; /** * Table Diff. */ class TableDiff { /** * @deprecated Use {@see getOldTable()} instead. * * @var string */ public $name; /** * @deprecated Rename tables via {@link AbstractSchemaManager::renameTable()} instead. * * @var string|false */ public $newName = false; /** * All added columns * * @internal Use {@see getAddedColumns()} instead. * * @var Column[] */ public $addedColumns; /** * All modified columns * * @internal Use {@see getModifiedColumns()} instead. * * @var ColumnDiff[] */ public $changedColumns = []; /** * All dropped columns * * @internal Use {@see getDroppedColumns()} instead. * * @var Column[] */ public $removedColumns = []; /** * Columns that are only renamed from key to column instance name. * * @internal Use {@see getRenamedColumns()} instead. * * @var Column[] */ public $renamedColumns = []; /** * All added indexes. * * @internal Use {@see getAddedIndexes()} instead. * * @var Index[] */ public $addedIndexes = []; /** * All changed indexes. * * @internal Use {@see getModifiedIndexes()} instead. * * @var Index[] */ public $changedIndexes = []; /** * All removed indexes * * @internal Use {@see getDroppedIndexes()} instead. * * @var Index[] */ public $removedIndexes = []; /** * Indexes that are only renamed but are identical otherwise. * * @internal Use {@see getRenamedIndexes()} instead. * * @var Index[] */ public $renamedIndexes = []; /** * All added foreign key definitions * * @internal Use {@see getAddedForeignKeys()} instead. * * @var ForeignKeyConstraint[] */ public $addedForeignKeys = []; /** * All changed foreign keys * * @internal Use {@see getModifiedForeignKeys()} instead. * * @var ForeignKeyConstraint[] */ public $changedForeignKeys = []; /** * All removed foreign keys * * @internal Use {@see getDroppedForeignKeys()} instead. * * @var (ForeignKeyConstraint|string)[] */ public $removedForeignKeys = []; /** * @internal Use {@see getOldTable()} instead. * * @var Table|null */ public $fromTable; /** * Constructs a TableDiff object. * * @internal The diff can be only instantiated by a {@see Comparator}. * * @param string $tableName * @param array<Column> $addedColumns * @param array<ColumnDiff> $modifiedColumns * @param array<Column> $droppedColumns * @param array<Index> $addedIndexes * @param array<Index> $changedIndexes * @param array<Index> $removedIndexes * @param list<ForeignKeyConstraint> $addedForeignKeys * @param list<ForeignKeyConstraint> $changedForeignKeys * @param list<ForeignKeyConstraint|string> $removedForeignKeys * @param array<string,Column> $renamedColumns * @param array<string,Index> $renamedIndexes */ public function __construct( $tableName, $addedColumns = [], $modifiedColumns = [], $droppedColumns = [], $addedIndexes = [], $changedIndexes = [], $removedIndexes = [], ?Table $fromTable = null, $addedForeignKeys = [], $changedForeignKeys = [], $removedForeignKeys = [], $renamedColumns = [], $renamedIndexes = [] ) { $this->name = $tableName; $this->addedColumns = $addedColumns; $this->changedColumns = $modifiedColumns; $this->renamedColumns = $renamedColumns; $this->removedColumns = $droppedColumns; $this->addedIndexes = $addedIndexes; $this->changedIndexes = $changedIndexes; $this->renamedIndexes = $renamedIndexes; $this->removedIndexes = $removedIndexes; $this->addedForeignKeys = $addedForeignKeys; $this->changedForeignKeys = $changedForeignKeys; $this->removedForeignKeys = $removedForeignKeys; if ($fromTable === null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5678', 'Not passing the $fromTable to %s is deprecated.', __METHOD__, ); } $this->fromTable = $fromTable; } /** * @deprecated Use {@see getOldTable()} instead. * * @param AbstractPlatform $platform The platform to use for retrieving this table diff's name. * * @return Identifier */ public function getName(AbstractPlatform $platform) { return new Identifier( $this->fromTable instanceof Table ? $this->fromTable->getQuotedName($platform) : $this->name, ); } /** * @deprecated Rename tables via {@link AbstractSchemaManager::renameTable()} instead. * * @return Identifier|false */ public function getNewName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', '%s is deprecated. Rename tables via AbstractSchemaManager::renameTable() instead.', __METHOD__, ); if ($this->newName === false) { return false; } return new Identifier($this->newName); } public function getOldTable(): ?Table { return $this->fromTable; } /** @return list<Column> */ public function getAddedColumns(): array { return array_values($this->addedColumns); } /** @return list<ColumnDiff> */ public function getModifiedColumns(): array { return array_values($this->changedColumns); } /** @return list<Column> */ public function getDroppedColumns(): array { return array_values($this->removedColumns); } /** @return array<string,Column> */ public function getRenamedColumns(): array { return $this->renamedColumns; } /** @return list<Index> */ public function getAddedIndexes(): array { return array_values($this->addedIndexes); } /** * @internal This method exists only for compatibility with the current implementation of schema managers * that modify the diff while processing it. */ public function unsetAddedIndex(Index $index): void { $this->addedIndexes = array_filter( $this->addedIndexes, static function (Index $addedIndex) use ($index): bool { return $addedIndex !== $index; }, ); } /** @return array<Index> */ public function getModifiedIndexes(): array { return array_values($this->changedIndexes); } /** @return list<Index> */ public function getDroppedIndexes(): array { return array_values($this->removedIndexes); } /** * @internal This method exists only for compatibility with the current implementation of schema managers * that modify the diff while processing it. */ public function unsetDroppedIndex(Index $index): void { $this->removedIndexes = array_filter( $this->removedIndexes, static function (Index $removedIndex) use ($index): bool { return $removedIndex !== $index; }, ); } /** @return array<string,Index> */ public function getRenamedIndexes(): array { return $this->renamedIndexes; } /** @return list<ForeignKeyConstraint> */ public function getAddedForeignKeys(): array { return $this->addedForeignKeys; } /** @return list<ForeignKeyConstraint> */ public function getModifiedForeignKeys(): array { return $this->changedForeignKeys; } /** @return list<ForeignKeyConstraint|string> */ public function getDroppedForeignKeys(): array { return $this->removedForeignKeys; } /** * @internal This method exists only for compatibility with the current implementation of the schema comparator. * * @param ForeignKeyConstraint|string $foreignKey */ public function unsetDroppedForeignKey($foreignKey): void { $this->removedForeignKeys = array_filter( $this->removedForeignKeys, static function ($removedForeignKey) use ($foreignKey): bool { return $removedForeignKey !== $foreignKey; }, ); } /** * Returns whether the diff is empty (contains no changes). */ public function isEmpty(): bool { return count($this->addedColumns) === 0 && count($this->changedColumns) === 0 && count($this->removedColumns) === 0 && count($this->renamedColumns) === 0 && count($this->addedIndexes) === 0 && count($this->changedIndexes) === 0 && count($this->removedIndexes) === 0 && count($this->renamedIndexes) === 0 && count($this->addedForeignKeys) === 0 && count($this->changedForeignKeys) === 0 && count($this->removedForeignKeys) === 0; } } Schema/OracleSchemaManager.php 0000755 00000040061 00000000000 0012243 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_change_key_case; use function array_values; use function implode; use function is_string; use function preg_match; use function str_replace; use function strpos; use function strtolower; use function strtoupper; use function trim; use const CASE_LOWER; /** * Oracle Schema Manager. * * @extends AbstractSchemaManager<OraclePlatform> */ class OracleSchemaManager extends AbstractSchemaManager { /** * {@inheritDoc} */ public function listTableNames() { return $this->doListTableNames(); } /** * {@inheritDoc} */ public function listTables() { return $this->doListTables(); } /** * {@inheritDoc} * * @deprecated Use {@see introspectTable()} instead. */ public function listTableDetails($name) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5595', '%s is deprecated. Use introspectTable() instead.', __METHOD__, ); return $this->doListTableDetails($name); } /** * {@inheritDoc} */ public function listTableColumns($table, $database = null) { return $this->doListTableColumns($table, $database); } /** * {@inheritDoc} */ public function listTableIndexes($table) { return $this->doListTableIndexes($table); } /** * {@inheritDoc} */ public function listTableForeignKeys($table, $database = null) { return $this->doListTableForeignKeys($table, $database); } /** * {@inheritDoc} */ protected function _getPortableViewDefinition($view) { $view = array_change_key_case($view, CASE_LOWER); return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']); } /** * {@inheritDoc} */ protected function _getPortableTableDefinition($table) { $table = array_change_key_case($table, CASE_LOWER); return $this->getQuotedIdentifierName($table['table_name']); } /** * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { $indexBuffer = []; foreach ($tableIndexes as $tableIndex) { $tableIndex = array_change_key_case($tableIndex, CASE_LOWER); $keyName = strtolower($tableIndex['name']); $buffer = []; if ($tableIndex['is_primary'] === 'P') { $keyName = 'primary'; $buffer['primary'] = true; $buffer['non_unique'] = false; } else { $buffer['primary'] = false; $buffer['non_unique'] = ! $tableIndex['is_unique']; } $buffer['key_name'] = $keyName; $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']); $indexBuffer[] = $buffer; } return parent::_getPortableTableIndexesList($indexBuffer, $tableName); } /** * {@inheritDoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); $dbType = strtolower($tableColumn['data_type']); if (strpos($dbType, 'timestamp(') === 0) { if (strpos($dbType, 'with time zone') !== false) { $dbType = 'timestamptz'; } else { $dbType = 'timestamp'; } } $unsigned = $fixed = $precision = $scale = $length = null; if (! isset($tableColumn['column_name'])) { $tableColumn['column_name'] = ''; } // Default values returned from database sometimes have trailing spaces. if (is_string($tableColumn['data_default'])) { $tableColumn['data_default'] = trim($tableColumn['data_default']); } if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') { $tableColumn['data_default'] = null; } if ($tableColumn['data_default'] !== null) { // Default values returned from database are represented as literal expressions if (preg_match('/^\'(.*)\'$/s', $tableColumn['data_default'], $matches) === 1) { $tableColumn['data_default'] = str_replace("''", "'", $matches[1]); } } if ($tableColumn['data_precision'] !== null) { $precision = (int) $tableColumn['data_precision']; } if ($tableColumn['data_scale'] !== null) { $scale = (int) $tableColumn['data_scale']; } $type = $this->_platform->getDoctrineTypeMapping($dbType); $type = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type); $tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type); switch ($dbType) { case 'number': if ($precision === 20 && $scale === 0) { $type = 'bigint'; } elseif ($precision === 5 && $scale === 0) { $type = 'smallint'; } elseif ($precision === 1 && $scale === 0) { $type = 'boolean'; } elseif ($scale > 0) { $type = 'decimal'; } break; case 'varchar': case 'varchar2': case 'nvarchar2': $length = $tableColumn['char_length']; $fixed = false; break; case 'raw': $length = $tableColumn['data_length']; $fixed = true; break; case 'char': case 'nchar': $length = $tableColumn['char_length']; $fixed = true; break; } $options = [ 'notnull' => $tableColumn['nullable'] === 'N', 'fixed' => (bool) $fixed, 'unsigned' => (bool) $unsigned, 'default' => $tableColumn['data_default'], 'length' => $length, 'precision' => $precision, 'scale' => $scale, 'comment' => isset($tableColumn['comments']) && $tableColumn['comments'] !== '' ? $tableColumn['comments'] : null, ]; return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $list = []; foreach ($tableForeignKeys as $value) { $value = array_change_key_case($value, CASE_LOWER); if (! isset($list[$value['constraint_name']])) { if ($value['delete_rule'] === 'NO ACTION') { $value['delete_rule'] = null; } $list[$value['constraint_name']] = [ 'name' => $this->getQuotedIdentifierName($value['constraint_name']), 'local' => [], 'foreign' => [], 'foreignTable' => $value['references_table'], 'onDelete' => $value['delete_rule'], ]; } $localColumn = $this->getQuotedIdentifierName($value['local_column']); $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']); $list[$value['constraint_name']]['local'][$value['position']] = $localColumn; $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn; } return parent::_getPortableTableForeignKeysList($list); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint { return new ForeignKeyConstraint( array_values($tableForeignKey['local']), $this->getQuotedIdentifierName($tableForeignKey['foreignTable']), array_values($tableForeignKey['foreign']), $this->getQuotedIdentifierName($tableForeignKey['name']), ['onDelete' => $tableForeignKey['onDelete']], ); } /** * {@inheritDoc} */ protected function _getPortableSequenceDefinition($sequence) { $sequence = array_change_key_case($sequence, CASE_LOWER); return new Sequence( $this->getQuotedIdentifierName($sequence['sequence_name']), (int) $sequence['increment_by'], (int) $sequence['min_value'], ); } /** * {@inheritDoc} */ protected function _getPortableDatabaseDefinition($database) { $database = array_change_key_case($database, CASE_LOWER); return $database['username']; } /** * {@inheritDoc} */ public function createDatabase($database) { $statement = $this->_platform->getCreateDatabaseSQL($database); $params = $this->_conn->getParams(); if (isset($params['password'])) { $statement .= ' IDENTIFIED BY ' . $params['password']; } $this->_conn->executeStatement($statement); $statement = 'GRANT DBA TO ' . $database; $this->_conn->executeStatement($statement); } /** * @internal The method should be only used from within the OracleSchemaManager class hierarchy. * * @param string $table * * @return bool * * @throws Exception */ public function dropAutoincrement($table) { $sql = $this->_platform->getDropAutoincrementSql($table); foreach ($sql as $query) { $this->_conn->executeStatement($query); } return true; } /** * {@inheritDoc} */ public function dropTable($name) { $this->tryMethod('dropAutoincrement', $name); parent::dropTable($name); } /** * Returns the quoted representation of the given identifier name. * * Quotes non-uppercase identifiers explicitly to preserve case * and thus make references to the particular identifier work. * * @param string $identifier The identifier to quote. */ private function getQuotedIdentifierName($identifier): string { if (preg_match('/[a-z]/', $identifier) === 1) { return $this->_platform->quoteIdentifier($identifier); } return $identifier; } protected function selectTableNames(string $databaseName): Result { $sql = <<<'SQL' SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = :OWNER ORDER BY TABLE_NAME SQL; return $this->_conn->executeQuery($sql, ['OWNER' => $databaseName]); } protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' C.TABLE_NAME,'; } $sql .= <<<'SQL' C.COLUMN_NAME, C.DATA_TYPE, C.DATA_DEFAULT, C.DATA_PRECISION, C.DATA_SCALE, C.CHAR_LENGTH, C.DATA_LENGTH, C.NULLABLE, D.COMMENTS FROM ALL_TAB_COLUMNS C INNER JOIN ALL_TABLES T ON T.OWNER = C.OWNER AND T.TABLE_NAME = C.TABLE_NAME LEFT JOIN ALL_COL_COMMENTS D ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND D.COLUMN_NAME = C.COLUMN_NAME SQL; $conditions = ['C.OWNER = :OWNER']; $params = ['OWNER' => $databaseName]; if ($tableName !== null) { $conditions[] = 'C.TABLE_NAME = :TABLE_NAME'; $params['TABLE_NAME'] = $tableName; } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.COLUMN_ID'; return $this->_conn->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' IND_COL.TABLE_NAME,'; } $sql .= <<<'SQL' IND_COL.INDEX_NAME AS NAME, IND.INDEX_TYPE AS TYPE, DECODE(IND.UNIQUENESS, 'NONUNIQUE', 0, 'UNIQUE', 1) AS IS_UNIQUE, IND_COL.COLUMN_NAME, IND_COL.COLUMN_POSITION AS COLUMN_POS, CON.CONSTRAINT_TYPE AS IS_PRIMARY FROM ALL_IND_COLUMNS IND_COL LEFT JOIN ALL_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME LEFT JOIN ALL_CONSTRAINTS CON ON CON.OWNER = IND_COL.INDEX_OWNER AND CON.INDEX_NAME = IND_COL.INDEX_NAME SQL; $conditions = ['IND_COL.INDEX_OWNER = :OWNER']; $params = ['OWNER' => $databaseName]; if ($tableName !== null) { $conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME'; $params['TABLE_NAME'] = $tableName; } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME' . ', IND_COL.COLUMN_POSITION'; return $this->_conn->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' COLS.TABLE_NAME,'; } $sql .= <<<'SQL' ALC.CONSTRAINT_NAME, ALC.DELETE_RULE, COLS.COLUMN_NAME LOCAL_COLUMN, COLS.POSITION, R_COLS.TABLE_NAME REFERENCES_TABLE, R_COLS.COLUMN_NAME FOREIGN_COLUMN FROM ALL_CONS_COLUMNS COLS LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME LEFT JOIN ALL_CONS_COLUMNS R_COLS ON R_COLS.OWNER = ALC.R_OWNER AND R_COLS.CONSTRAINT_NAME = ALC.R_CONSTRAINT_NAME AND R_COLS.POSITION = COLS.POSITION SQL; $conditions = ["ALC.CONSTRAINT_TYPE = 'R'", 'COLS.OWNER = :OWNER']; $params = ['OWNER' => $databaseName]; if ($tableName !== null) { $conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME'; $params['TABLE_NAME'] = $tableName; } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME' . ', COLS.POSITION'; return $this->_conn->executeQuery($sql, $params); } /** * {@inheritDoc} */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { $sql = 'SELECT TABLE_NAME, COMMENTS'; $conditions = ['OWNER = :OWNER']; $params = ['OWNER' => $databaseName]; if ($tableName !== null) { $conditions[] = 'TABLE_NAME = :TABLE_NAME'; $params['TABLE_NAME'] = $tableName; } $sql .= ' FROM ALL_TAB_COMMENTS WHERE ' . implode(' AND ', $conditions); /** @var array<string,array<string,mixed>> $metadata */ $metadata = $this->_conn->executeQuery($sql, $params) ->fetchAllAssociativeIndexed(); $tableOptions = []; foreach ($metadata as $table => $data) { $data = array_change_key_case($data, CASE_LOWER); $tableOptions[$table] = [ 'comment' => $data['comments'], ]; } return $tableOptions; } protected function normalizeName(string $name): string { $identifier = new Identifier($name); return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name); } } Schema/Comparator.php 0000755 00000057062 00000000000 0010542 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use BadMethodCallException; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types; use Doctrine\Deprecations\Deprecation; use function array_intersect_key; use function array_key_exists; use function array_keys; use function array_map; use function array_merge; use function array_unique; use function assert; use function count; use function get_class; use function sprintf; use function strtolower; /** * Compares two Schemas and return an instance of SchemaDiff. * * @method SchemaDiff compareSchemas(Schema $fromSchema, Schema $toSchema) */ class Comparator { private ?AbstractPlatform $platform; /** @internal The comparator can be only instantiated by a schema manager. */ public function __construct(?AbstractPlatform $platform = null) { if ($platform === null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4746', 'Not passing a $platform to %s is deprecated.' . ' Use AbstractSchemaManager::createComparator() to instantiate the comparator.', __METHOD__, ); } $this->platform = $platform; } /** @param list<mixed> $args */ public function __call(string $method, array $args): SchemaDiff { if ($method !== 'compareSchemas') { throw new BadMethodCallException(sprintf('Unknown method "%s"', $method)); } return $this->doCompareSchemas(...$args); } /** @param list<mixed> $args */ public static function __callStatic(string $method, array $args): SchemaDiff { if ($method !== 'compareSchemas') { throw new BadMethodCallException(sprintf('Unknown method "%s"', $method)); } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4707', 'Calling %s::%s() statically is deprecated.', self::class, $method, ); $comparator = new self(); return $comparator->doCompareSchemas(...$args); } /** * Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema. * * This method should be called non-statically since it will be declared as non-static in the next major release. * * @return SchemaDiff * * @throws SchemaException */ private function doCompareSchemas( Schema $fromSchema, Schema $toSchema ) { $createdSchemas = []; $droppedSchemas = []; $createdTables = []; $alteredTables = []; $droppedTables = []; $createdSequences = []; $alteredSequences = []; $droppedSequences = []; $orphanedForeignKeys = []; $foreignKeysToTable = []; foreach ($toSchema->getNamespaces() as $namespace) { if ($fromSchema->hasNamespace($namespace)) { continue; } $createdSchemas[$namespace] = $namespace; } foreach ($fromSchema->getNamespaces() as $namespace) { if ($toSchema->hasNamespace($namespace)) { continue; } $droppedSchemas[$namespace] = $namespace; } foreach ($toSchema->getTables() as $table) { $tableName = $table->getShortestName($toSchema->getName()); if (! $fromSchema->hasTable($tableName)) { $createdTables[$tableName] = $toSchema->getTable($tableName); } else { $tableDifferences = $this->diffTable( $fromSchema->getTable($tableName), $toSchema->getTable($tableName), ); if ($tableDifferences !== false) { $alteredTables[$tableName] = $tableDifferences; } } } /* Check if there are tables removed */ foreach ($fromSchema->getTables() as $table) { $tableName = $table->getShortestName($fromSchema->getName()); $table = $fromSchema->getTable($tableName); if (! $toSchema->hasTable($tableName)) { $droppedTables[$tableName] = $table; } // also remember all foreign keys that point to a specific table foreach ($table->getForeignKeys() as $foreignKey) { $foreignTable = strtolower($foreignKey->getForeignTableName()); if (! isset($foreignKeysToTable[$foreignTable])) { $foreignKeysToTable[$foreignTable] = []; } $foreignKeysToTable[$foreignTable][] = $foreignKey; } } foreach ($droppedTables as $tableName => $table) { if (! isset($foreignKeysToTable[$tableName])) { continue; } foreach ($foreignKeysToTable[$tableName] as $foreignKey) { if (isset($droppedTables[strtolower($foreignKey->getLocalTableName())])) { continue; } $orphanedForeignKeys[] = $foreignKey; } // deleting duplicated foreign keys present on both on the orphanedForeignKey // and the removedForeignKeys from changedTables foreach ($foreignKeysToTable[$tableName] as $foreignKey) { // strtolower the table name to make if compatible with getShortestName $localTableName = strtolower($foreignKey->getLocalTableName()); if (! isset($alteredTables[$localTableName])) { continue; } foreach ($alteredTables[$localTableName]->getDroppedForeignKeys() as $droppedForeignKey) { assert($droppedForeignKey instanceof ForeignKeyConstraint); // We check if the key is from the removed table if not we skip. if ($tableName !== strtolower($droppedForeignKey->getForeignTableName())) { continue; } $alteredTables[$localTableName]->unsetDroppedForeignKey($droppedForeignKey); } } } foreach ($toSchema->getSequences() as $sequence) { $sequenceName = $sequence->getShortestName($toSchema->getName()); if (! $fromSchema->hasSequence($sequenceName)) { if (! $this->isAutoIncrementSequenceInSchema($fromSchema, $sequence)) { $createdSequences[] = $sequence; } } else { if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) { $alteredSequences[] = $toSchema->getSequence($sequenceName); } } } foreach ($fromSchema->getSequences() as $sequence) { if ($this->isAutoIncrementSequenceInSchema($toSchema, $sequence)) { continue; } $sequenceName = $sequence->getShortestName($fromSchema->getName()); if ($toSchema->hasSequence($sequenceName)) { continue; } $droppedSequences[] = $sequence; } $diff = new SchemaDiff( $createdTables, $alteredTables, $droppedTables, $fromSchema, $createdSchemas, $droppedSchemas, $createdSequences, $alteredSequences, $droppedSequences, ); $diff->orphanedForeignKeys = $orphanedForeignKeys; return $diff; } /** * @deprecated Use non-static call to {@see compareSchemas()} instead. * * @return SchemaDiff * * @throws SchemaException */ public function compare(Schema $fromSchema, Schema $toSchema) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4707', 'Method compare() is deprecated. Use a non-static call to compareSchemas() instead.', ); return $this->compareSchemas($fromSchema, $toSchema); } /** * @param Schema $schema * @param Sequence $sequence */ private function isAutoIncrementSequenceInSchema($schema, $sequence): bool { foreach ($schema->getTables() as $table) { if ($sequence->isAutoIncrementsFor($table)) { return true; } } return false; } /** @return bool */ public function diffSequence(Sequence $sequence1, Sequence $sequence2) { if ($sequence1->getAllocationSize() !== $sequence2->getAllocationSize()) { return true; } return $sequence1->getInitialValue() !== $sequence2->getInitialValue(); } /** * Returns the difference between the tables $fromTable and $toTable. * * If there are no differences this method returns the boolean false. * * @deprecated Use {@see compareTables()} and, optionally, {@see TableDiff::isEmpty()} instead. * * @return TableDiff|false * * @throws Exception */ public function diffTable(Table $fromTable, Table $toTable) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5770', '%s is deprecated. Use compareTables() instead.', __METHOD__, ); $diff = $this->compareTables($fromTable, $toTable); if ($diff->isEmpty()) { return false; } return $diff; } /** * Compares the tables and returns the difference between them. * * @throws Exception */ public function compareTables(Table $fromTable, Table $toTable): TableDiff { $addedColumns = []; $modifiedColumns = []; $droppedColumns = []; $addedIndexes = []; $modifiedIndexes = []; $droppedIndexes = []; $addedForeignKeys = []; $modifiedForeignKeys = []; $droppedForeignKeys = []; $fromTableColumns = $fromTable->getColumns(); $toTableColumns = $toTable->getColumns(); /* See if all the columns in "from" table exist in "to" table */ foreach ($toTableColumns as $columnName => $column) { if ($fromTable->hasColumn($columnName)) { continue; } $addedColumns[$columnName] = $column; } /* See if there are any removed columns in "to" table */ foreach ($fromTableColumns as $columnName => $column) { // See if column is removed in "to" table. if (! $toTable->hasColumn($columnName)) { $droppedColumns[$columnName] = $column; continue; } $toColumn = $toTable->getColumn($columnName); // See if column has changed properties in "to" table. $changedProperties = $this->diffColumn($column, $toColumn); if ($this->platform !== null) { if ($this->columnsEqual($column, $toColumn)) { continue; } } elseif (count($changedProperties) === 0) { continue; } $modifiedColumns[$column->getName()] = new ColumnDiff( $column->getName(), $toColumn, $changedProperties, $column, ); } $renamedColumns = $this->detectRenamedColumns($addedColumns, $droppedColumns); $fromTableIndexes = $fromTable->getIndexes(); $toTableIndexes = $toTable->getIndexes(); /* See if all the indexes in "from" table exist in "to" table */ foreach ($toTableIndexes as $indexName => $index) { if (($index->isPrimary() && $fromTable->getPrimaryKey() !== null) || $fromTable->hasIndex($indexName)) { continue; } $addedIndexes[$indexName] = $index; } /* See if there are any removed indexes in "to" table */ foreach ($fromTableIndexes as $indexName => $index) { // See if index is removed in "to" table. if ( ($index->isPrimary() && $toTable->getPrimaryKey() === null) || ! $index->isPrimary() && ! $toTable->hasIndex($indexName) ) { $droppedIndexes[$indexName] = $index; continue; } // See if index has changed in "to" table. $toTableIndex = $index->isPrimary() ? $toTable->getPrimaryKey() : $toTable->getIndex($indexName); assert($toTableIndex instanceof Index); if (! $this->diffIndex($index, $toTableIndex)) { continue; } $modifiedIndexes[$indexName] = $toTableIndex; } $renamedIndexes = $this->detectRenamedIndexes($addedIndexes, $droppedIndexes); $fromForeignKeys = $fromTable->getForeignKeys(); $toForeignKeys = $toTable->getForeignKeys(); foreach ($fromForeignKeys as $fromKey => $fromConstraint) { foreach ($toForeignKeys as $toKey => $toConstraint) { if ($this->diffForeignKey($fromConstraint, $toConstraint) === false) { unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]); } else { if (strtolower($fromConstraint->getName()) === strtolower($toConstraint->getName())) { $modifiedForeignKeys[] = $toConstraint; unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]); } } } } foreach ($fromForeignKeys as $fromConstraint) { $droppedForeignKeys[] = $fromConstraint; } foreach ($toForeignKeys as $toConstraint) { $addedForeignKeys[] = $toConstraint; } return new TableDiff( $toTable->getName(), $addedColumns, $modifiedColumns, $droppedColumns, $addedIndexes, $modifiedIndexes, $droppedIndexes, $fromTable, $addedForeignKeys, $modifiedForeignKeys, $droppedForeignKeys, $renamedColumns, $renamedIndexes, ); } /** * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop * however ambiguities between different possibilities should not lead to renaming at all. * * @param array<string,Column> $addedColumns * @param array<string,Column> $removedColumns * * @return array<string,Column> * * @throws Exception */ private function detectRenamedColumns(array &$addedColumns, array &$removedColumns): array { $candidatesByName = []; foreach ($addedColumns as $addedColumnName => $addedColumn) { foreach ($removedColumns as $removedColumn) { if (! $this->columnsEqual($addedColumn, $removedColumn)) { continue; } $candidatesByName[$addedColumn->getName()][] = [$removedColumn, $addedColumn, $addedColumnName]; } } $renamedColumns = []; foreach ($candidatesByName as $candidates) { if (count($candidates) !== 1) { continue; } [$removedColumn, $addedColumn] = $candidates[0]; $removedColumnName = $removedColumn->getName(); $addedColumnName = strtolower($addedColumn->getName()); if (isset($renamedColumns[$removedColumnName])) { continue; } $renamedColumns[$removedColumnName] = $addedColumn; unset( $addedColumns[$addedColumnName], $removedColumns[strtolower($removedColumnName)], ); } return $renamedColumns; } /** * Try to find indexes that only changed their name, rename operations maybe cheaper than add/drop * however ambiguities between different possibilities should not lead to renaming at all. * * @param array<string,Index> $addedIndexes * @param array<string,Index> $removedIndexes * * @return array<string,Index> */ private function detectRenamedIndexes(array &$addedIndexes, array &$removedIndexes): array { $candidatesByName = []; // Gather possible rename candidates by comparing each added and removed index based on semantics. foreach ($addedIndexes as $addedIndexName => $addedIndex) { foreach ($removedIndexes as $removedIndex) { if ($this->diffIndex($addedIndex, $removedIndex)) { continue; } $candidatesByName[$addedIndex->getName()][] = [$removedIndex, $addedIndex, $addedIndexName]; } } $renamedIndexes = []; foreach ($candidatesByName as $candidates) { // If the current rename candidate contains exactly one semantically equal index, // we can safely rename it. // Otherwise, it is unclear if a rename action is really intended, // therefore we let those ambiguous indexes be added/dropped. if (count($candidates) !== 1) { continue; } [$removedIndex, $addedIndex] = $candidates[0]; $removedIndexName = strtolower($removedIndex->getName()); $addedIndexName = strtolower($addedIndex->getName()); if (isset($renamedIndexes[$removedIndexName])) { continue; } $renamedIndexes[$removedIndexName] = $addedIndex; unset( $addedIndexes[$addedIndexName], $removedIndexes[$removedIndexName], ); } return $renamedIndexes; } /** * @internal The method should be only used from within the {@see Comparator} class hierarchy. * * @return bool */ public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2) { if ( array_map('strtolower', $key1->getUnquotedLocalColumns()) !== array_map('strtolower', $key2->getUnquotedLocalColumns()) ) { return true; } if ( array_map('strtolower', $key1->getUnquotedForeignColumns()) !== array_map('strtolower', $key2->getUnquotedForeignColumns()) ) { return true; } if ($key1->getUnqualifiedForeignTableName() !== $key2->getUnqualifiedForeignTableName()) { return true; } if ($key1->onUpdate() !== $key2->onUpdate()) { return true; } return $key1->onDelete() !== $key2->onDelete(); } /** * Compares the definitions of the given columns * * @internal The method should be only used from within the {@see Comparator} class hierarchy. * * @throws Exception */ public function columnsEqual(Column $column1, Column $column2): bool { if ($this->platform === null) { return $this->diffColumn($column1, $column2) === []; } return $this->platform->columnsEqual($column1, $column2); } /** * Returns the difference between the columns * * If there are differences this method returns the changed properties as a * string array, otherwise an empty array gets returned. * * @deprecated Use {@see columnsEqual()} instead. * * @return string[] */ public function diffColumn(Column $column1, Column $column2) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5650', '%s is deprecated. Use diffTable() instead.', __METHOD__, ); $properties1 = $column1->toArray(); $properties2 = $column2->toArray(); $changedProperties = []; if (get_class($properties1['type']) !== get_class($properties2['type'])) { $changedProperties[] = 'type'; } foreach (['notnull', 'unsigned', 'autoincrement'] as $property) { if ($properties1[$property] === $properties2[$property]) { continue; } $changedProperties[] = $property; } // Null values need to be checked additionally as they tell whether to create or drop a default value. // null != 0, null != false, null != '' etc. This affects platform's table alteration SQL generation. if ( ($properties1['default'] === null) !== ($properties2['default'] === null) || $properties1['default'] != $properties2['default'] ) { $changedProperties[] = 'default'; } if ( ($properties1['type'] instanceof Types\StringType && ! $properties1['type'] instanceof Types\GuidType) || $properties1['type'] instanceof Types\BinaryType ) { // check if value of length is set at all, default value assumed otherwise. $length1 = $properties1['length'] ?? 255; $length2 = $properties2['length'] ?? 255; if ($length1 !== $length2) { $changedProperties[] = 'length'; } if ($properties1['fixed'] !== $properties2['fixed']) { $changedProperties[] = 'fixed'; } } elseif ($properties1['type'] instanceof Types\DecimalType) { if (($properties1['precision'] ?? 10) !== ($properties2['precision'] ?? 10)) { $changedProperties[] = 'precision'; } if ($properties1['scale'] !== $properties2['scale']) { $changedProperties[] = 'scale'; } } // A null value and an empty string are actually equal for a comment so they should not trigger a change. if ( $properties1['comment'] !== $properties2['comment'] && ! ($properties1['comment'] === null && $properties2['comment'] === '') && ! ($properties2['comment'] === null && $properties1['comment'] === '') ) { $changedProperties[] = 'comment'; } $customOptions1 = $column1->getCustomSchemaOptions(); $customOptions2 = $column2->getCustomSchemaOptions(); foreach (array_merge(array_keys($customOptions1), array_keys($customOptions2)) as $key) { if (! array_key_exists($key, $properties1) || ! array_key_exists($key, $properties2)) { $changedProperties[] = $key; } elseif ($properties1[$key] !== $properties2[$key]) { $changedProperties[] = $key; } } $platformOptions1 = $column1->getPlatformOptions(); $platformOptions2 = $column2->getPlatformOptions(); foreach (array_keys(array_intersect_key($platformOptions1, $platformOptions2)) as $key) { if ($properties1[$key] === $properties2[$key]) { continue; } $changedProperties[] = $key; } return array_unique($changedProperties); } /** * Finds the difference between the indexes $index1 and $index2. * * Compares $index1 with $index2 and returns true if there are any * differences or false in case there are no differences. * * @internal The method should be only used from within the {@see Comparator} class hierarchy. * * @return bool */ public function diffIndex(Index $index1, Index $index2) { return ! ($index1->isFulfilledBy($index2) && $index2->isFulfilledBy($index1)); } } Schema/Table.php 0000755 00000067434 00000000000 0007466 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\Exception\InvalidTableName; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_filter; use function array_keys; use function array_merge; use function in_array; use function preg_match; use function strlen; use function strtolower; use const ARRAY_FILTER_USE_KEY; /** * Object Representation of a table. */ class Table extends AbstractAsset { /** @var Column[] */ protected $_columns = []; /** @var Index[] */ protected $_indexes = []; /** @var string|null */ protected $_primaryKeyName; /** @var UniqueConstraint[] */ protected $uniqueConstraints = []; /** @var ForeignKeyConstraint[] */ protected $_fkConstraints = []; /** @var mixed[] */ protected $_options = [ 'create_options' => [], ]; /** @var SchemaConfig|null */ protected $_schemaConfig; /** @var Index[] */ private array $implicitIndexes = []; /** * @param Column[] $columns * @param Index[] $indexes * @param UniqueConstraint[] $uniqueConstraints * @param ForeignKeyConstraint[] $fkConstraints * @param mixed[] $options * * @throws SchemaException * @throws Exception */ public function __construct( string $name, array $columns = [], array $indexes = [], array $uniqueConstraints = [], array $fkConstraints = [], array $options = [] ) { if ($name === '') { throw InvalidTableName::new($name); } $this->_setName($name); foreach ($columns as $column) { $this->_addColumn($column); } foreach ($indexes as $idx) { $this->_addIndex($idx); } foreach ($uniqueConstraints as $uniqueConstraint) { $this->_addUniqueConstraint($uniqueConstraint); } foreach ($fkConstraints as $constraint) { $this->_addForeignKeyConstraint($constraint); } $this->_options = array_merge($this->_options, $options); } /** @return void */ public function setSchemaConfig(SchemaConfig $schemaConfig) { $this->_schemaConfig = $schemaConfig; } /** @return int */ protected function _getMaxIdentifierLength() { if ($this->_schemaConfig instanceof SchemaConfig) { return $this->_schemaConfig->getMaxIdentifierLength(); } return 63; } /** * Sets the Primary Key. * * @param string[] $columnNames * @param string|false $indexName * * @return self * * @throws SchemaException */ public function setPrimaryKey(array $columnNames, $indexName = false) { if ($indexName === false) { $indexName = 'primary'; } $this->_addIndex($this->_createIndex($columnNames, $indexName, true, true)); foreach ($columnNames as $columnName) { $column = $this->getColumn($columnName); $column->setNotnull(true); } return $this; } /** * @param string[] $columnNames * @param string[] $flags * @param mixed[] $options * * @return self * * @throws SchemaException */ public function addIndex(array $columnNames, ?string $indexName = null, array $flags = [], array $options = []) { $indexName ??= $this->_generateIdentifierName( array_merge([$this->getName()], $columnNames), 'idx', $this->_getMaxIdentifierLength(), ); return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options)); } /** * @param string[] $columnNames * @param string[] $flags * @param mixed[] $options * * @return self */ public function addUniqueConstraint( array $columnNames, ?string $indexName = null, array $flags = [], array $options = [] ): Table { $indexName ??= $this->_generateIdentifierName( array_merge([$this->getName()], $columnNames), 'uniq', $this->_getMaxIdentifierLength(), ); return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $flags, $options)); } /** * Drops the primary key from this table. * * @return void * * @throws SchemaException */ public function dropPrimaryKey() { if ($this->_primaryKeyName === null) { return; } $this->dropIndex($this->_primaryKeyName); $this->_primaryKeyName = null; } /** * Drops an index from this table. * * @param string $name The index name. * * @return void * * @throws SchemaException If the index does not exist. */ public function dropIndex($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasIndex($name)) { throw SchemaException::indexDoesNotExist($name, $this->_name); } unset($this->_indexes[$name]); } /** * @param string[] $columnNames * @param string|null $indexName * @param mixed[] $options * * @return self * * @throws SchemaException */ public function addUniqueIndex(array $columnNames, $indexName = null, array $options = []) { $indexName ??= $this->_generateIdentifierName( array_merge([$this->getName()], $columnNames), 'uniq', $this->_getMaxIdentifierLength(), ); return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options)); } /** * Renames an index. * * @param string $oldName The name of the index to rename from. * @param string|null $newName The name of the index to rename to. * If null is given, the index name will be auto-generated. * * @return self This table instance. * * @throws SchemaException If no index exists for the given current name * or if an index with the given new name already exists on this table. */ public function renameIndex($oldName, $newName = null) { $oldName = $this->normalizeIdentifier($oldName); $normalizedNewName = $this->normalizeIdentifier($newName); if ($oldName === $normalizedNewName) { return $this; } if (! $this->hasIndex($oldName)) { throw SchemaException::indexDoesNotExist($oldName, $this->_name); } if ($this->hasIndex($normalizedNewName)) { throw SchemaException::indexAlreadyExists($normalizedNewName, $this->_name); } $oldIndex = $this->_indexes[$oldName]; if ($oldIndex->isPrimary()) { $this->dropPrimaryKey(); return $this->setPrimaryKey($oldIndex->getColumns(), $newName ?? false); } unset($this->_indexes[$oldName]); if ($oldIndex->isUnique()) { return $this->addUniqueIndex($oldIndex->getColumns(), $newName, $oldIndex->getOptions()); } return $this->addIndex($oldIndex->getColumns(), $newName, $oldIndex->getFlags(), $oldIndex->getOptions()); } /** * Checks if an index begins in the order of the given columns. * * @param string[] $columnNames * * @return bool */ public function columnsAreIndexed(array $columnNames) { foreach ($this->getIndexes() as $index) { if ($index->spansColumns($columnNames)) { return true; } } return false; } /** * @param string[] $columnNames * @param string $indexName * @param bool $isUnique * @param bool $isPrimary * @param string[] $flags * @param mixed[] $options * * @throws SchemaException */ private function _createIndex( array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [] ): Index { if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) { throw SchemaException::indexNameInvalid($indexName); } foreach ($columnNames as $columnName) { if (! $this->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $this->_name); } } return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options); } /** * @param string $name * @param string $typeName * @param mixed[] $options * * @return Column * * @throws SchemaException */ public function addColumn($name, $typeName, array $options = []) { $column = new Column($name, Type::getType($typeName), $options); $this->_addColumn($column); return $column; } /** * Change Column Details. * * @deprecated Use {@link modifyColumn()} instead. * * @param string $name * @param mixed[] $options * * @return self * * @throws SchemaException */ public function changeColumn($name, array $options) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5747', '%s is deprecated. Use modifyColumn() instead.', __METHOD__, ); return $this->modifyColumn($name, $options); } /** * @param string $name * @param mixed[] $options * * @return self * * @throws SchemaException */ public function modifyColumn($name, array $options) { $column = $this->getColumn($name); $column->setOptions($options); return $this; } /** * Drops a Column from the Table. * * @param string $name * * @return self */ public function dropColumn($name) { $name = $this->normalizeIdentifier($name); unset($this->_columns[$name]); return $this; } /** * Adds a foreign key constraint. * * Name is inferred from the local columns. * * @param Table|string $foreignTable Table schema instance or table name * @param string[] $localColumnNames * @param string[] $foreignColumnNames * @param mixed[] $options * @param string|null $name * * @return self * * @throws SchemaException */ public function addForeignKeyConstraint( $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [], $name = null ) { $name ??= $this->_generateIdentifierName( array_merge([$this->getName()], $localColumnNames), 'fk', $this->_getMaxIdentifierLength(), ); if ($foreignTable instanceof Table) { foreach ($foreignColumnNames as $columnName) { if (! $foreignTable->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName()); } } } foreach ($localColumnNames as $columnName) { if (! $this->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $this->_name); } } $constraint = new ForeignKeyConstraint( $localColumnNames, $foreignTable, $foreignColumnNames, $name, $options, ); return $this->_addForeignKeyConstraint($constraint); } /** * @param string $name * @param mixed $value * * @return self */ public function addOption($name, $value) { $this->_options[$name] = $value; return $this; } /** * @return void * * @throws SchemaException */ protected function _addColumn(Column $column) { $columnName = $column->getName(); $columnName = $this->normalizeIdentifier($columnName); if (isset($this->_columns[$columnName])) { throw SchemaException::columnAlreadyExists($this->getName(), $columnName); } $this->_columns[$columnName] = $column; } /** * Adds an index to the table. * * @return self * * @throws SchemaException */ protected function _addIndex(Index $indexCandidate) { $indexName = $indexCandidate->getName(); $indexName = $this->normalizeIdentifier($indexName); $replacedImplicitIndexes = []; foreach ($this->implicitIndexes as $name => $implicitIndex) { if (! $implicitIndex->isFulfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) { continue; } $replacedImplicitIndexes[] = $name; } if ( (isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) || ($this->_primaryKeyName !== null && $indexCandidate->isPrimary()) ) { throw SchemaException::indexAlreadyExists($indexName, $this->_name); } foreach ($replacedImplicitIndexes as $name) { unset($this->_indexes[$name], $this->implicitIndexes[$name]); } if ($indexCandidate->isPrimary()) { $this->_primaryKeyName = $indexName; } $this->_indexes[$indexName] = $indexCandidate; return $this; } /** @return self */ protected function _addUniqueConstraint(UniqueConstraint $constraint): Table { $mergedNames = array_merge([$this->getName()], $constraint->getColumns()); $name = strlen($constraint->getName()) > 0 ? $constraint->getName() : $this->_generateIdentifierName($mergedNames, 'fk', $this->_getMaxIdentifierLength()); $name = $this->normalizeIdentifier($name); $this->uniqueConstraints[$name] = $constraint; // If there is already an index that fulfills this requirements drop the request. In the case of __construct // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates. // This creates computation overhead in this case, however no duplicate indexes are ever added (column based). $indexName = $this->_generateIdentifierName($mergedNames, 'idx', $this->_getMaxIdentifierLength()); $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, true, false); foreach ($this->_indexes as $existingIndex) { if ($indexCandidate->isFulfilledBy($existingIndex)) { return $this; } } $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate; return $this; } /** @return self */ protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint) { $constraint->setLocalTable($this); if (strlen($constraint->getName()) > 0) { $name = $constraint->getName(); } else { $name = $this->_generateIdentifierName( array_merge([$this->getName()], $constraint->getLocalColumns()), 'fk', $this->_getMaxIdentifierLength(), ); } $name = $this->normalizeIdentifier($name); $this->_fkConstraints[$name] = $constraint; /* Add an implicit index (defined by the DBAL) on the foreign key columns. If there is already a user-defined index that fulfills these requirements drop the request. In the case of __construct() calling this method during hydration from schema-details, all the explicitly added indexes lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns). */ $indexName = $this->_generateIdentifierName( array_merge([$this->getName()], $constraint->getColumns()), 'idx', $this->_getMaxIdentifierLength(), ); $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false); foreach ($this->_indexes as $existingIndex) { if ($indexCandidate->isFulfilledBy($existingIndex)) { return $this; } } $this->_addIndex($indexCandidate); $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate; return $this; } /** * Returns whether this table has a foreign key constraint with the given name. * * @param string $name * * @return bool */ public function hasForeignKey($name) { $name = $this->normalizeIdentifier($name); return isset($this->_fkConstraints[$name]); } /** * Returns the foreign key constraint with the given name. * * @param string $name The constraint name. * * @return ForeignKeyConstraint * * @throws SchemaException If the foreign key does not exist. */ public function getForeignKey($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasForeignKey($name)) { throw SchemaException::foreignKeyDoesNotExist($name, $this->_name); } return $this->_fkConstraints[$name]; } /** * Removes the foreign key constraint with the given name. * * @param string $name The constraint name. * * @return void * * @throws SchemaException */ public function removeForeignKey($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasForeignKey($name)) { throw SchemaException::foreignKeyDoesNotExist($name, $this->_name); } unset($this->_fkConstraints[$name]); } /** * Returns whether this table has a unique constraint with the given name. */ public function hasUniqueConstraint(string $name): bool { $name = $this->normalizeIdentifier($name); return isset($this->uniqueConstraints[$name]); } /** * Returns the unique constraint with the given name. * * @throws SchemaException If the unique constraint does not exist. */ public function getUniqueConstraint(string $name): UniqueConstraint { $name = $this->normalizeIdentifier($name); if (! $this->hasUniqueConstraint($name)) { throw SchemaException::uniqueConstraintDoesNotExist($name, $this->_name); } return $this->uniqueConstraints[$name]; } /** * Removes the unique constraint with the given name. * * @throws SchemaException If the unique constraint does not exist. */ public function removeUniqueConstraint(string $name): void { $name = $this->normalizeIdentifier($name); if (! $this->hasUniqueConstraint($name)) { throw SchemaException::uniqueConstraintDoesNotExist($name, $this->_name); } unset($this->uniqueConstraints[$name]); } /** * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest) * * @return Column[] */ public function getColumns() { $primaryKeyColumns = $this->getPrimaryKey() !== null ? $this->getPrimaryKeyColumns() : []; $foreignKeyColumns = $this->getForeignKeyColumns(); $remainderColumns = $this->filterColumns( array_merge(array_keys($primaryKeyColumns), array_keys($foreignKeyColumns)), true, ); return array_merge($primaryKeyColumns, $foreignKeyColumns, $remainderColumns); } /** * Returns the foreign key columns * * @deprecated Use {@see getForeignKey()} and {@see ForeignKeyConstraint::getLocalColumns()} instead. * * @return Column[] */ public function getForeignKeyColumns() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5731', '%s is deprecated. Use getForeignKey() and ForeignKeyConstraint::getLocalColumns() instead.', __METHOD__, ); $foreignKeyColumns = []; foreach ($this->getForeignKeys() as $foreignKey) { $foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getLocalColumns()); } return $this->filterColumns($foreignKeyColumns); } /** * Returns only columns that have specified names * * @param string[] $columnNames * * @return Column[] */ private function filterColumns(array $columnNames, bool $reverse = false): array { return array_filter($this->_columns, static function (string $columnName) use ($columnNames, $reverse): bool { return in_array($columnName, $columnNames, true) !== $reverse; }, ARRAY_FILTER_USE_KEY); } /** * Returns whether this table has a Column with the given name. * * @param string $name The column name. * * @return bool */ public function hasColumn($name) { $name = $this->normalizeIdentifier($name); return isset($this->_columns[$name]); } /** * Returns the Column with the given name. * * @param string $name The column name. * * @return Column * * @throws SchemaException If the column does not exist. */ public function getColumn($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasColumn($name)) { throw SchemaException::columnDoesNotExist($name, $this->_name); } return $this->_columns[$name]; } /** * Returns the primary key. * * @return Index|null The primary key, or null if this Table has no primary key. */ public function getPrimaryKey() { if ($this->_primaryKeyName !== null) { return $this->getIndex($this->_primaryKeyName); } return null; } /** * Returns the primary key columns. * * @deprecated Use {@see getPrimaryKey()} and {@see Index::getColumns()} instead. * * @return Column[] * * @throws Exception */ public function getPrimaryKeyColumns() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5731', '%s is deprecated. Use getPrimaryKey() and Index::getColumns() instead.', __METHOD__, ); $primaryKey = $this->getPrimaryKey(); if ($primaryKey === null) { throw new Exception('Table ' . $this->getName() . ' has no primary key.'); } return $this->filterColumns($primaryKey->getColumns()); } /** * Returns whether this table has a primary key. * * @deprecated Use {@see getPrimaryKey()} instead. * * @return bool */ public function hasPrimaryKey() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5731', '%s is deprecated. Use getPrimaryKey() instead.', __METHOD__, ); return $this->_primaryKeyName !== null && $this->hasIndex($this->_primaryKeyName); } /** * Returns whether this table has an Index with the given name. * * @param string $name The index name. * * @return bool */ public function hasIndex($name) { $name = $this->normalizeIdentifier($name); return isset($this->_indexes[$name]); } /** * Returns the Index with the given name. * * @param string $name The index name. * * @return Index * * @throws SchemaException If the index does not exist. */ public function getIndex($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasIndex($name)) { throw SchemaException::indexDoesNotExist($name, $this->_name); } return $this->_indexes[$name]; } /** @return Index[] */ public function getIndexes() { return $this->_indexes; } /** * Returns the unique constraints. * * @return UniqueConstraint[] */ public function getUniqueConstraints(): array { return $this->uniqueConstraints; } /** * Returns the foreign key constraints. * * @return ForeignKeyConstraint[] */ public function getForeignKeys() { return $this->_fkConstraints; } /** * @param string $name * * @return bool */ public function hasOption($name) { return isset($this->_options[$name]); } /** * @param string $name * * @return mixed */ public function getOption($name) { return $this->_options[$name]; } /** @return mixed[] */ public function getOptions() { return $this->_options; } /** * @deprecated * * @return void * * @throws SchemaException */ public function visit(Visitor $visitor) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5435', 'Table::visit() is deprecated.', ); $visitor->acceptTable($this); foreach ($this->getColumns() as $column) { $visitor->acceptColumn($this, $column); } foreach ($this->getIndexes() as $index) { $visitor->acceptIndex($this, $index); } foreach ($this->getForeignKeys() as $constraint) { $visitor->acceptForeignKey($this, $constraint); } } /** * Clone of a Table triggers a deep clone of all affected assets. * * @return void */ public function __clone() { foreach ($this->_columns as $k => $column) { $this->_columns[$k] = clone $column; } foreach ($this->_indexes as $k => $index) { $this->_indexes[$k] = clone $index; } foreach ($this->_fkConstraints as $k => $fk) { $this->_fkConstraints[$k] = clone $fk; $this->_fkConstraints[$k]->setLocalTable($this); } } /** * @param string[] $columnNames * @param string[] $flags * @param mixed[] $options * * @throws SchemaException */ private function _createUniqueConstraint( array $columnNames, string $indexName, array $flags = [], array $options = [] ): UniqueConstraint { if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) { throw SchemaException::indexNameInvalid($indexName); } foreach ($columnNames as $columnName) { if (! $this->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $this->_name); } } return new UniqueConstraint($indexName, $columnNames, $flags, $options); } /** * Normalizes a given identifier. * * Trims quotes and lowercases the given identifier. * * @return string The normalized identifier. */ private function normalizeIdentifier(?string $identifier): string { if ($identifier === null) { return ''; } return $this->trimQuotes(strtolower($identifier)); } public function setComment(?string $comment): self { // For keeping backward compatibility with MySQL in previous releases, table comments are stored as options. $this->addOption('comment', $comment); return $this; } public function getComment(): ?string { return $this->_options['comment'] ?? null; } } Schema/ColumnDiff.php 0000755 00000010214 00000000000 0010445 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\Deprecations\Deprecation; use function in_array; /** * Represents the change of a column. */ class ColumnDiff { /** * @deprecated Use {@see $fromColumn} and {@see Column::getName()} instead. * * @var string */ public $oldColumnName; /** * @internal Use {@see getNewColumn()} instead. * * @var Column */ public $column; /** * @deprecated Use {@see hasTypeChanged()}, {@see hasLengthChanged()}, {@see hasPrecisionChanged()}, * {@see hasScaleChanged()}, {@see hasUnsignedChanged()}, {@see hasFixedChanged()}, {@see hasNotNullChanged()}, * {@see hasDefaultChanged()}, {@see hasAutoIncrementChanged()} or {@see hasCommentChanged()} instead. * * @var string[] */ public $changedProperties = []; /** * @internal Use {@see getOldColumn()} instead. * * @var Column|null */ public $fromColumn; /** * @internal The diff can be only instantiated by a {@see Comparator}. * * @param string $oldColumnName * @param string[] $changedProperties */ public function __construct( $oldColumnName, Column $column, array $changedProperties = [], ?Column $fromColumn = null ) { if ($fromColumn === null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4785', 'Not passing the $fromColumn to %s is deprecated.', __METHOD__, ); } $this->oldColumnName = $oldColumnName; $this->column = $column; $this->changedProperties = $changedProperties; $this->fromColumn = $fromColumn; } public function getOldColumn(): ?Column { return $this->fromColumn; } public function getNewColumn(): Column { return $this->column; } public function hasTypeChanged(): bool { return $this->hasChanged('type'); } public function hasLengthChanged(): bool { return $this->hasChanged('length'); } public function hasPrecisionChanged(): bool { return $this->hasChanged('precision'); } public function hasScaleChanged(): bool { return $this->hasChanged('scale'); } public function hasUnsignedChanged(): bool { return $this->hasChanged('unsigned'); } public function hasFixedChanged(): bool { return $this->hasChanged('fixed'); } public function hasNotNullChanged(): bool { return $this->hasChanged('notnull'); } public function hasDefaultChanged(): bool { return $this->hasChanged('default'); } public function hasAutoIncrementChanged(): bool { return $this->hasChanged('autoincrement'); } public function hasCommentChanged(): bool { return $this->hasChanged('comment'); } /** * @deprecated Use {@see hasTypeChanged()}, {@see hasLengthChanged()}, {@see hasPrecisionChanged()}, * {@see hasScaleChanged()}, {@see hasUnsignedChanged()}, {@see hasFixedChanged()}, {@see hasNotNullChanged()}, * {@see hasDefaultChanged()}, {@see hasAutoIncrementChanged()} or {@see hasCommentChanged()} instead. * * @param string $propertyName * * @return bool */ public function hasChanged($propertyName) { return in_array($propertyName, $this->changedProperties, true); } /** * @deprecated Use {@see $fromColumn} instead. * * @return Identifier */ public function getOldColumnName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5622', '%s is deprecated. Use $fromColumn instead.', __METHOD__, ); if ($this->fromColumn !== null) { $name = $this->fromColumn->getName(); $quote = $this->fromColumn->isQuoted(); } else { $name = $this->oldColumnName; $quote = false; } return new Identifier($name, $quote); } } Schema/MySQLSchemaManager.php 0000755 00000046040 00000000000 0012006 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\MariaDb1027Platform; use Doctrine\DBAL\Platforms\MySQL; use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\CachingCollationMetadataProvider; use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\ConnectionCollationMetadataProvider; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_change_key_case; use function array_shift; use function assert; use function explode; use function implode; use function is_string; use function preg_match; use function strpos; use function strtok; use function strtolower; use function strtr; use const CASE_LOWER; /** * Schema manager for the MySQL RDBMS. * * @extends AbstractSchemaManager<AbstractMySQLPlatform> */ class MySQLSchemaManager extends AbstractSchemaManager { /** @see https://mariadb.com/kb/en/library/string-literals/#escape-sequences */ private const MARIADB_ESCAPE_SEQUENCES = [ '\\0' => "\0", "\\'" => "'", '\\"' => '"', '\\b' => "\b", '\\n' => "\n", '\\r' => "\r", '\\t' => "\t", '\\Z' => "\x1a", '\\\\' => '\\', '\\%' => '%', '\\_' => '_', // Internally, MariaDB escapes single quotes using the standard syntax "''" => "'", ]; /** * {@inheritDoc} */ public function listTableNames() { return $this->doListTableNames(); } /** * {@inheritDoc} */ public function listTables() { return $this->doListTables(); } /** * {@inheritDoc} * * @deprecated Use {@see introspectTable()} instead. */ public function listTableDetails($name) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5595', '%s is deprecated. Use introspectTable() instead.', __METHOD__, ); return $this->doListTableDetails($name); } /** * {@inheritDoc} */ public function listTableColumns($table, $database = null) { return $this->doListTableColumns($table, $database); } /** * {@inheritDoc} */ public function listTableIndexes($table) { return $this->doListTableIndexes($table); } /** * {@inheritDoc} */ public function listTableForeignKeys($table, $database = null) { return $this->doListTableForeignKeys($table, $database); } /** * {@inheritDoc} */ protected function _getPortableViewDefinition($view) { return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']); } /** * {@inheritDoc} */ protected function _getPortableTableDefinition($table) { return array_shift($table); } /** * {@inheritDoc} */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { foreach ($tableIndexes as $k => $v) { $v = array_change_key_case($v, CASE_LOWER); if ($v['key_name'] === 'PRIMARY') { $v['primary'] = true; } else { $v['primary'] = false; } if (strpos($v['index_type'], 'FULLTEXT') !== false) { $v['flags'] = ['FULLTEXT']; } elseif (strpos($v['index_type'], 'SPATIAL') !== false) { $v['flags'] = ['SPATIAL']; } // Ignore prohibited prefix `length` for spatial index if (strpos($v['index_type'], 'SPATIAL') === false) { $v['length'] = isset($v['sub_part']) ? (int) $v['sub_part'] : null; } $tableIndexes[$k] = $v; } return parent::_getPortableTableIndexesList($tableIndexes, $tableName); } /** * {@inheritDoc} */ protected function _getPortableDatabaseDefinition($database) { return $database['Database']; } /** * {@inheritDoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); $dbType = strtolower($tableColumn['type']); $dbType = strtok($dbType, '(), '); assert(is_string($dbType)); $length = $tableColumn['length'] ?? strtok('(), '); $fixed = null; if (! isset($tableColumn['name'])) { $tableColumn['name'] = ''; } $scale = null; $precision = null; $type = $origType = $this->_platform->getDoctrineTypeMapping($dbType); // In cases where not connected to a database DESCRIBE $table does not return 'Comment' if (isset($tableColumn['comment'])) { $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); } switch ($dbType) { case 'char': case 'binary': $fixed = true; break; case 'float': case 'double': case 'real': case 'numeric': case 'decimal': if ( preg_match( '([A-Za-z]+\(([0-9]+),([0-9]+)\))', $tableColumn['type'], $match, ) === 1 ) { $precision = $match[1]; $scale = $match[2]; $length = null; } break; case 'tinytext': $length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYTEXT; break; case 'text': $length = AbstractMySQLPlatform::LENGTH_LIMIT_TEXT; break; case 'mediumtext': $length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMTEXT; break; case 'tinyblob': $length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYBLOB; break; case 'blob': $length = AbstractMySQLPlatform::LENGTH_LIMIT_BLOB; break; case 'mediumblob': $length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB; break; case 'tinyint': case 'smallint': case 'mediumint': case 'int': case 'integer': case 'bigint': case 'year': $length = null; break; } if ($this->_platform instanceof MariaDb1027Platform) { $columnDefault = $this->getMariaDb1027ColumnDefault($this->_platform, $tableColumn['default']); } else { $columnDefault = $tableColumn['default']; } $options = [ 'length' => $length !== null ? (int) $length : null, 'unsigned' => strpos($tableColumn['type'], 'unsigned') !== false, 'fixed' => (bool) $fixed, 'default' => $columnDefault, 'notnull' => $tableColumn['null'] !== 'YES', 'scale' => null, 'precision' => null, 'autoincrement' => strpos($tableColumn['extra'], 'auto_increment') !== false, 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, ]; if ($scale !== null && $precision !== null) { $options['scale'] = (int) $scale; $options['precision'] = (int) $precision; } $column = new Column($tableColumn['field'], Type::getType($type), $options); if (isset($tableColumn['characterset'])) { $column->setPlatformOption('charset', $tableColumn['characterset']); } if (isset($tableColumn['collation'])) { $column->setPlatformOption('collation', $tableColumn['collation']); } if (isset($tableColumn['declarationMismatch'])) { $column->setPlatformOption('declarationMismatch', $tableColumn['declarationMismatch']); } // Check underlying database type where doctrine type is inferred from DC2Type comment // and set a flag if it is not as expected. if ($type === 'json' && $origType !== $type && $this->expectedDbType($type, $options) !== $dbType) { $column->setPlatformOption('declarationMismatch', true); } return $column; } /** * Returns the database data type for a given doctrine type and column * * Note that for data types that depend on length where length is not part of the column definition * and therefore the $tableColumn['length'] will not be set, for example TEXT (which could be LONGTEXT, * MEDIUMTEXT) or BLOB (LONGBLOB or TINYBLOB), the expectedDbType cannot be inferred exactly, merely * the default type. * * This method is intended to be used to determine underlying database type where doctrine type is * inferred from a DC2Type comment. * * @param mixed[] $tableColumn */ private function expectedDbType(string $type, array $tableColumn): string { $_type = Type::getType($type); $expectedDbType = strtolower($_type->getSQLDeclaration($tableColumn, $this->_platform)); $expectedDbType = strtok($expectedDbType, '(), '); return $expectedDbType === false ? '' : $expectedDbType; } /** * Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers. * * - Since MariaDb 10.2.7 column defaults stored in information_schema are now quoted * to distinguish them from expressions (see MDEV-10134). * - CURRENT_TIMESTAMP, CURRENT_TIME, CURRENT_DATE are stored in information_schema * as current_timestamp(), currdate(), currtime() * - Quoted 'NULL' is not enforced by Maria, it is technically possible to have * null in some circumstances (see https://jira.mariadb.org/browse/MDEV-14053) * - \' is always stored as '' in information_schema (normalized) * * @link https://mariadb.com/kb/en/library/information-schema-columns-table/ * @link https://jira.mariadb.org/browse/MDEV-13132 * * @param string|null $columnDefault default value as stored in information_schema for MariaDB >= 10.2.7 */ private function getMariaDb1027ColumnDefault(MariaDb1027Platform $platform, ?string $columnDefault): ?string { if ($columnDefault === 'NULL' || $columnDefault === null) { return null; } if (preg_match('/^\'(.*)\'$/', $columnDefault, $matches) === 1) { return strtr($matches[1], self::MARIADB_ESCAPE_SEQUENCES); } switch ($columnDefault) { case 'current_timestamp()': return $platform->getCurrentTimestampSQL(); case 'curdate()': return $platform->getCurrentDateSQL(); case 'curtime()': return $platform->getCurrentTimeSQL(); } return $columnDefault; } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $list = []; foreach ($tableForeignKeys as $value) { $value = array_change_key_case($value, CASE_LOWER); if (! isset($list[$value['constraint_name']])) { if (! isset($value['delete_rule']) || $value['delete_rule'] === 'RESTRICT') { $value['delete_rule'] = null; } if (! isset($value['update_rule']) || $value['update_rule'] === 'RESTRICT') { $value['update_rule'] = null; } $list[$value['constraint_name']] = [ 'name' => $value['constraint_name'], 'local' => [], 'foreign' => [], 'foreignTable' => $value['referenced_table_name'], 'onDelete' => $value['delete_rule'], 'onUpdate' => $value['update_rule'], ]; } $list[$value['constraint_name']]['local'][] = $value['column_name']; $list[$value['constraint_name']]['foreign'][] = $value['referenced_column_name']; } return parent::_getPortableTableForeignKeysList($list); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint { return new ForeignKeyConstraint( $tableForeignKey['local'], $tableForeignKey['foreignTable'], $tableForeignKey['foreign'], $tableForeignKey['name'], [ 'onDelete' => $tableForeignKey['onDelete'], 'onUpdate' => $tableForeignKey['onUpdate'], ], ); } public function createComparator(): Comparator { return new MySQL\Comparator( $this->_platform, new CachingCollationMetadataProvider( new ConnectionCollationMetadataProvider($this->_conn), ), ); } protected function selectTableNames(string $databaseName): Result { $sql = <<<'SQL' SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME SQL; return $this->_conn->executeQuery($sql, [$databaseName]); } protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { // @todo 4.0 - call getColumnTypeSQLSnippet() instead [$columnTypeSQL, $joinCheckConstraintSQL] = $this->_platform->getColumnTypeSQLSnippets('c', $databaseName); $sql = 'SELECT'; if ($tableName === null) { $sql .= ' c.TABLE_NAME,'; } $sql .= <<<SQL c.COLUMN_NAME AS field, $columnTypeSQL AS type, c.IS_NULLABLE AS `null`, c.COLUMN_KEY AS `key`, c.COLUMN_DEFAULT AS `default`, c.EXTRA, c.COLUMN_COMMENT AS comment, c.CHARACTER_SET_NAME AS characterset, c.COLLATION_NAME AS collation FROM information_schema.COLUMNS c INNER JOIN information_schema.TABLES t ON t.TABLE_NAME = c.TABLE_NAME $joinCheckConstraintSQL SQL; // The schema name is passed multiple times as a literal in the WHERE clause instead of using a JOIN condition // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions // caused by https://bugs.mysql.com/bug.php?id=81347 $conditions = ['c.TABLE_SCHEMA = ?', 't.TABLE_SCHEMA = ?', "t.TABLE_TYPE = 'BASE TABLE'"]; $params = [$databaseName, $databaseName]; if ($tableName !== null) { $conditions[] = 't.TABLE_NAME = ?'; $params[] = $tableName; } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY ORDINAL_POSITION'; return $this->_conn->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' TABLE_NAME,'; } $sql .= <<<'SQL' NON_UNIQUE AS Non_Unique, INDEX_NAME AS Key_name, COLUMN_NAME AS Column_Name, SUB_PART AS Sub_Part, INDEX_TYPE AS Index_Type FROM information_schema.STATISTICS SQL; $conditions = ['TABLE_SCHEMA = ?']; $params = [$databaseName]; if ($tableName !== null) { $conditions[] = 'TABLE_NAME = ?'; $params[] = $tableName; } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY SEQ_IN_INDEX'; return $this->_conn->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT DISTINCT'; if ($tableName === null) { $sql .= ' k.TABLE_NAME,'; } $sql .= <<<'SQL' k.CONSTRAINT_NAME, k.COLUMN_NAME, k.REFERENCED_TABLE_NAME, k.REFERENCED_COLUMN_NAME, k.ORDINAL_POSITION /*!50116, c.UPDATE_RULE, c.DELETE_RULE */ FROM information_schema.key_column_usage k /*!50116 INNER JOIN information_schema.referential_constraints c ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.TABLE_NAME = k.TABLE_NAME */ SQL; $conditions = ['k.TABLE_SCHEMA = ?']; $params = [$databaseName]; if ($tableName !== null) { $conditions[] = 'k.TABLE_NAME = ?'; $params[] = $tableName; } $conditions[] = 'k.REFERENCED_COLUMN_NAME IS NOT NULL'; $sql .= ' WHERE ' . implode(' AND ', $conditions) // The schema name is passed multiple times in the WHERE clause instead of using a JOIN condition // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions // caused by https://bugs.mysql.com/bug.php?id=81347. // Use a string literal for the database name since the internal PDO SQL parser // cannot recognize parameter placeholders inside conditional comments . ' /*!50116 AND c.CONSTRAINT_SCHEMA = ' . $this->_conn->quote($databaseName) . ' */' . ' ORDER BY k.ORDINAL_POSITION'; return $this->_conn->executeQuery($sql, $params); } /** * {@inheritDoc} */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { $sql = $this->_platform->fetchTableOptionsByTable($tableName !== null); $params = [$databaseName]; if ($tableName !== null) { $params[] = $tableName; } /** @var array<string,array<string,mixed>> $metadata */ $metadata = $this->_conn->executeQuery($sql, $params) ->fetchAllAssociativeIndexed(); $tableOptions = []; foreach ($metadata as $table => $data) { $data = array_change_key_case($data, CASE_LOWER); $tableOptions[$table] = [ 'engine' => $data['engine'], 'collation' => $data['table_collation'], 'charset' => $data['character_set_name'], 'autoincrement' => $data['auto_increment'], 'comment' => $data['table_comment'], 'create_options' => $this->parseCreateOptions($data['create_options']), ]; } return $tableOptions; } /** @return string[]|true[] */ private function parseCreateOptions(?string $string): array { $options = []; if ($string === null || $string === '') { return $options; } foreach (explode(' ', $string) as $pair) { $parts = explode('=', $pair, 2); $options[$parts[0]] = $parts[1] ?? true; } return $options; } } Schema/LegacySchemaManagerFactory.php 0000755 00000000707 00000000000 0013575 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Connection; /** @internal Will be removed in 4.0. */ final class LegacySchemaManagerFactory implements SchemaManagerFactory { public function createSchemaManager(Connection $connection): AbstractSchemaManager { return $connection->getDriver()->getSchemaManager( $connection, $connection->getDatabasePlatform(), ); } } Schema/ForeignKeyConstraint.php 0000755 00000026034 00000000000 0012535 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use function array_keys; use function array_map; use function strrpos; use function strtolower; use function strtoupper; use function substr; /** * An abstraction class for a foreign key constraint. */ class ForeignKeyConstraint extends AbstractAsset implements Constraint { /** * Instance of the referencing table the foreign key constraint is associated with. * * @var Table */ protected $_localTable; /** * Asset identifier instances of the referencing table column names the foreign key constraint is associated with. * array($columnName => Identifier) * * @var Identifier[] */ protected $_localColumnNames; /** * Table or asset identifier instance of the referenced table name the foreign key constraint is associated with. * * @var Table|Identifier */ protected $_foreignTableName; /** * Asset identifier instances of the referenced table column names the foreign key constraint is associated with. * array($columnName => Identifier) * * @var Identifier[] */ protected $_foreignColumnNames; /** * Options associated with the foreign key constraint. * * @var mixed[] */ protected $_options; /** * Initializes the foreign key constraint. * * @param string[] $localColumnNames Names of the referencing table columns. * @param Table|string $foreignTableName Referenced table. * @param string[] $foreignColumnNames Names of the referenced table columns. * @param string|null $name Name of the foreign key constraint. * @param mixed[] $options Options associated with the foreign key constraint. */ public function __construct( array $localColumnNames, $foreignTableName, array $foreignColumnNames, $name = null, array $options = [] ) { if ($name !== null) { $this->_setName($name); } $this->_localColumnNames = $this->createIdentifierMap($localColumnNames); if ($foreignTableName instanceof Table) { $this->_foreignTableName = $foreignTableName; } else { $this->_foreignTableName = new Identifier($foreignTableName); } $this->_foreignColumnNames = $this->createIdentifierMap($foreignColumnNames); $this->_options = $options; } /** * @param string[] $names * * @return Identifier[] */ private function createIdentifierMap(array $names): array { $identifiers = []; foreach ($names as $name) { $identifiers[$name] = new Identifier($name); } return $identifiers; } /** * Returns the name of the referencing table * the foreign key constraint is associated with. * * @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. * * @return string */ public function getLocalTableName() { return $this->_localTable->getName(); } /** * Sets the Table instance of the referencing table * the foreign key constraint is associated with. * * @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. * * @param Table $table Instance of the referencing table. * * @return void */ public function setLocalTable(Table $table) { $this->_localTable = $table; } /** * @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. * * @return Table */ public function getLocalTable() { return $this->_localTable; } /** * Returns the names of the referencing table columns * the foreign key constraint is associated with. * * @return string[] */ public function getLocalColumns() { return array_keys($this->_localColumnNames); } /** * Returns the quoted representation of the referencing table column names * the foreign key constraint is associated with. * * But only if they were defined with one or the referencing table column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedLocalColumns(AbstractPlatform $platform) { $columns = []; foreach ($this->_localColumnNames as $column) { $columns[] = $column->getQuotedName($platform); } return $columns; } /** * Returns unquoted representation of local table column names for comparison with other FK * * @return string[] */ public function getUnquotedLocalColumns() { return array_map([$this, 'trimQuotes'], $this->getLocalColumns()); } /** * Returns unquoted representation of foreign table column names for comparison with other FK * * @return string[] */ public function getUnquotedForeignColumns() { return array_map([$this, 'trimQuotes'], $this->getForeignColumns()); } /** * {@inheritDoc} * * @deprecated Use {@see getLocalColumns()} instead. * * @see getLocalColumns */ public function getColumns() { return $this->getLocalColumns(); } /** * Returns the quoted representation of the referencing table column names * the foreign key constraint is associated with. * * But only if they were defined with one or the referencing table column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @deprecated Use {@see getQuotedLocalColumns()} instead. * * @see getQuotedLocalColumns * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedColumns(AbstractPlatform $platform) { return $this->getQuotedLocalColumns($platform); } /** * Returns the name of the referenced table * the foreign key constraint is associated with. * * @return string */ public function getForeignTableName() { return $this->_foreignTableName->getName(); } /** * Returns the non-schema qualified foreign table name. * * @return string */ public function getUnqualifiedForeignTableName() { $name = $this->_foreignTableName->getName(); $position = strrpos($name, '.'); if ($position !== false) { $name = substr($name, $position + 1); } return strtolower($name); } /** * Returns the quoted representation of the referenced table name * the foreign key constraint is associated with. * * But only if it was defined with one or the referenced table name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string */ public function getQuotedForeignTableName(AbstractPlatform $platform) { return $this->_foreignTableName->getQuotedName($platform); } /** * Returns the names of the referenced table columns * the foreign key constraint is associated with. * * @return string[] */ public function getForeignColumns() { return array_keys($this->_foreignColumnNames); } /** * Returns the quoted representation of the referenced table column names * the foreign key constraint is associated with. * * But only if they were defined with one or the referenced table column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedForeignColumns(AbstractPlatform $platform) { $columns = []; foreach ($this->_foreignColumnNames as $column) { $columns[] = $column->getQuotedName($platform); } return $columns; } /** * Returns whether or not a given option * is associated with the foreign key constraint. * * @param string $name Name of the option to check. * * @return bool */ public function hasOption($name) { return isset($this->_options[$name]); } /** * Returns an option associated with the foreign key constraint. * * @param string $name Name of the option the foreign key constraint is associated with. * * @return mixed */ public function getOption($name) { return $this->_options[$name]; } /** * Returns the options associated with the foreign key constraint. * * @return mixed[] */ public function getOptions() { return $this->_options; } /** * Returns the referential action for UPDATE operations * on the referenced table the foreign key constraint is associated with. * * @return string|null */ public function onUpdate() { return $this->onEvent('onUpdate'); } /** * Returns the referential action for DELETE operations * on the referenced table the foreign key constraint is associated with. * * @return string|null */ public function onDelete() { return $this->onEvent('onDelete'); } /** * Returns the referential action for a given database operation * on the referenced table the foreign key constraint is associated with. * * @param string $event Name of the database operation/event to return the referential action for. */ private function onEvent($event): ?string { if (isset($this->_options[$event])) { $onEvent = strtoupper($this->_options[$event]); if ($onEvent !== 'NO ACTION' && $onEvent !== 'RESTRICT') { return $onEvent; } } return null; } /** * Checks whether this foreign key constraint intersects the given index columns. * * Returns `true` if at least one of this foreign key's local columns * matches one of the given index's columns, `false` otherwise. * * @param Index $index The index to be checked against. * * @return bool */ public function intersectsIndexColumns(Index $index) { foreach ($index->getColumns() as $indexColumn) { foreach ($this->_localColumnNames as $localColumn) { if (strtolower($indexColumn) === strtolower($localColumn->getName())) { return true; } } } return false; } } Schema/Schema.php 0000755 00000031247 00000000000 0007630 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\DBAL\SQL\Builder\CreateSchemaObjectsSQLBuilder; use Doctrine\DBAL\SQL\Builder\DropSchemaObjectsSQLBuilder; use Doctrine\Deprecations\Deprecation; use function array_keys; use function strpos; use function strtolower; /** * Object representation of a database schema. * * Different vendors have very inconsistent naming with regard to the concept * of a "schema". Doctrine understands a schema as the entity that conceptually * wraps a set of database objects such as tables, sequences, indexes and * foreign keys that belong to each other into a namespace. A Doctrine Schema * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL. * * Every asset in the doctrine schema has a name. A name consists of either a * namespace.local name pair or just a local unqualified name. * * The abstraction layer that covers a PostgreSQL schema is the namespace of an * database object (asset). A schema can have a name, which will be used as * default namespace for the unqualified database objects that are created in * the schema. * * In the case of MySQL where cross-database queries are allowed this leads to * databases being "misinterpreted" as namespaces. This is intentional, however * the CREATE/DROP SQL visitors will just filter this queries and do not * execute them. Only the queries for the currently connected database are * executed. */ class Schema extends AbstractAsset { /** * The namespaces in this schema. * * @var string[] */ private array $namespaces = []; /** @var Table[] */ protected $_tables = []; /** @var Sequence[] */ protected $_sequences = []; /** @var SchemaConfig */ protected $_schemaConfig; /** * @param Table[] $tables * @param Sequence[] $sequences * @param string[] $namespaces * * @throws SchemaException */ public function __construct( array $tables = [], array $sequences = [], ?SchemaConfig $schemaConfig = null, array $namespaces = [] ) { $schemaConfig ??= new SchemaConfig(); $this->_schemaConfig = $schemaConfig; $this->_setName($schemaConfig->getName() ?? 'public'); foreach ($namespaces as $namespace) { $this->createNamespace($namespace); } foreach ($tables as $table) { $this->_addTable($table); } foreach ($sequences as $sequence) { $this->_addSequence($sequence); } } /** * @deprecated * * @return bool */ public function hasExplicitForeignKeyIndexes() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4822', 'Schema::hasExplicitForeignKeyIndexes() is deprecated.', ); return $this->_schemaConfig->hasExplicitForeignKeyIndexes(); } /** * @return void * * @throws SchemaException */ protected function _addTable(Table $table) { $namespaceName = $table->getNamespaceName(); $tableName = $this->normalizeName($table); if (isset($this->_tables[$tableName])) { throw SchemaException::tableAlreadyExists($tableName); } if ( $namespaceName !== null && ! $table->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName) ) { $this->createNamespace($namespaceName); } $this->_tables[$tableName] = $table; $table->setSchemaConfig($this->_schemaConfig); } /** * @return void * * @throws SchemaException */ protected function _addSequence(Sequence $sequence) { $namespaceName = $sequence->getNamespaceName(); $seqName = $this->normalizeName($sequence); if (isset($this->_sequences[$seqName])) { throw SchemaException::sequenceAlreadyExists($seqName); } if ( $namespaceName !== null && ! $sequence->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName) ) { $this->createNamespace($namespaceName); } $this->_sequences[$seqName] = $sequence; } /** * Returns the namespaces of this schema. * * @return string[] A list of namespace names. */ public function getNamespaces() { return $this->namespaces; } /** * Gets all tables of this schema. * * @return Table[] */ public function getTables() { return $this->_tables; } /** * @param string $name * * @return Table * * @throws SchemaException */ public function getTable($name) { $name = $this->getFullQualifiedAssetName($name); if (! isset($this->_tables[$name])) { throw SchemaException::tableDoesNotExist($name); } return $this->_tables[$name]; } /** @param string $name */ private function getFullQualifiedAssetName($name): string { $name = $this->getUnquotedAssetName($name); if (strpos($name, '.') === false) { $name = $this->getName() . '.' . $name; } return strtolower($name); } private function normalizeName(AbstractAsset $asset): string { return $asset->getFullQualifiedName($this->getName()); } /** * Returns the unquoted representation of a given asset name. * * @param string $assetName Quoted or unquoted representation of an asset name. */ private function getUnquotedAssetName($assetName): string { if ($this->isIdentifierQuoted($assetName)) { return $this->trimQuotes($assetName); } return $assetName; } /** * Does this schema have a namespace with the given name? * * @param string $name * * @return bool */ public function hasNamespace($name) { $name = strtolower($this->getUnquotedAssetName($name)); return isset($this->namespaces[$name]); } /** * Does this schema have a table with the given name? * * @param string $name * * @return bool */ public function hasTable($name) { $name = $this->getFullQualifiedAssetName($name); return isset($this->_tables[$name]); } /** * Gets all table names, prefixed with a schema name, even the default one if present. * * @deprecated Use {@see getTables()} and {@see Table::getName()} instead. * * @return string[] */ public function getTableNames() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4800', 'Schema::getTableNames() is deprecated.' . ' Use Schema::getTables() and Table::getName() instead.', __METHOD__, ); return array_keys($this->_tables); } /** * @param string $name * * @return bool */ public function hasSequence($name) { $name = $this->getFullQualifiedAssetName($name); return isset($this->_sequences[$name]); } /** * @param string $name * * @return Sequence * * @throws SchemaException */ public function getSequence($name) { $name = $this->getFullQualifiedAssetName($name); if (! $this->hasSequence($name)) { throw SchemaException::sequenceDoesNotExist($name); } return $this->_sequences[$name]; } /** @return Sequence[] */ public function getSequences() { return $this->_sequences; } /** * Creates a new namespace. * * @param string $name The name of the namespace to create. * * @return Schema This schema instance. * * @throws SchemaException */ public function createNamespace($name) { $unquotedName = strtolower($this->getUnquotedAssetName($name)); if (isset($this->namespaces[$unquotedName])) { throw SchemaException::namespaceAlreadyExists($unquotedName); } $this->namespaces[$unquotedName] = $name; return $this; } /** * Creates a new table. * * @param string $name * * @return Table * * @throws SchemaException */ public function createTable($name) { $table = new Table($name); $this->_addTable($table); foreach ($this->_schemaConfig->getDefaultTableOptions() as $option => $value) { $table->addOption($option, $value); } return $table; } /** * Renames a table. * * @param string $oldName * @param string $newName * * @return Schema * * @throws SchemaException */ public function renameTable($oldName, $newName) { $table = $this->getTable($oldName); $table->_setName($newName); $this->dropTable($oldName); $this->_addTable($table); return $this; } /** * Drops a table from the schema. * * @param string $name * * @return Schema * * @throws SchemaException */ public function dropTable($name) { $name = $this->getFullQualifiedAssetName($name); $this->getTable($name); unset($this->_tables[$name]); return $this; } /** * Creates a new sequence. * * @param string $name * @param int $allocationSize * @param int $initialValue * * @return Sequence * * @throws SchemaException */ public function createSequence($name, $allocationSize = 1, $initialValue = 1) { $seq = new Sequence($name, $allocationSize, $initialValue); $this->_addSequence($seq); return $seq; } /** * @param string $name * * @return Schema */ public function dropSequence($name) { $name = $this->getFullQualifiedAssetName($name); unset($this->_sequences[$name]); return $this; } /** * Returns an array of necessary SQL queries to create the schema on the given platform. * * @return list<string> * * @throws Exception */ public function toSql(AbstractPlatform $platform) { $builder = new CreateSchemaObjectsSQLBuilder($platform); return $builder->buildSQL($this); } /** * Return an array of necessary SQL queries to drop the schema on the given platform. * * @return list<string> * * @throws Exception */ public function toDropSql(AbstractPlatform $platform) { $builder = new DropSchemaObjectsSQLBuilder($platform); return $builder->buildSQL($this); } /** * @deprecated * * @return string[] * * @throws SchemaException */ public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform) { $schemaDiff = (new Comparator())->compareSchemas($this, $toSchema); return $schemaDiff->toSql($platform); } /** * @deprecated * * @return string[] * * @throws SchemaException */ public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform) { $schemaDiff = (new Comparator())->compareSchemas($fromSchema, $this); return $schemaDiff->toSql($platform); } /** * @deprecated * * @return void */ public function visit(Visitor $visitor) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5435', 'Schema::visit() is deprecated.', ); $visitor->acceptSchema($this); if ($visitor instanceof NamespaceVisitor) { foreach ($this->namespaces as $namespace) { $visitor->acceptNamespace($namespace); } } foreach ($this->_tables as $table) { $table->visit($visitor); } foreach ($this->_sequences as $sequence) { $sequence->visit($visitor); } } /** * Cloning a Schema triggers a deep clone of all related assets. * * @return void */ public function __clone() { foreach ($this->_tables as $k => $table) { $this->_tables[$k] = clone $table; } foreach ($this->_sequences as $k => $sequence) { $this->_sequences[$k] = clone $sequence; } } } Schema/SchemaConfig.php 0000755 00000004633 00000000000 0010755 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\Deprecations\Deprecation; /** * Configuration for a Schema. */ class SchemaConfig { /** * @deprecated * * @var bool */ protected $hasExplicitForeignKeyIndexes = false; /** @var int */ protected $maxIdentifierLength = 63; /** @var string|null */ protected $name; /** @var mixed[] */ protected $defaultTableOptions = []; /** * @deprecated * * @return bool */ public function hasExplicitForeignKeyIndexes() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4822', 'SchemaConfig::hasExplicitForeignKeyIndexes() is deprecated.', ); return $this->hasExplicitForeignKeyIndexes; } /** * @deprecated * * @param bool $flag * * @return void */ public function setExplicitForeignKeyIndexes($flag) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4822', 'SchemaConfig::setExplicitForeignKeyIndexes() is deprecated.', ); $this->hasExplicitForeignKeyIndexes = (bool) $flag; } /** * @param int $length * * @return void */ public function setMaxIdentifierLength($length) { $this->maxIdentifierLength = (int) $length; } /** @return int */ public function getMaxIdentifierLength() { return $this->maxIdentifierLength; } /** * Gets the default namespace of schema objects. * * @return string|null */ public function getName() { return $this->name; } /** * Sets the default namespace name of schema objects. * * @param string $name The value to set. * * @return void */ public function setName($name) { $this->name = $name; } /** * Gets the default options that are passed to Table instances created with * Schema#createTable(). * * @return mixed[] */ public function getDefaultTableOptions() { return $this->defaultTableOptions; } /** * @param mixed[] $defaultTableOptions * * @return void */ public function setDefaultTableOptions(array $defaultTableOptions) { $this->defaultTableOptions = $defaultTableOptions; } } Schema/Sequence.php 0000755 00000006441 00000000000 0010176 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\Deprecations\Deprecation; use function count; use function sprintf; /** * Sequence structure. */ class Sequence extends AbstractAsset { /** @var int */ protected $allocationSize = 1; /** @var int */ protected $initialValue = 1; /** @var int|null */ protected $cache; /** * @param string $name * @param int $allocationSize * @param int $initialValue * @param int|null $cache */ public function __construct($name, $allocationSize = 1, $initialValue = 1, $cache = null) { $this->_setName($name); $this->setAllocationSize($allocationSize); $this->setInitialValue($initialValue); $this->cache = $cache; } /** @return int */ public function getAllocationSize() { return $this->allocationSize; } /** @return int */ public function getInitialValue() { return $this->initialValue; } /** @return int|null */ public function getCache() { return $this->cache; } /** * @param int $allocationSize * * @return Sequence */ public function setAllocationSize($allocationSize) { if ($allocationSize > 0) { $this->allocationSize = $allocationSize; } else { $this->allocationSize = 1; } return $this; } /** * @param int $initialValue * * @return Sequence */ public function setInitialValue($initialValue) { if ($initialValue > 0) { $this->initialValue = $initialValue; } else { $this->initialValue = 1; } return $this; } /** * @param int $cache * * @return Sequence */ public function setCache($cache) { $this->cache = $cache; return $this; } /** * Checks if this sequence is an autoincrement sequence for a given table. * * This is used inside the comparator to not report sequences as missing, * when the "from" schema implicitly creates the sequences. * * @return bool */ public function isAutoIncrementsFor(Table $table) { $primaryKey = $table->getPrimaryKey(); if ($primaryKey === null) { return false; } $pkColumns = $primaryKey->getColumns(); if (count($pkColumns) !== 1) { return false; } $column = $table->getColumn($pkColumns[0]); if (! $column->getAutoincrement()) { return false; } $sequenceName = $this->getShortestName($table->getNamespaceName()); $tableName = $table->getShortestName($table->getNamespaceName()); $tableSequenceName = sprintf('%s_%s_seq', $tableName, $column->getShortestName($table->getNamespaceName())); return $tableSequenceName === $sequenceName; } /** * @deprecated * * @return void */ public function visit(Visitor $visitor) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5435', 'Sequence::visit() is deprecated.', ); $visitor->acceptSequence($this); } } Id/TableGenerator.php 0000755 00000012417 00000000000 0010460 0 ustar 00 <?php namespace Doctrine\DBAL\Id; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception; use Doctrine\Deprecations\Deprecation; use Throwable; use function array_change_key_case; use function assert; use function is_int; use const CASE_LOWER; /** * Table ID Generator for those poor languages that are missing sequences. * * WARNING: The Table Id Generator clones a second independent database * connection to work correctly. This means using the generator requests that * generate IDs will have two open database connections. This is necessary to * be safe from transaction failures in the main connection. Make sure to only * ever use one TableGenerator otherwise you end up with many connections. * * TableID Generator does not work with SQLite. * * The TableGenerator does not take care of creating the SQL Table itself. You * should look at the `TableGeneratorSchemaVisitor` to do this for you. * Otherwise the schema for a table looks like: * * CREATE sequences ( * sequence_name VARCHAR(255) NOT NULL, * sequence_value INT NOT NULL DEFAULT 1, * sequence_increment_by INT NOT NULL DEFAULT 1, * PRIMARY KEY (sequence_name) * ); * * Technically this generator works as follows: * * 1. Use a robust transaction serialization level. * 2. Open transaction * 3. Acquire a read lock on the table row (SELECT .. FOR UPDATE) * 4. Increment current value by one and write back to database * 5. Commit transaction * * If you are using a sequence_increment_by value that is larger than one the * ID Generator will keep incrementing values until it hits the incrementation * gap before issuing another query. * * If no row is present for a given sequence a new one will be created with the * default values 'value' = 1 and 'increment_by' = 1 * * @deprecated */ class TableGenerator { private Connection $conn; /** @var string */ private $generatorTableName; /** @var mixed[][] */ private array $sequences = []; /** * @param string $generatorTableName * * @throws Exception */ public function __construct(Connection $conn, $generatorTableName = 'sequences') { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4681', 'The TableGenerator class is is deprecated.', ); if ($conn->getDriver() instanceof Driver\PDO\SQLite\Driver) { throw new Exception('Cannot use TableGenerator with SQLite.'); } $this->conn = DriverManager::getConnection( $conn->getParams(), $conn->getConfiguration(), $conn->getEventManager(), ); $this->generatorTableName = $generatorTableName; } /** * Generates the next unused value for the given sequence name. * * @param string $sequence * * @return int * * @throws Exception */ public function nextValue($sequence) { if (isset($this->sequences[$sequence])) { $value = $this->sequences[$sequence]['value']; $this->sequences[$sequence]['value']++; if ($this->sequences[$sequence]['value'] >= $this->sequences[$sequence]['max']) { unset($this->sequences[$sequence]); } return $value; } $this->conn->beginTransaction(); try { $row = $this->conn->createQueryBuilder() ->select('sequence_value', 'sequence_increment_by') ->from($this->generatorTableName) ->where('sequence_name = ?') ->forUpdate() ->setParameter(1, $sequence) ->fetchAssociative(); if ($row !== false) { $row = array_change_key_case($row, CASE_LOWER); $value = $row['sequence_value']; $value++; assert(is_int($value)); if ($row['sequence_increment_by'] > 1) { $this->sequences[$sequence] = [ 'value' => $value, 'max' => $row['sequence_value'] + $row['sequence_increment_by'], ]; } $sql = 'UPDATE ' . $this->generatorTableName . ' ' . 'SET sequence_value = sequence_value + sequence_increment_by ' . 'WHERE sequence_name = ? AND sequence_value = ?'; $rows = $this->conn->executeStatement($sql, [$sequence, $row['sequence_value']]); if ($rows !== 1) { throw new Exception('Race-condition detected while updating sequence. Aborting generation'); } } else { $this->conn->insert( $this->generatorTableName, ['sequence_name' => $sequence, 'sequence_value' => 1, 'sequence_increment_by' => 1], ); $value = 1; } $this->conn->commit(); } catch (Throwable $e) { $this->conn->rollBack(); throw new Exception( 'Error occurred while generating ID with TableGenerator, aborted generation: ' . $e->getMessage(), 0, $e, ); } return $value; } } Id/TableGeneratorSchemaVisitor.php 0000755 00000003370 00000000000 0013157 0 ustar 00 <?php namespace Doctrine\DBAL\Id; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\Deprecations\Deprecation; /** @deprecated */ class TableGeneratorSchemaVisitor implements Visitor { /** @var string */ private $generatorTableName; /** @param string $generatorTableName */ public function __construct($generatorTableName = 'sequences') { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4681', 'The TableGeneratorSchemaVisitor class is is deprecated.', ); $this->generatorTableName = $generatorTableName; } /** * {@inheritDoc} */ public function acceptSchema(Schema $schema) { $table = $schema->createTable($this->generatorTableName); $table->addColumn('sequence_name', 'string'); $table->addColumn('sequence_value', 'integer', ['default' => 1]); $table->addColumn('sequence_increment_by', 'integer', ['default' => 1]); } /** * {@inheritDoc} */ public function acceptTable(Table $table) { } /** * {@inheritDoc} */ public function acceptColumn(Table $table, Column $column) { } /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { } /** * {@inheritDoc} */ public function acceptIndex(Table $table, Index $index) { } /** * {@inheritDoc} */ public function acceptSequence(Sequence $sequence) { } } ArrayParameters/Exception/MissingNamedParameter.php 0000755 00000000654 00000000000 0016505 0 ustar 00 <?php namespace Doctrine\DBAL\ArrayParameters\Exception; use Doctrine\DBAL\ArrayParameters\Exception; use LogicException; use function sprintf; /** @psalm-immutable */ class MissingNamedParameter extends LogicException implements Exception { public static function new(string $name): self { return new self( sprintf('Named parameter "%s" does not have a bound value.', $name), ); } } ArrayParameters/Exception/MissingPositionalParameter.php 0000755 00000000720 00000000000 0017574 0 ustar 00 <?php namespace Doctrine\DBAL\ArrayParameters\Exception; use Doctrine\DBAL\ArrayParameters\Exception; use LogicException; use function sprintf; /** * @internal * * @psalm-immutable */ class MissingPositionalParameter extends LogicException implements Exception { public static function new(int $index): self { return new self( sprintf('Positional parameter at index %d does not have a bound value.', $index), ); } } ArrayParameters/Exception.php 0000755 00000000174 00000000000 0012263 0 ustar 00 <?php namespace Doctrine\DBAL\ArrayParameters; use Throwable; /** @internal */ interface Exception extends Throwable { } Event/SchemaAlterTableRemoveColumnEventArgs.php 0000755 00000003154 00000000000 0015620 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for removing table columns are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaAlterTableRemoveColumnEventArgs extends SchemaEventArgs { private Column $column; private TableDiff $tableDiff; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; public function __construct(Column $column, TableDiff $tableDiff, AbstractPlatform $platform) { $this->column = $column; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** @return Column */ public function getColumn() { return $this->column; } /** @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableRemoveColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } Event/SchemaAlterTableChangeColumnEventArgs.php 0000755 00000003223 00000000000 0015545 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for changing table columns are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaAlterTableChangeColumnEventArgs extends SchemaEventArgs { private ColumnDiff $columnDiff; private TableDiff $tableDiff; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; public function __construct(ColumnDiff $columnDiff, TableDiff $tableDiff, AbstractPlatform $platform) { $this->columnDiff = $columnDiff; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** @return ColumnDiff */ public function getColumnDiff() { return $this->columnDiff; } /** @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableChangeColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } Event/TransactionEventArgs.php 0000755 00000000675 00000000000 0012416 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Event; use Doctrine\Common\EventArgs; use Doctrine\DBAL\Connection; /** @deprecated */ abstract class TransactionEventArgs extends EventArgs { private Connection $connection; public function __construct(Connection $connection) { $this->connection = $connection; } public function getConnection(): Connection { return $this->connection; } } Event/SchemaEventArgs.php 0000755 00000000746 00000000000 0011330 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\Common\EventArgs; /** * Base class for schema related events. * * @deprecated */ class SchemaEventArgs extends EventArgs { private bool $preventDefault = false; /** @return SchemaEventArgs */ public function preventDefault() { $this->preventDefault = true; return $this; } /** @return bool */ public function isDefaultPrevented() { return $this->preventDefault; } } Event/SchemaColumnDefinitionEventArgs.php 0000755 00000003413 00000000000 0014511 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Column; /** * Event Arguments used when the portable column definition is generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaColumnDefinitionEventArgs extends SchemaEventArgs { private ?Column $column = null; /** * Raw column data as fetched from the database. * * @var mixed[] */ private $tableColumn; /** @var string */ private $table; /** @var string */ private $database; private Connection $connection; /** * @param mixed[] $tableColumn * @param string $table * @param string $database */ public function __construct(array $tableColumn, $table, $database, Connection $connection) { $this->tableColumn = $tableColumn; $this->table = $table; $this->database = $database; $this->connection = $connection; } /** * Allows to clear the column which means the column will be excluded from * tables column list. * * @return SchemaColumnDefinitionEventArgs */ public function setColumn(?Column $column = null) { $this->column = $column; return $this; } /** @return Column|null */ public function getColumn() { return $this->column; } /** @return mixed[] */ public function getTableColumn() { return $this->tableColumn; } /** @return string */ public function getTable() { return $this->table; } /** @return string */ public function getDatabase() { return $this->database; } /** @return Connection */ public function getConnection() { return $this->connection; } } Event/SchemaIndexDefinitionEventArgs.php 0000755 00000003011 00000000000 0014315 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Index; /** * Event Arguments used when the portable index definition is generated inside {@see AbstractSchemaManager}. * * @deprecated */ class SchemaIndexDefinitionEventArgs extends SchemaEventArgs { private ?Index $index = null; /** * Raw index data as fetched from the database. * * @var mixed[] */ private array $tableIndex; /** @var string */ private $table; private Connection $connection; /** * @param mixed[] $tableIndex * @param string $table */ public function __construct(array $tableIndex, $table, Connection $connection) { $this->tableIndex = $tableIndex; $this->table = $table; $this->connection = $connection; } /** * Allows to clear the index which means the index will be excluded from tables index list. * * @return SchemaIndexDefinitionEventArgs */ public function setIndex(?Index $index = null) { $this->index = $index; return $this; } /** @return Index|null */ public function getIndex() { return $this->index; } /** @return mixed[] */ public function getTableIndex() { return $this->tableIndex; } /** @return string */ public function getTable() { return $this->table; } /** @return Connection */ public function getConnection() { return $this->connection; } } Event/Listeners/SQLiteSessionInit.php 0000755 00000001272 00000000000 0013605 0 ustar 00 <?php namespace Doctrine\DBAL\Event\Listeners; use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; /** @deprecated Use {@see \Doctrine\DBAL\Driver\AbstractSQLiteDriver\Middleware\EnableForeignKeys} instead. */ class SQLiteSessionInit implements EventSubscriber { /** * @return void * * @throws Exception */ public function postConnect(ConnectionEventArgs $args) { $args->getConnection()->executeStatement('PRAGMA foreign_keys=ON'); } /** * {@inheritDoc} */ public function getSubscribedEvents() { return [Events::postConnect]; } } Event/Listeners/OracleSessionInit.php 0000755 00000004237 00000000000 0013655 0 ustar 00 <?php namespace Doctrine\DBAL\Event\Listeners; use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use function array_change_key_case; use function array_merge; use function count; use function implode; use const CASE_UPPER; /** * Should be used when Oracle Server default environment does not match the Doctrine requirements. * * The following environment variables are required for the Doctrine default date format: * * NLS_TIME_FORMAT="HH24:MI:SS" * NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS" * NLS_TIMESTAMP_FORMAT="YYYY-MM-DD HH24:MI:SS" * NLS_TIMESTAMP_TZ_FORMAT="YYYY-MM-DD HH24:MI:SS TZH:TZM" * * @deprecated Use {@see \Doctrine\DBAL\Driver\OCI8\Middleware\InitializeSession} instead. */ class OracleSessionInit implements EventSubscriber { /** @var string[] */ protected $_defaultSessionVars = [ 'NLS_TIME_FORMAT' => 'HH24:MI:SS', 'NLS_DATE_FORMAT' => 'YYYY-MM-DD HH24:MI:SS', 'NLS_TIMESTAMP_FORMAT' => 'YYYY-MM-DD HH24:MI:SS', 'NLS_TIMESTAMP_TZ_FORMAT' => 'YYYY-MM-DD HH24:MI:SS TZH:TZM', 'NLS_NUMERIC_CHARACTERS' => '.,', ]; /** @param string[] $oracleSessionVars */ public function __construct(array $oracleSessionVars = []) { $this->_defaultSessionVars = array_merge($this->_defaultSessionVars, $oracleSessionVars); } /** * @return void * * @throws Exception */ public function postConnect(ConnectionEventArgs $args) { if (count($this->_defaultSessionVars) === 0) { return; } $vars = []; foreach (array_change_key_case($this->_defaultSessionVars, CASE_UPPER) as $option => $value) { if ($option === 'CURRENT_SCHEMA') { $vars[] = $option . ' = ' . $value; } else { $vars[] = $option . " = '" . $value . "'"; } } $sql = 'ALTER SESSION SET ' . implode(' ', $vars); $args->getConnection()->executeStatement($sql); } /** * {@inheritDoc} */ public function getSubscribedEvents() { return [Events::postConnect]; } } Event/Listeners/SQLSessionInit.php 0000755 00000001556 00000000000 0013110 0 ustar 00 <?php namespace Doctrine\DBAL\Event\Listeners; use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; /** * Session init listener for executing a single SQL statement right after a connection is opened. * * @deprecated Implement a middleware instead. */ class SQLSessionInit implements EventSubscriber { /** @var string */ protected $sql; /** @param string $sql */ public function __construct($sql) { $this->sql = $sql; } /** * @return void * * @throws Exception */ public function postConnect(ConnectionEventArgs $args) { $args->getConnection()->executeStatement($this->sql); } /** * {@inheritDoc} */ public function getSubscribedEvents() { return [Events::postConnect]; } } Event/ConnectionEventArgs.php 0000755 00000001023 00000000000 0012214 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\Common\EventArgs; use Doctrine\DBAL\Connection; /** * Event Arguments used when a Driver connection is established inside Doctrine\DBAL\Connection. * * @deprecated */ class ConnectionEventArgs extends EventArgs { private Connection $connection; public function __construct(Connection $connection) { $this->connection = $connection; } /** @return Connection */ public function getConnection() { return $this->connection; } } Event/SchemaCreateTableColumnEventArgs.php 0000755 00000003073 00000000000 0014576 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Table; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for creating table columns are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaCreateTableColumnEventArgs extends SchemaEventArgs { private Column $column; private Table $table; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; public function __construct(Column $column, Table $table, AbstractPlatform $platform) { $this->column = $column; $this->table = $table; $this->platform = $platform; } /** @return Column */ public function getColumn() { return $this->column; } /** @return Table */ public function getTable() { return $this->table; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaCreateTableColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } Event/SchemaAlterTableAddColumnEventArgs.php 0000755 00000004022 00000000000 0015046 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\Deprecations\Deprecation; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for adding table columns are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaAlterTableAddColumnEventArgs extends SchemaEventArgs { private Column $column; private TableDiff $tableDiff; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; public function __construct(Column $column, TableDiff $tableDiff, AbstractPlatform $platform) { $this->column = $column; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** @return Column */ public function getColumn() { return $this->column; } /** @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableAddColumnEventArgs */ public function addSql($sql) { if (is_array($sql)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Passing multiple SQL statements as an array to SchemaAlterTableAddColumnEventaArrgs::addSql() ' . 'is deprecated. Pass each statement as an individual argument instead.', ); } $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } Event/SchemaAlterTableEventArgs.php 0000755 00000002567 00000000000 0013273 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for creating tables are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaAlterTableEventArgs extends SchemaEventArgs { private TableDiff $tableDiff; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; public function __construct(TableDiff $tableDiff, AbstractPlatform $platform) { $this->tableDiff = $tableDiff; $this->platform = $platform; } /** @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } Event/TransactionCommitEventArgs.php 0000755 00000000226 00000000000 0013557 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Event; /** @deprecated */ class TransactionCommitEventArgs extends TransactionEventArgs { } Event/SchemaDropTableEventArgs.php 0000755 00000002321 00000000000 0013114 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Table; use InvalidArgumentException; /** * Event Arguments used when the SQL query for dropping tables are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaDropTableEventArgs extends SchemaEventArgs { /** @var string|Table */ private $table; private AbstractPlatform $platform; /** @var string|null */ private $sql; /** * @param string|Table $table * * @throws InvalidArgumentException */ public function __construct($table, AbstractPlatform $platform) { $this->table = $table; $this->platform = $platform; } /** @return string|Table */ public function getTable() { return $this->table; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * @param string $sql * * @return SchemaDropTableEventArgs */ public function setSql($sql) { $this->sql = $sql; return $this; } /** @return string|null */ public function getSql() { return $this->sql; } } Event/SchemaAlterTableRenameColumnEventArgs.php 0000755 00000003606 00000000000 0015574 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for renaming table columns are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaAlterTableRenameColumnEventArgs extends SchemaEventArgs { /** @var string */ private $oldColumnName; private Column $column; private TableDiff $tableDiff; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; /** @param string $oldColumnName */ public function __construct($oldColumnName, Column $column, TableDiff $tableDiff, AbstractPlatform $platform) { $this->oldColumnName = $oldColumnName; $this->column = $column; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** @return string */ public function getOldColumnName() { return $this->oldColumnName; } /** @return Column */ public function getColumn() { return $this->column; } /** @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableRenameColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } Event/SchemaCreateTableEventArgs.php 0000755 00000003514 00000000000 0013420 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Table; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for creating tables are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaCreateTableEventArgs extends SchemaEventArgs { private Table $table; /** @var mixed[][] */ private array $columns; /** @var mixed[] */ private array $options; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; /** * @param mixed[][] $columns * @param mixed[] $options */ public function __construct(Table $table, array $columns, array $options, AbstractPlatform $platform) { $this->table = $table; $this->columns = $columns; $this->options = $options; $this->platform = $platform; } /** @return Table */ public function getTable() { return $this->table; } /** @return mixed[][] */ public function getColumns() { return $this->columns; } /** @return mixed[] */ public function getOptions() { return $this->options; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaCreateTableEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } Event/TransactionBeginEventArgs.php 0000755 00000000225 00000000000 0013352 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Event; /** @deprecated */ class TransactionBeginEventArgs extends TransactionEventArgs { } Event/TransactionRollBackEventArgs.php 0000755 00000000230 00000000000 0014013 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Event; /** @deprecated */ class TransactionRollBackEventArgs extends TransactionEventArgs { } DriverManager.php 0000755 00000022221 00000000000 0007746 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\Common\EventManager; use Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Exception\MalformedDsnException; use Doctrine\DBAL\Tools\DsnParser; use Doctrine\Deprecations\Deprecation; use SensitiveParameter; use function array_keys; use function array_merge; use function is_a; /** * Factory for creating {@see Connection} instances. * * @psalm-type OverrideParams = array{ * application_name?: string, * charset?: string, * dbname?: string, * default_dbname?: string, * driver?: key-of<self::DRIVER_MAP>, * driverClass?: class-string<Driver>, * driverOptions?: array<mixed>, * host?: string, * password?: string, * path?: string, * persistent?: bool, * platform?: Platforms\AbstractPlatform, * port?: int, * serverVersion?: string, * url?: string, * user?: string, * unix_socket?: string, * } * @psalm-type Params = array{ * application_name?: string, * charset?: string, * dbname?: string, * defaultTableOptions?: array<string, mixed>, * default_dbname?: string, * driver?: key-of<self::DRIVER_MAP>, * driverClass?: class-string<Driver>, * driverOptions?: array<mixed>, * host?: string, * keepSlave?: bool, * keepReplica?: bool, * master?: OverrideParams, * memory?: bool, * password?: string, * path?: string, * persistent?: bool, * platform?: Platforms\AbstractPlatform, * port?: int, * primary?: OverrideParams, * replica?: array<OverrideParams>, * serverVersion?: string, * sharding?: array<string,mixed>, * slaves?: array<OverrideParams>, * url?: string, * user?: string, * wrapperClass?: class-string<Connection>, * unix_socket?: string, * } */ final class DriverManager { /** * List of supported drivers and their mappings to the driver classes. * * To add your own driver use the 'driverClass' parameter to {@see DriverManager::getConnection()}. */ private const DRIVER_MAP = [ 'pdo_mysql' => PDO\MySQL\Driver::class, 'pdo_sqlite' => PDO\SQLite\Driver::class, 'pdo_pgsql' => PDO\PgSQL\Driver::class, 'pdo_oci' => PDO\OCI\Driver::class, 'oci8' => OCI8\Driver::class, 'ibm_db2' => IBMDB2\Driver::class, 'pdo_sqlsrv' => PDO\SQLSrv\Driver::class, 'mysqli' => Mysqli\Driver::class, 'pgsql' => PgSQL\Driver::class, 'sqlsrv' => SQLSrv\Driver::class, 'sqlite3' => SQLite3\Driver::class, ]; /** * List of URL schemes from a database URL and their mappings to driver. * * @deprecated Use actual driver names instead. * * @var array<string, string> * @psalm-var array<string, key-of<self::DRIVER_MAP>> */ private static array $driverSchemeAliases = [ 'db2' => 'ibm_db2', 'mssql' => 'pdo_sqlsrv', 'mysql' => 'pdo_mysql', 'mysql2' => 'pdo_mysql', // Amazon RDS, for some weird reason 'postgres' => 'pdo_pgsql', 'postgresql' => 'pdo_pgsql', 'pgsql' => 'pdo_pgsql', 'sqlite' => 'pdo_sqlite', 'sqlite3' => 'pdo_sqlite', ]; /** * Private constructor. This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } /** * Creates a connection object based on the specified parameters. * This method returns a Doctrine\DBAL\Connection which wraps the underlying * driver connection. * * $params must contain at least one of the following. * * Either 'driver' with one of the array keys of {@see DRIVER_MAP}, * OR 'driverClass' that contains the full class name (with namespace) of the * driver class to instantiate. * * Other (optional) parameters: * * <b>user (string)</b>: * The username to use when connecting. * * <b>password (string)</b>: * The password to use when connecting. * * <b>driverOptions (array)</b>: * Any additional driver-specific options for the driver. These are just passed * through to the driver. * * <b>wrapperClass</b>: * You may specify a custom wrapper class through the 'wrapperClass' * parameter but this class MUST inherit from Doctrine\DBAL\Connection. * * <b>driverClass</b>: * The driver class to use. * * @param Configuration|null $config The configuration to use. * @param EventManager|null $eventManager The event manager to use. * @psalm-param Params $params * * @psalm-return ($params is array{wrapperClass: class-string<T>} ? T : Connection) * * @throws Exception * * @template T of Connection */ public static function getConnection( #[SensitiveParameter] array $params, ?Configuration $config = null, ?EventManager $eventManager = null ): Connection { // create default config and event manager, if not set $config ??= new Configuration(); $eventManager ??= new EventManager(); $params = self::parseDatabaseUrl($params); // URL support for PrimaryReplicaConnection if (isset($params['primary'])) { $params['primary'] = self::parseDatabaseUrl($params['primary']); } if (isset($params['replica'])) { foreach ($params['replica'] as $key => $replicaParams) { $params['replica'][$key] = self::parseDatabaseUrl($replicaParams); } } $driver = self::createDriver($params['driver'] ?? null, $params['driverClass'] ?? null); foreach ($config->getMiddlewares() as $middleware) { $driver = $middleware->wrap($driver); } $wrapperClass = $params['wrapperClass'] ?? Connection::class; if (! is_a($wrapperClass, Connection::class, true)) { throw Exception::invalidWrapperClass($wrapperClass); } return new $wrapperClass($params, $driver, $config, $eventManager); } /** * Returns the list of supported drivers. * * @return string[] * @psalm-return list<key-of<self::DRIVER_MAP>> */ public static function getAvailableDrivers(): array { return array_keys(self::DRIVER_MAP); } /** * @throws Exception * * @psalm-assert key-of<self::DRIVER_MAP>|null $driver * @psalm-assert class-string<Driver>|null $driverClass */ private static function createDriver(?string $driver, ?string $driverClass): Driver { if ($driverClass === null) { if ($driver === null) { throw Exception::driverRequired(); } if (! isset(self::DRIVER_MAP[$driver])) { throw Exception::unknownDriver($driver, array_keys(self::DRIVER_MAP)); } $driverClass = self::DRIVER_MAP[$driver]; } elseif (! is_a($driverClass, Driver::class, true)) { throw Exception::invalidDriverClass($driverClass); } return new $driverClass(); } /** * Extracts parts from a database URL, if present, and returns an * updated list of parameters. * * @param mixed[] $params The list of parameters. * @psalm-param Params $params * * @return mixed[] A modified list of parameters with info from a database * URL extracted into indidivual parameter parts. * @psalm-return Params * * @throws Exception */ private static function parseDatabaseUrl( #[SensitiveParameter] array $params ): array { if (! isset($params['url'])) { return $params; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5843', 'The "url" connection parameter is deprecated. Please use %s to parse a database url before calling %s.', DsnParser::class, self::class, ); $parser = new DsnParser(self::$driverSchemeAliases); try { $parsedParams = $parser->parse($params['url']); } catch (MalformedDsnException $e) { throw new Exception('Malformed parameter "url".', 0, $e); } if (isset($parsedParams['driver'])) { // The requested driver from the URL scheme takes precedence // over the default custom driver from the connection parameters (if any). unset($params['driverClass']); } $params = array_merge($params, $parsedParams); // If a schemeless connection URL is given, we require a default driver or default custom driver // as connection parameter. if (! isset($params['driverClass']) && ! isset($params['driver'])) { throw Exception::driverRequired($params['url']); } return $params; } } Cache/ArrayResult.php 0000755 00000004133 00000000000 0010502 0 ustar 00 <?php namespace Doctrine\DBAL\Cache; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result; use function array_values; use function count; use function reset; /** @internal The class is internal to the caching layer implementation. */ final class ArrayResult implements Result { /** @var list<array<string, mixed>> */ private array $data; private int $columnCount = 0; private int $num = 0; /** @param list<array<string, mixed>> $data */ public function __construct(array $data) { $this->data = $data; if (count($data) === 0) { return; } $this->columnCount = count($data[0]); } /** * {@inheritDoc} */ public function fetchNumeric() { $row = $this->fetch(); if ($row === false) { return false; } return array_values($row); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->fetch(); } /** * {@inheritDoc} */ public function fetchOne() { $row = $this->fetch(); if ($row === false) { return false; } return reset($row); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } public function rowCount(): int { return count($this->data); } public function columnCount(): int { return $this->columnCount; } public function free(): void { $this->data = []; } /** @return array<string, mixed>|false */ private function fetch() { if (! isset($this->data[$this->num])) { return false; } return $this->data[$this->num++]; } } Cache/CacheException.php 0000755 00000000715 00000000000 0011111 0 ustar 00 <?php namespace Doctrine\DBAL\Cache; use Doctrine\DBAL\Exception; /** @psalm-immutable */ class CacheException extends Exception { /** @return CacheException */ public static function noCacheKey() { return new self('No cache key was set.'); } /** @return CacheException */ public static function noResultDriverConfigured() { return new self('Trying to cache a query but no result driver is configured.'); } } Cache/QueryCacheProfile.php 0000755 00000012161 00000000000 0011577 0 ustar 00 <?php namespace Doctrine\DBAL\Cache; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use Psr\Cache\CacheItemPoolInterface; use TypeError; use function get_class; use function hash; use function serialize; use function sha1; use function sprintf; /** * Query Cache Profile handles the data relevant for query caching. * * It is a value object, setter methods return NEW instances. */ class QueryCacheProfile { private ?CacheItemPoolInterface $resultCache = null; /** @var int */ private $lifetime; /** @var string|null */ private $cacheKey; /** * @param int $lifetime * @param string|null $cacheKey * @param CacheItemPoolInterface|Cache|null $resultCache */ public function __construct($lifetime = 0, $cacheKey = null, ?object $resultCache = null) { $this->lifetime = $lifetime; $this->cacheKey = $cacheKey; if ($resultCache instanceof CacheItemPoolInterface) { $this->resultCache = $resultCache; } elseif ($resultCache instanceof Cache) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4620', 'Passing an instance of %s to %s as $resultCache is deprecated. Pass an instance of %s instead.', Cache::class, __METHOD__, CacheItemPoolInterface::class, ); $this->resultCache = CacheAdapter::wrap($resultCache); } elseif ($resultCache !== null) { throw new TypeError(sprintf( '$resultCache: Expected either null or an instance of %s or %s, got %s.', CacheItemPoolInterface::class, Cache::class, get_class($resultCache), )); } } public function getResultCache(): ?CacheItemPoolInterface { return $this->resultCache; } /** * @deprecated Use {@see getResultCache()} instead. * * @return Cache|null */ public function getResultCacheDriver() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4620', '%s is deprecated, call getResultCache() instead.', __METHOD__, ); return $this->resultCache !== null ? DoctrineProvider::wrap($this->resultCache) : null; } /** @return int */ public function getLifetime() { return $this->lifetime; } /** * @return string * * @throws CacheException */ public function getCacheKey() { if ($this->cacheKey === null) { throw CacheException::noCacheKey(); } return $this->cacheKey; } /** * Generates the real cache key from query, params, types and connection parameters. * * @param string $sql * @param list<mixed>|array<string, mixed> $params * @param array<int, Type|int|string|null>|array<string, Type|int|string|null> $types * @param array<string, mixed> $connectionParams * * @return array{string, string} */ public function generateCacheKeys($sql, $params, $types, array $connectionParams = []) { if (isset($connectionParams['password'])) { unset($connectionParams['password']); } $realCacheKey = 'query=' . $sql . '¶ms=' . serialize($params) . '&types=' . serialize($types) . '&connectionParams=' . hash('sha256', serialize($connectionParams)); // should the key be automatically generated using the inputs or is the cache key set? $cacheKey = $this->cacheKey ?? sha1($realCacheKey); return [$cacheKey, $realCacheKey]; } public function setResultCache(CacheItemPoolInterface $cache): QueryCacheProfile { return new QueryCacheProfile($this->lifetime, $this->cacheKey, $cache); } /** * @deprecated Use {@see setResultCache()} instead. * * @return QueryCacheProfile */ public function setResultCacheDriver(Cache $cache) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4620', '%s is deprecated, call setResultCache() instead.', __METHOD__, ); return new QueryCacheProfile($this->lifetime, $this->cacheKey, CacheAdapter::wrap($cache)); } /** * @param string|null $cacheKey * * @return QueryCacheProfile */ public function setCacheKey($cacheKey) { return new QueryCacheProfile($this->lifetime, $cacheKey, $this->resultCache); } /** * @param int $lifetime * * @return QueryCacheProfile */ public function setLifetime($lifetime) { return new QueryCacheProfile($lifetime, $this->cacheKey, $this->resultCache); } } Connection.php 0000755 00000010203 00000000000 0007314 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Contract\CommandWithDataInterface; use Pheanstalk\Contract\SocketFactoryInterface; use Pheanstalk\Contract\SocketInterface; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\ServerBadFormatException; use Pheanstalk\Exception\ServerInternalErrorException; use Pheanstalk\Exception\ServerOutOfMemoryException; use Pheanstalk\Exception\ServerUnknownCommandException; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; /** * A connection to a beanstalkd server, backed by any type of socket. * The connection is responsible for: * - managing the connection to the server via its socket factory * - dispatching commands * - parsing the response into a RawResponse object */ final class Connection { private const CRLF = "\r\n"; private const CRLF_LENGTH = 2; private SocketInterface|null $socket = null; public function __construct( private readonly SocketFactoryInterface $factory ) { } /** * Connect the socket, this is done automatically when dispatching commands */ public function connect(): void { $this->getSocket(); } /** * Disconnect the socket. * Subsequent socket operations will create a new connection. */ public function disconnect(): void { if (isset($this->socket)) { $this->socket->disconnect(); $this->socket = null; } } /** * @param SocketInterface $socket * @param int<0, max> $length * @return string * @throws Exception\ClientException */ private function readData(SocketInterface $socket, int $length): string { $result = $socket->read($length); if ($socket->read(self::CRLF_LENGTH) !== self::CRLF) { throw new Exception\ClientException(sprintf( 'Expected %u bytes of CRLF after %u bytes of data', self::CRLF_LENGTH, $length )); } return $result; } private function readRawResponse(string $commandLine): RawResponse { $socket = $this->getSocket(); // This is always a simple line consisting of a response type name and 0 - 2 optional numerical arguments. $responseLine = $socket->getLine(); $responseParts = explode(' ', $responseLine); // count($responseParts) == 1|2|3 $responseType = ResponseType::from(array_shift($responseParts)); // count($responseParts) == 1|2 if ($responseType->hasData()) { $dataLength = (int) array_pop($responseParts); if ($dataLength < 0) { throw MalformedResponseException::negativeDataLength(); } $data = $this->readData($socket, $dataLength); } else { $data = null; } // count($responseParts) = 0|1 return match ($responseType) { ResponseType::OutOfMemory => throw new ServerOutOfMemoryException(), ResponseType::InternalError => throw new ServerInternalErrorException(), ResponseType::BadFormat => throw new ServerBadFormatException($commandLine), ResponseType::UnknownCommand => throw new ServerUnknownCommandException(), default => new RawResponse($responseType, array_pop($responseParts), $data ?? null) }; } public function dispatchCommand(CommandInterface $command): RawResponse { $socket = $this->getSocket(); $commandLine = $command->getCommandLine(); $buffer = $commandLine . self::CRLF; if ($command instanceof CommandWithDataInterface) { $buffer .= $command->getData() . self::CRLF; } $socket->write($buffer); return $this->readRawResponse($commandLine); } /** * Socket handle for the connection to beanstalkd. * @throws Exception\ConnectionException */ private function getSocket(): SocketInterface { if (!isset($this->socket)) { $this->socket = $this->factory->create(); } return $this->socket; } } Statement.php 0000755 00000016726 00000000000 0007201 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function func_num_args; use function is_string; /** * A database abstraction-level statement that implements support for logging, DBAL mapping types, etc. */ class Statement { /** * The SQL statement. * * @var string */ protected $sql; /** * The bound parameters. * * @var mixed[] */ protected $params = []; /** * The parameter types. * * @var int[]|string[] */ protected $types = []; /** * The underlying driver statement. * * @var Driver\Statement */ protected $stmt; /** * The underlying database platform. * * @var AbstractPlatform */ protected $platform; /** * The connection this statement is bound to and executed on. * * @var Connection */ protected $conn; /** * Creates a new <tt>Statement</tt> for the given SQL and <tt>Connection</tt>. * * @internal The statement can be only instantiated by {@see Connection}. * * @param Connection $conn The connection for handling statement errors. * @param Driver\Statement $statement The underlying driver-level statement. * @param string $sql The SQL of the statement. * * @throws Exception */ public function __construct(Connection $conn, Driver\Statement $statement, string $sql) { $this->conn = $conn; $this->stmt = $statement; $this->sql = $sql; $this->platform = $conn->getDatabasePlatform(); } /** * Binds a parameter value to the statement. * * The value can optionally be bound with a DBAL mapping type. * If bound with a DBAL mapping type, the binding type is derived from the mapping * type and the value undergoes the conversion routines of the mapping type before * being bound. * * @param string|int $param The name or position of the parameter. * @param mixed $value The value of the parameter. * @param mixed $type Either a PDO binding type or a DBAL mapping type name or instance. * * @return bool TRUE on success, FALSE on failure. * * @throws Exception */ public function bindValue($param, $value, $type = ParameterType::STRING) { $this->params[$param] = $value; $this->types[$param] = $type; $bindingType = ParameterType::STRING; if ($type !== null) { if (is_string($type)) { $type = Type::getType($type); } $bindingType = $type; if ($type instanceof Type) { $value = $type->convertToDatabaseValue($value, $this->platform); $bindingType = $type->getBindingType(); } } try { return $this->stmt->bindValue($param, $value, $bindingType); } catch (Driver\Exception $e) { throw $this->conn->convertException($e); } } /** * Binds a parameter to a value by reference. * * Binding a parameter by reference does not support DBAL mapping types. * * @deprecated Use {@see bindValue()} instead. * * @param string|int $param The name or position of the parameter. * @param mixed $variable The reference to the variable to bind. * @param int $type The binding type. * @param int|null $length Must be specified when using an OUT bind * so that PHP allocates enough memory to hold the returned value. * * @return bool TRUE on success, FALSE on failure. * * @throws Exception */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); $this->params[$param] = $variable; $this->types[$param] = $type; try { if (func_num_args() > 3) { return $this->stmt->bindParam($param, $variable, $type, $length); } return $this->stmt->bindParam($param, $variable, $type); } catch (Driver\Exception $e) { throw $this->conn->convertException($e); } } /** * Executes the statement with the currently bound parameters. * * @deprecated Statement::execute() is deprecated, use Statement::executeQuery() or executeStatement() instead * * @param mixed[]|null $params * * @throws Exception */ public function execute($params = null): Result { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4580', '%s() is deprecated, use Statement::executeQuery() or Statement::executeStatement() instead', __METHOD__, ); if ($params !== null) { $this->params = $params; } $logger = $this->conn->getConfiguration()->getSQLLogger(); if ($logger !== null) { $logger->startQuery($this->sql, $this->params, $this->types); } try { return new Result( $this->stmt->execute($params), $this->conn, ); } catch (Driver\Exception $ex) { throw $this->conn->convertExceptionDuringQuery($ex, $this->sql, $this->params, $this->types); } finally { if ($logger !== null) { $logger->stopQuery(); } } } /** * Executes the statement with the currently bound parameters and return result. * * @param mixed[] $params * * @throws Exception */ public function executeQuery(array $params = []): Result { if (func_num_args() > 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::executeQuery() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); } if ($params === []) { $params = null; // Workaround as long execute() exists and used internally. } return $this->execute($params); } /** * Executes the statement with the currently bound parameters and return affected rows. * * @param mixed[] $params * * @throws Exception */ public function executeStatement(array $params = []): int { if (func_num_args() > 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::executeStatement() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); } if ($params === []) { $params = null; // Workaround as long execute() exists and used internally. } return $this->execute($params)->rowCount(); } /** * Gets the wrapped driver statement. * * @return Driver\Statement */ public function getWrappedStatement() { return $this->stmt; } } SQL/Parser/Visitor.php 0000755 00000000765 00000000000 0010563 0 ustar 00 <?php namespace Doctrine\DBAL\SQL\Parser; /** * SQL parser visitor * * @internal */ interface Visitor { /** * Accepts an SQL fragment containing a positional parameter */ public function acceptPositionalParameter(string $sql): void; /** * Accepts an SQL fragment containing a named parameter */ public function acceptNamedParameter(string $sql): void; /** * Accepts other SQL fragments */ public function acceptOther(string $sql): void; } SQL/Parser/Exception/RegularExpressionError.php 0000755 00000000625 00000000000 0015550 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\SQL\Parser\Exception; use Doctrine\DBAL\SQL\Parser\Exception; use RuntimeException; use function preg_last_error; use function preg_last_error_msg; class RegularExpressionError extends RuntimeException implements Exception { public static function new(): self { return new self(preg_last_error_msg(), preg_last_error()); } } SQL/Parser/Exception.php 0000755 00000000200 00000000000 0011042 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\SQL\Parser; use Throwable; interface Exception extends Throwable { } SQL/Builder/CreateSchemaObjectsSQLBuilder.php 0000755 00000003630 00000000000 0014775 0 ustar 00 <?php namespace Doctrine\DBAL\SQL\Builder; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use function array_merge; final class CreateSchemaObjectsSQLBuilder { private AbstractPlatform $platform; public function __construct(AbstractPlatform $platform) { $this->platform = $platform; } /** * @return list<string> * * @throws Exception */ public function buildSQL(Schema $schema): array { return array_merge( $this->buildNamespaceStatements($schema->getNamespaces()), $this->buildSequenceStatements($schema->getSequences()), $this->buildTableStatements($schema->getTables()), ); } /** * @param list<string> $namespaces * * @return list<string> * * @throws Exception */ private function buildNamespaceStatements(array $namespaces): array { $statements = []; if ($this->platform->supportsSchemas()) { foreach ($namespaces as $namespace) { $statements[] = $this->platform->getCreateSchemaSQL($namespace); } } return $statements; } /** * @param list<Table> $tables * * @return list<string> * * @throws Exception */ private function buildTableStatements(array $tables): array { return $this->platform->getCreateTablesSQL($tables); } /** * @param list<Sequence> $sequences * * @return list<string> * * @throws Exception */ private function buildSequenceStatements(array $sequences): array { $statements = []; foreach ($sequences as $sequence) { $statements[] = $this->platform->getCreateSequenceSQL($sequence); } return $statements; } } SQL/Builder/SelectSQLBuilder.php 0000755 00000000346 00000000000 0012357 0 ustar 00 <?php namespace Doctrine\DBAL\SQL\Builder; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Query\SelectQuery; interface SelectSQLBuilder { /** @throws Exception */ public function buildSQL(SelectQuery $query): string; } SQL/Builder/DefaultSelectSQLBuilder.php 0000755 00000004772 00000000000 0013673 0 ustar 00 <?php namespace Doctrine\DBAL\SQL\Builder; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode; use Doctrine\DBAL\Query\SelectQuery; use function count; use function implode; final class DefaultSelectSQLBuilder implements SelectSQLBuilder { private AbstractPlatform $platform; private ?string $forUpdateSQL; private ?string $skipLockedSQL; /** @internal The SQL builder should be instantiated only by database platforms. */ public function __construct(AbstractPlatform $platform, ?string $forUpdateSQL, ?string $skipLockedSQL) { $this->platform = $platform; $this->forUpdateSQL = $forUpdateSQL; $this->skipLockedSQL = $skipLockedSQL; } /** @throws Exception */ public function buildSQL(SelectQuery $query): string { $parts = ['SELECT']; if ($query->isDistinct()) { $parts[] = 'DISTINCT'; } $parts[] = implode(', ', $query->getColumns()); $from = $query->getFrom(); if (count($from) > 0) { $parts[] = 'FROM ' . implode(', ', $from); } $where = $query->getWhere(); if ($where !== null) { $parts[] = 'WHERE ' . $where; } $groupBy = $query->getGroupBy(); if (count($groupBy) > 0) { $parts[] = 'GROUP BY ' . implode(', ', $groupBy); } $having = $query->getHaving(); if ($having !== null) { $parts[] = 'HAVING ' . $having; } $orderBy = $query->getOrderBy(); if (count($orderBy) > 0) { $parts[] = 'ORDER BY ' . implode(', ', $orderBy); } $sql = implode(' ', $parts); $limit = $query->getLimit(); if ($limit->isDefined()) { $sql = $this->platform->modifyLimitQuery($sql, $limit->getMaxResults(), $limit->getFirstResult()); } $forUpdate = $query->getForUpdate(); if ($forUpdate !== null) { if ($this->forUpdateSQL === null) { throw Exception::notSupported('FOR UPDATE'); } $sql .= ' ' . $this->forUpdateSQL; if ($forUpdate->getConflictResolutionMode() === ConflictResolutionMode::SKIP_LOCKED) { if ($this->skipLockedSQL === null) { throw Exception::notSupported('SKIP LOCKED'); } $sql .= ' ' . $this->skipLockedSQL; } } return $sql; } } SQL/Builder/DropSchemaObjectsSQLBuilder.php 0000755 00000002527 00000000000 0014502 0 ustar 00 <?php namespace Doctrine\DBAL\SQL\Builder; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use function array_merge; final class DropSchemaObjectsSQLBuilder { private AbstractPlatform $platform; public function __construct(AbstractPlatform $platform) { $this->platform = $platform; } /** * @return list<string> * * @throws Exception */ public function buildSQL(Schema $schema): array { return array_merge( $this->buildSequenceStatements($schema->getSequences()), $this->buildTableStatements($schema->getTables()), ); } /** * @param list<Table> $tables * * @return list<string> */ private function buildTableStatements(array $tables): array { return $this->platform->getDropTablesSQL($tables); } /** * @param list<Sequence> $sequences * * @return list<string> * * @throws Exception */ private function buildSequenceStatements(array $sequences): array { $statements = []; foreach ($sequences as $sequence) { $statements[] = $this->platform->getDropSequenceSQL($sequence); } return $statements; } } SQL/Parser.php 0000755 00000010115 00000000000 0007112 0 ustar 00 <?php namespace Doctrine\DBAL\SQL; use Doctrine\DBAL\SQL\Parser\Exception; use Doctrine\DBAL\SQL\Parser\Exception\RegularExpressionError; use Doctrine\DBAL\SQL\Parser\Visitor; use function array_merge; use function assert; use function current; use function implode; use function key; use function next; use function preg_last_error; use function preg_match; use function reset; use function sprintf; use function strlen; use const PREG_NO_ERROR; /** * The SQL parser that focuses on identifying prepared statement parameters. It implements parsing other tokens like * string literals and comments only as a way to not confuse their contents with the the parameter placeholders. * * The parsing logic and the implementation is inspired by the PHP PDO parser. * * @internal * * @see https://github.com/php/php-src/blob/php-7.4.12/ext/pdo/pdo_sql_parser.re#L49-L69 */ final class Parser { private const SPECIAL_CHARS = ':\?\'"`\\[\\-\\/'; private const BACKTICK_IDENTIFIER = '`[^`]*`'; private const BRACKET_IDENTIFIER = '(?<!\b(?i:ARRAY))\[(?:[^\]])*\]'; private const MULTICHAR = ':{2,}'; private const NAMED_PARAMETER = ':[a-zA-Z0-9_]+'; private const POSITIONAL_PARAMETER = '(?<!\\?)\\?(?!\\?)'; private const ONE_LINE_COMMENT = '--[^\r\n]*'; private const MULTI_LINE_COMMENT = '/\*([^*]+|\*+[^/*])*\**\*/'; private const SPECIAL = '[' . self::SPECIAL_CHARS . ']'; private const OTHER = '[^' . self::SPECIAL_CHARS . ']+'; private string $sqlPattern; public function __construct(bool $mySQLStringEscaping) { if ($mySQLStringEscaping) { $patterns = [ $this->getMySQLStringLiteralPattern("'"), $this->getMySQLStringLiteralPattern('"'), ]; } else { $patterns = [ $this->getAnsiSQLStringLiteralPattern("'"), $this->getAnsiSQLStringLiteralPattern('"'), ]; } $patterns = array_merge($patterns, [ self::BACKTICK_IDENTIFIER, self::BRACKET_IDENTIFIER, self::MULTICHAR, self::ONE_LINE_COMMENT, self::MULTI_LINE_COMMENT, self::OTHER, ]); $this->sqlPattern = sprintf('(%s)', implode('|', $patterns)); } /** * Parses the given SQL statement * * @throws Exception */ public function parse(string $sql, Visitor $visitor): void { /** @var array<string,callable> $patterns */ $patterns = [ self::NAMED_PARAMETER => static function (string $sql) use ($visitor): void { $visitor->acceptNamedParameter($sql); }, self::POSITIONAL_PARAMETER => static function (string $sql) use ($visitor): void { $visitor->acceptPositionalParameter($sql); }, $this->sqlPattern => static function (string $sql) use ($visitor): void { $visitor->acceptOther($sql); }, self::SPECIAL => static function (string $sql) use ($visitor): void { $visitor->acceptOther($sql); }, ]; $offset = 0; while (($handler = current($patterns)) !== false) { if (preg_match('~\G' . key($patterns) . '~s', $sql, $matches, 0, $offset) === 1) { $handler($matches[0]); reset($patterns); $offset += strlen($matches[0]); } elseif (preg_last_error() !== PREG_NO_ERROR) { // @codeCoverageIgnoreStart throw RegularExpressionError::new(); // @codeCoverageIgnoreEnd } else { next($patterns); } } assert($offset === strlen($sql)); } private function getMySQLStringLiteralPattern(string $delimiter): string { return $delimiter . '((\\\\.)|(?![' . $delimiter . '\\\\]).)*' . $delimiter; } private function getAnsiSQLStringLiteralPattern(string $delimiter): string { return $delimiter . '[^' . $delimiter . ']*' . $delimiter; } } Connections/PrimaryReadReplicaConnection.php 0000755 00000025634 00000000000 0015254 0 ustar 00 <?php namespace Doctrine\DBAL\Connections; use Doctrine\Common\EventManager; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection as DriverConnection; use Doctrine\DBAL\Driver\Exception as DriverException; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Statement; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use SensitiveParameter; use function array_rand; use function count; /** * Primary-Replica Connection * * Connection can be used with primary-replica setups. * * Important for the understanding of this connection should be how and when * it picks the replica or primary. * * 1. Replica if primary was never picked before and ONLY if 'getWrappedConnection' * or 'executeQuery' is used. * 2. Primary picked when 'executeStatement', 'insert', 'delete', 'update', 'createSavepoint', * 'releaseSavepoint', 'beginTransaction', 'rollback', 'commit' or 'prepare' is called. * 3. If Primary was picked once during the lifetime of the connection it will always get picked afterwards. * 4. One replica connection is randomly picked ONCE during a request. * * ATTENTION: You can write to the replica with this connection if you execute a write query without * opening up a transaction. For example: * * $conn = DriverManager::getConnection(...); * $conn->executeQuery("DELETE FROM table"); * * Be aware that Connection#executeQuery is a method specifically for READ * operations only. * * Use Connection#executeStatement for any SQL statement that changes/updates * state in the database (UPDATE, INSERT, DELETE or DDL statements). * * This connection is limited to replica operations using the * Connection#executeQuery operation only, because it wouldn't be compatible * with the ORM or SchemaManager code otherwise. Both use all the other * operations in a context where writes could happen to a replica, which makes * this restricted approach necessary. * * You can manually connect to the primary at any time by calling: * * $conn->ensureConnectedToPrimary(); * * Instantiation through the DriverManager looks like: * * @psalm-import-type Params from DriverManager * @example * * $conn = DriverManager::getConnection(array( * 'wrapperClass' => 'Doctrine\DBAL\Connections\PrimaryReadReplicaConnection', * 'driver' => 'pdo_mysql', * 'primary' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''), * 'replica' => array( * array('user' => 'replica1', 'password' => '', 'host' => '', 'dbname' => ''), * array('user' => 'replica2', 'password' => '', 'host' => '', 'dbname' => ''), * ) * )); * * You can also pass 'driverOptions' and any other documented option to each of this drivers * to pass additional information. */ class PrimaryReadReplicaConnection extends Connection { /** * Primary and Replica connection (one of the randomly picked replicas). * * @var DriverConnection[]|null[] */ protected $connections = ['primary' => null, 'replica' => null]; /** * You can keep the replica connection and then switch back to it * during the request if you know what you are doing. * * @var bool */ protected $keepReplica = false; /** * Creates Primary Replica Connection. * * @internal The connection can be only instantiated by the driver manager. * * @param array<string,mixed> $params * @psalm-param Params $params * * @throws Exception * @throws InvalidArgumentException */ public function __construct( array $params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null ) { if (! isset($params['replica'], $params['primary'])) { throw new InvalidArgumentException('primary or replica configuration missing'); } if (count($params['replica']) === 0) { throw new InvalidArgumentException('You have to configure at least one replica.'); } if (isset($params['driver'])) { $params['primary']['driver'] = $params['driver']; foreach ($params['replica'] as $replicaKey => $replica) { $params['replica'][$replicaKey]['driver'] = $params['driver']; } } $this->keepReplica = (bool) ($params['keepReplica'] ?? false); parent::__construct($params, $driver, $config, $eventManager); } /** * Checks if the connection is currently towards the primary or not. */ public function isConnectedToPrimary(): bool { return $this->_conn !== null && $this->_conn === $this->connections['primary']; } /** * @param string|null $connectionName * * @return bool */ public function connect($connectionName = null) { if ($connectionName !== null) { throw new InvalidArgumentException( 'Passing a connection name as first argument is not supported anymore.' . ' Use ensureConnectedToPrimary()/ensureConnectedToReplica() instead.', ); } return $this->performConnect(); } protected function performConnect(?string $connectionName = null): bool { $requestedConnectionChange = ($connectionName !== null); $connectionName = $connectionName ?? 'replica'; if ($connectionName !== 'replica' && $connectionName !== 'primary') { throw new InvalidArgumentException('Invalid option to connect(), only primary or replica allowed.'); } // If we have a connection open, and this is not an explicit connection // change request, then abort right here, because we are already done. // This prevents writes to the replica in case of "keepReplica" option enabled. if ($this->_conn !== null && ! $requestedConnectionChange) { return false; } $forcePrimaryAsReplica = false; if ($this->getTransactionNestingLevel() > 0) { $connectionName = 'primary'; $forcePrimaryAsReplica = true; } if (isset($this->connections[$connectionName])) { $this->_conn = $this->connections[$connectionName]; if ($forcePrimaryAsReplica && ! $this->keepReplica) { $this->connections['replica'] = $this->_conn; } return false; } if ($connectionName === 'primary') { $this->connections['primary'] = $this->_conn = $this->connectTo($connectionName); // Set replica connection to primary to avoid invalid reads if (! $this->keepReplica) { $this->connections['replica'] = $this->connections['primary']; } } else { $this->connections['replica'] = $this->_conn = $this->connectTo($connectionName); } if ($this->_eventManager->hasListeners(Events::postConnect)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated. Implement a middleware instead.', Events::postConnect, ); $eventArgs = new ConnectionEventArgs($this); $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); } return true; } /** * Connects to the primary node of the database cluster. * * All following statements after this will be executed against the primary node. */ public function ensureConnectedToPrimary(): bool { return $this->performConnect('primary'); } /** * Connects to a replica node of the database cluster. * * All following statements after this will be executed against the replica node, * unless the keepReplica option is set to false and a primary connection * was already opened. */ public function ensureConnectedToReplica(): bool { return $this->performConnect('replica'); } /** * Connects to a specific connection. * * @param string $connectionName * * @return DriverConnection * * @throws Exception */ protected function connectTo($connectionName) { $params = $this->getParams(); $connectionParams = $this->chooseConnectionConfiguration($connectionName, $params); try { return $this->_driver->connect($connectionParams); } catch (DriverException $e) { throw $this->convertException($e); } } /** * @param string $connectionName * @param mixed[] $params * * @return mixed */ protected function chooseConnectionConfiguration( $connectionName, #[SensitiveParameter] $params ) { if ($connectionName === 'primary') { return $params['primary']; } $config = $params['replica'][array_rand($params['replica'])]; if (! isset($config['charset']) && isset($params['primary']['charset'])) { $config['charset'] = $params['primary']['charset']; } return $config; } /** * {@inheritDoc} */ public function executeStatement($sql, array $params = [], array $types = []) { $this->ensureConnectedToPrimary(); return parent::executeStatement($sql, $params, $types); } /** * {@inheritDoc} */ public function beginTransaction() { $this->ensureConnectedToPrimary(); return parent::beginTransaction(); } /** * {@inheritDoc} */ public function commit() { $this->ensureConnectedToPrimary(); return parent::commit(); } /** * {@inheritDoc} */ public function rollBack() { $this->ensureConnectedToPrimary(); return parent::rollBack(); } /** * {@inheritDoc} */ public function close() { unset($this->connections['primary'], $this->connections['replica']); parent::close(); $this->_conn = null; $this->connections = ['primary' => null, 'replica' => null]; } /** * {@inheritDoc} */ public function createSavepoint($savepoint) { $this->ensureConnectedToPrimary(); parent::createSavepoint($savepoint); } /** * {@inheritDoc} */ public function releaseSavepoint($savepoint) { $this->ensureConnectedToPrimary(); parent::releaseSavepoint($savepoint); } /** * {@inheritDoc} */ public function rollbackSavepoint($savepoint) { $this->ensureConnectedToPrimary(); parent::rollbackSavepoint($savepoint); } public function prepare(string $sql): Statement { $this->ensureConnectedToPrimary(); return parent::prepare($sql); } } Types/DateTimeTzImmutableType.php 0000755 00000004034 00000000000 0013042 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; /** * Immutable type of {@see DateTimeTzType}. */ class DateTimeTzImmutableType extends DateTimeTzType { /** * {@inheritDoc} */ public function getName() { return Types::DATETIMETZ_IMMUTABLE; } /** * {@inheritDoc} * * @psalm-param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { return $value->format($platform->getDateTimeTzFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeImmutable) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat($platform->getDateTimeTzFormatString(), $value); if ($dateTime !== false) { return $dateTime; } throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateTimeTzFormatString(), ); } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } Types/Types.php 0000755 00000003060 00000000000 0007430 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Types; /** * Default built-in types provided by Doctrine DBAL. */ final class Types { /** @deprecated Use {@link Types::JSON} instead. */ public const ARRAY = 'array'; public const ASCII_STRING = 'ascii_string'; public const BIGINT = 'bigint'; public const BINARY = 'binary'; public const BLOB = 'blob'; public const BOOLEAN = 'boolean'; public const DATE_MUTABLE = 'date'; public const DATE_IMMUTABLE = 'date_immutable'; public const DATEINTERVAL = 'dateinterval'; public const DATETIME_MUTABLE = 'datetime'; public const DATETIME_IMMUTABLE = 'datetime_immutable'; public const DATETIMETZ_MUTABLE = 'datetimetz'; public const DATETIMETZ_IMMUTABLE = 'datetimetz_immutable'; public const DECIMAL = 'decimal'; public const FLOAT = 'float'; public const GUID = 'guid'; public const INTEGER = 'integer'; public const JSON = 'json'; /** @deprecated Use {@link Types::JSON} instead. */ public const OBJECT = 'object'; public const SIMPLE_ARRAY = 'simple_array'; public const SMALLINT = 'smallint'; public const STRING = 'string'; public const TEXT = 'text'; public const TIME_MUTABLE = 'time'; public const TIME_IMMUTABLE = 'time_immutable'; /** @codeCoverageIgnore */ private function __construct() { } } Types/ConversionException.php 0000755 00000007157 00000000000 0012343 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Exception; use Throwable; use function func_get_arg; use function func_num_args; use function get_class; use function gettype; use function implode; use function is_object; use function is_scalar; use function sprintf; use function strlen; use function substr; use function var_export; /** * Conversion Exception is thrown when the database to PHP conversion fails. * * @psalm-immutable */ class ConversionException extends Exception { /** * Thrown when a Database to Doctrine Type Conversion fails. * * @param mixed $value * @param string $toType * * @return ConversionException */ public static function conversionFailed($value, $toType, ?Throwable $previous = null) { $value = strlen($value) > 32 ? substr($value, 0, 20) . '...' : $value; return new self('Could not convert database value "' . $value . '" to Doctrine Type ' . $toType, 0, $previous); } /** * Thrown when a Database to Doctrine Type Conversion fails and we can make a statement * about the expected format. * * @param mixed $value * @param string $toType * @param string $expectedFormat * * @return ConversionException */ public static function conversionFailedFormat($value, $toType, $expectedFormat, ?Throwable $previous = null) { $value = strlen($value) > 32 ? substr($value, 0, 20) . '...' : $value; return new self( 'Could not convert database value "' . $value . '" to Doctrine Type ' . $toType . '. Expected format: ' . $expectedFormat, 0, $previous, ); } /** * Thrown when the PHP value passed to the converter was not of the expected type. * * @param mixed $value * @param string $toType * @param string[] $possibleTypes * * @return ConversionException */ public static function conversionFailedInvalidType( $value, $toType, array $possibleTypes, ?Throwable $previous = null ) { if (is_scalar($value) || $value === null) { return new self(sprintf( 'Could not convert PHP value %s to type %s. Expected one of the following types: %s', var_export($value, true), $toType, implode(', ', $possibleTypes), ), 0, $previous); } return new self(sprintf( 'Could not convert PHP value of type %s to type %s. Expected one of the following types: %s', is_object($value) ? get_class($value) : gettype($value), $toType, implode(', ', $possibleTypes), ), 0, $previous); } /** * @param mixed $value * @param string $format * @param string $error * * @return ConversionException */ public static function conversionFailedSerialization($value, $format, $error /*, ?Throwable $previous = null */) { $actualType = is_object($value) ? get_class($value) : gettype($value); return new self(sprintf( "Could not convert PHP type '%s' to '%s', as an '%s' error was triggered by the serialization", $actualType, $format, $error, ), 0, func_num_args() >= 4 ? func_get_arg(3) : null); } public static function conversionFailedUnserialization(string $format, string $error): self { return new self(sprintf( "Could not convert database value to '%s' as an error was triggered by the unserialization: '%s'", $format, $error, )); } } Types/BooleanType.php 0000755 00000003340 00000000000 0010546 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\Deprecations\Deprecation; /** * Type that maps an SQL boolean to a PHP boolean. */ class BooleanType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBooleanTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return $platform->convertBooleansToDatabaseValue($value); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : bool) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $platform->convertFromBoolean($value); } /** * {@inheritDoc} */ public function getName() { return Types::BOOLEAN; } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::BOOLEAN; } /** * @deprecated * * @return bool */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); // We require a commented boolean type in order to distinguish between // boolean and smallint as both (have to) map to the same native type. return $platform instanceof DB2Platform; } } Types/DateType.php 0000755 00000005243 00000000000 0010050 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeImmutable; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function get_class; /** * Type that maps an SQL DATE to a PHP Date object. */ class DateType extends Type { /** * {@inheritDoc} */ public function getName() { return Types::DATE_MUTABLE; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getDateTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @psalm-param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeInterface) { if ($value instanceof DateTimeImmutable) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6017', 'Passing an instance of %s is deprecated, use %s::%s() instead.', get_class($value), DateImmutableType::class, __FUNCTION__, ); } return $value->format($platform->getDateFormatString()); } throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', DateTime::class]); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeInterface) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value instanceof DateTimeImmutable) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6017', 'Passing an instance of %s is deprecated, use %s::%s() instead.', get_class($value), DateImmutableType::class, __FUNCTION__, ); } if ($value === null || $value instanceof DateTimeInterface) { return $value; } $dateTime = DateTime::createFromFormat('!' . $platform->getDateFormatString(), $value); if ($dateTime !== false) { return $dateTime; } throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateFormatString(), ); } } Types/BlobType.php 0000755 00000002465 00000000000 0010054 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use function assert; use function fopen; use function fseek; use function fwrite; use function is_resource; use function is_string; /** * Type that maps an SQL BLOB to a PHP resource stream. */ class BlobType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBlobTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } if (is_string($value)) { $fp = fopen('php://temp', 'rb+'); assert(is_resource($fp)); fwrite($fp, $value); fseek($fp, 0); $value = $fp; } if (! is_resource($value)) { throw ConversionException::conversionFailed($value, Types::BLOB); } return $value; } /** * {@inheritDoc} */ public function getName() { return Types::BLOB; } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::LARGE_OBJECT; } } Types/VarDateTimeImmutableType.php 0000755 00000003644 00000000000 0013203 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use Exception; /** * Immutable type of {@see VarDateTimeType}. */ class VarDateTimeImmutableType extends VarDateTimeType { /** * {@inheritDoc} */ public function getName() { return Types::DATETIME_IMMUTABLE; } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { return $value->format($platform->getDateTimeFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeImmutable) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } try { $dateTime = new DateTimeImmutable($value); } catch (Exception $e) { throw ConversionException::conversionFailed($value, $this->getName(), $e); } return $dateTime; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } Types/DecimalType.php 0000755 00000002045 00000000000 0010526 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use function is_float; use function is_int; use const PHP_VERSION_ID; /** * Type that maps an SQL DECIMAL to a PHP string. */ class DecimalType extends Type { /** * {@inheritDoc} */ public function getName() { return Types::DECIMAL; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getDecimalTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { // Some drivers starting from PHP 8.1 can represent decimals as float/int // See also: https://github.com/doctrine/dbal/pull/4818 if ((PHP_VERSION_ID >= 80100 || $platform instanceof SqlitePlatform) && (is_float($value) || is_int($value))) { return (string) $value; } return $value; } } Types/BinaryType.php 0000755 00000002503 00000000000 0010413 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use function assert; use function fopen; use function fseek; use function fwrite; use function is_resource; use function is_string; /** * Type that maps ab SQL BINARY/VARBINARY to a PHP resource stream. */ class BinaryType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBinaryTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } if (is_string($value)) { $fp = fopen('php://temp', 'rb+'); assert(is_resource($fp)); fwrite($fp, $value); fseek($fp, 0); $value = $fp; } if (! is_resource($value)) { throw ConversionException::conversionFailed($value, Types::BINARY); } return $value; } /** * {@inheritDoc} */ public function getName() { return Types::BINARY; } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::BINARY; } } Types/DateTimeType.php 0000755 00000005646 00000000000 0010676 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeImmutable; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use Exception; use function get_class; /** * Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object. */ class DateTimeType extends Type implements PhpDateTimeMappingType { /** * {@inheritDoc} */ public function getName() { return Types::DATETIME_MUTABLE; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getDateTimeTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6017', 'Passing an instance of %s is deprecated, use %s::%s() instead.', get_class($value), DateTimeImmutableType::class, __FUNCTION__, ); } if ($value instanceof DateTimeInterface) { return $value->format($platform->getDateTimeFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTime::class, DateTimeImmutable::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeInterface) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value instanceof DateTimeImmutable) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6017', 'Passing an instance of %s is deprecated, use %s::%s() instead.', get_class($value), DateTimeImmutableType::class, __FUNCTION__, ); } if ($value === null || $value instanceof DateTimeInterface) { return $value; } $dateTime = DateTime::createFromFormat($platform->getDateTimeFormatString(), $value); if ($dateTime !== false) { return $dateTime; } try { return new DateTime($value); } catch (Exception $e) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateTimeFormatString(), $e, ); } } } Types/AsciiStringType.php 0000755 00000001106 00000000000 0011404 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; final class AsciiStringType extends StringType { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { return $platform->getAsciiStringTypeDeclarationSQL($column); } public function getBindingType(): int { return ParameterType::ASCII; } public function getName(): string { return Types::ASCII_STRING; } } Types/SmallIntType.php 0000755 00000001707 00000000000 0010717 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps a database SMALLINT to a PHP integer. */ class SmallIntType extends Type implements PhpIntegerMappingType { /** * {@inheritDoc} */ public function getName() { return Types::SMALLINT; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getSmallIntTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : int) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (int) $value; } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::INTEGER; } } Types/BigIntType.php 0000755 00000001703 00000000000 0010344 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps a database BIGINT to a PHP string. */ class BigIntType extends Type implements PhpIntegerMappingType { /** * {@inheritDoc} */ public function getName() { return Types::BIGINT; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBigIntTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::STRING; } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (string) $value; } } Types/FloatType.php 0000755 00000001276 00000000000 0010242 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; class FloatType extends Type { /** * {@inheritDoc} */ public function getName() { return Types::FLOAT; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getFloatDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : float) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (float) $value; } } Types/TextType.php 0000755 00000001335 00000000000 0010115 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use function is_resource; use function stream_get_contents; /** * Type that maps an SQL CLOB to a PHP string. */ class TextType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { return is_resource($value) ? stream_get_contents($value) : $value; } /** * {@inheritDoc} */ public function getName() { return Types::TEXT; } } Types/TimeType.php 0000755 00000005171 00000000000 0010071 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeImmutable; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function get_class; /** * Type that maps an SQL TIME to a PHP DateTime object. */ class TimeType extends Type { /** * {@inheritDoc} */ public function getName() { return Types::TIME_MUTABLE; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getTimeTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6017', 'Passing an instance of %s is deprecated, use %s::%s() instead.', get_class($value), TimeImmutableType::class, __FUNCTION__, ); } if ($value instanceof DateTimeInterface) { return $value->format($platform->getTimeFormatString()); } throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', DateTime::class]); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeInterface) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value instanceof DateTimeImmutable) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6017', 'Passing an instance of %s is deprecated, use %s::%s() instead.', get_class($value), TimeImmutableType::class, __FUNCTION__, ); } if ($value === null || $value instanceof DateTimeInterface) { return $value; } $dateTime = DateTime::createFromFormat('!' . $platform->getTimeFormatString(), $value); if ($dateTime !== false) { return $dateTime; } throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getTimeFormatString(), ); } } Types/DateTimeTzType.php 0000755 00000007342 00000000000 0011207 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeImmutable; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function get_class; /** * DateTime type accepting additional information about timezone offsets. * * Caution: Databases are not necessarily experts at storing timezone related * data of dates. First, of not all the supported vendors support storing Timezone data, and some of * them only use the offset to calculate the timestamp in its default timezone (usually UTC) and persist * the value without the offset information. They even don't save the actual timezone names attached * to a DateTime instance (for example "Europe/Berlin" or "America/Montreal") but the current offset * of them related to UTC. That means, depending on daylight saving times or not, you may get different * offsets. * * This datatype makes only sense to use, if your application only needs to accept the timezone offset, * not the actual timezone that uses transitions. Otherwise your DateTime instance * attached with a timezone such as "Europe/Berlin" gets saved into the database with * the offset and re-created from persistence with only the offset, not the original timezone * attached. */ class DateTimeTzType extends Type implements PhpDateTimeMappingType { /** * {@inheritDoc} */ public function getName() { return Types::DATETIMETZ_MUTABLE; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getDateTimeTzTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6017', 'Passing an instance of %s is deprecated, use %s::%s() instead.', get_class($value), DateTimeTzImmutableType::class, __FUNCTION__, ); } if ($value instanceof DateTimeInterface) { return $value->format($platform->getDateTimeTzFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTime::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeInterface) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value instanceof DateTimeImmutable) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6017', 'Passing an instance of %s is deprecated, use %s::%s() instead.', get_class($value), DateTimeTzImmutableType::class, __FUNCTION__, ); } if ($value === null || $value instanceof DateTimeInterface) { return $value; } $dateTime = DateTime::createFromFormat($platform->getDateTimeTzFormatString(), $value); if ($dateTime !== false) { return $dateTime; } throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateTimeTzFormatString(), ); } } Types/TimeImmutableType.php 0000755 00000003762 00000000000 0011735 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; /** * Immutable type of {@see TimeType}. */ class TimeImmutableType extends TimeType { /** * {@inheritDoc} */ public function getName() { return Types::TIME_IMMUTABLE; } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { return $value->format($platform->getTimeFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeImmutable) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat('!' . $platform->getTimeFormatString(), $value); if ($dateTime !== false) { return $dateTime; } throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getTimeFormatString(), ); } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } Types/PhpIntegerMappingType.php 0000755 00000000243 00000000000 0012547 0 ustar 00 <?php namespace Doctrine\DBAL\Types; /** * Implementations should map a database type to a PHP integer. * * @internal */ interface PhpIntegerMappingType { } Types/DateTimeImmutableType.php 0000755 00000004252 00000000000 0012526 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use Exception; /** * Immutable type of {@see DateTimeType}. */ class DateTimeImmutableType extends DateTimeType { /** * {@inheritDoc} */ public function getName() { return Types::DATETIME_IMMUTABLE; } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { return $value->format($platform->getDateTimeFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeImmutable) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat($platform->getDateTimeFormatString(), $value); if ($dateTime !== false) { return $dateTime; } try { return new DateTimeImmutable($value); } catch (Exception $e) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateTimeFormatString(), $e, ); } } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } Types/GuidType.php 0000755 00000001671 00000000000 0010064 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; /** * Represents a GUID/UUID datatype (both are actually synonyms) in the database. */ class GuidType extends StringType { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getGuidTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getName() { return Types::GUID; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return ! $platform->hasNativeGuidType(); } } Types/VarDateTimeType.php 0000755 00000002063 00000000000 0011335 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; use Exception; /** * Variable DateTime Type using DateTime::__construct() instead of DateTime::createFromFormat(). * * This type has performance implications as it runs twice as long as the regular * {@see DateTimeType}, however in certain PostgreSQL configurations with * TIMESTAMP(n) columns where n > 0 it is necessary to use this type. */ class VarDateTimeType extends DateTimeType { /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeInterface) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTime) { return $value; } try { $dateTime = new DateTime($value); } catch (Exception $e) { throw ConversionException::conversionFailed($value, $this->getName(), $e); } return $dateTime; } } Types/ObjectType.php 0000755 00000003601 00000000000 0010375 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function is_resource; use function restore_error_handler; use function serialize; use function set_error_handler; use function stream_get_contents; use function unserialize; /** * Type that maps a PHP object to a clob SQL type. * * @deprecated Use {@link JsonType} instead. */ class ObjectType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param mixed $value * * @return string */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return serialize($value); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $value = is_resource($value) ? stream_get_contents($value) : $value; set_error_handler(function (int $code, string $message): bool { throw ConversionException::conversionFailedUnserialization($this->getName(), $message); }); try { return unserialize($value); } finally { restore_error_handler(); } } /** * {@inheritDoc} */ public function getName() { return Types::OBJECT; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } Types/Type.php 0000755 00000021163 00000000000 0007251 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Exception; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function array_map; use function get_class; /** * The base class for so-called Doctrine mapping types. * * A Type object is obtained by calling the static {@see getType()} method. */ abstract class Type { /** * The map of supported doctrine mapping types. */ private const BUILTIN_TYPES_MAP = [ Types::ARRAY => ArrayType::class, Types::ASCII_STRING => AsciiStringType::class, Types::BIGINT => BigIntType::class, Types::BINARY => BinaryType::class, Types::BLOB => BlobType::class, Types::BOOLEAN => BooleanType::class, Types::DATE_MUTABLE => DateType::class, Types::DATE_IMMUTABLE => DateImmutableType::class, Types::DATEINTERVAL => DateIntervalType::class, Types::DATETIME_MUTABLE => DateTimeType::class, Types::DATETIME_IMMUTABLE => DateTimeImmutableType::class, Types::DATETIMETZ_MUTABLE => DateTimeTzType::class, Types::DATETIMETZ_IMMUTABLE => DateTimeTzImmutableType::class, Types::DECIMAL => DecimalType::class, Types::FLOAT => FloatType::class, Types::GUID => GuidType::class, Types::INTEGER => IntegerType::class, Types::JSON => JsonType::class, Types::OBJECT => ObjectType::class, Types::SIMPLE_ARRAY => SimpleArrayType::class, Types::SMALLINT => SmallIntType::class, Types::STRING => StringType::class, Types::TEXT => TextType::class, Types::TIME_MUTABLE => TimeType::class, Types::TIME_IMMUTABLE => TimeImmutableType::class, ]; private static ?TypeRegistry $typeRegistry = null; /** @internal Do not instantiate directly - use {@see Type::addType()} method instead. */ final public function __construct() { } /** * Converts a value from its PHP representation to its database representation * of this type. * * @param mixed $value The value to convert. * @param AbstractPlatform $platform The currently used database platform. * * @return mixed The database representation of the value. * * @throws ConversionException */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return $value; } /** * Converts a value from its database representation to its PHP representation * of this type. * * @param mixed $value The value to convert. * @param AbstractPlatform $platform The currently used database platform. * * @return mixed The PHP representation of the value. * * @throws ConversionException */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value; } /** * Gets the SQL declaration snippet for a column of this type. * * @param mixed[] $column The column definition * @param AbstractPlatform $platform The currently used database platform. * * @return string */ abstract public function getSQLDeclaration(array $column, AbstractPlatform $platform); /** * Gets the name of this type. * * @deprecated this method will be removed in Doctrine DBAL 4.0, * use {@see TypeRegistry::lookupName()} instead. * * @return string */ abstract public function getName(); final public static function getTypeRegistry(): TypeRegistry { return self::$typeRegistry ??= self::createTypeRegistry(); } private static function createTypeRegistry(): TypeRegistry { $instances = []; foreach (self::BUILTIN_TYPES_MAP as $name => $class) { $instances[$name] = new $class(); } return new TypeRegistry($instances); } /** * Factory method to create type instances. * Type instances are implemented as flyweights. * * @param string $name The name of the type (as returned by getName()). * * @return Type * * @throws Exception */ public static function getType($name) { return self::getTypeRegistry()->get($name); } /** * Finds a name for the given type. * * @throws Exception */ public static function lookupName(self $type): string { return self::getTypeRegistry()->lookupName($type); } /** * Adds a custom type to the type map. * * @param string $name The name of the type. This should correspond to what getName() returns. * @param class-string<Type> $className The class name of the custom type. * * @return void * * @throws Exception */ public static function addType($name, $className) { self::getTypeRegistry()->register($name, new $className()); } /** * Checks if exists support for a type. * * @param string $name The name of the type. * * @return bool TRUE if type is supported; FALSE otherwise. */ public static function hasType($name) { return self::getTypeRegistry()->has($name); } /** * Overrides an already defined type to use a different implementation. * * @param string $name * @param class-string<Type> $className * * @return void * * @throws Exception */ public static function overrideType($name, $className) { self::getTypeRegistry()->override($name, new $className()); } /** * Gets the (preferred) binding type for values of this type that * can be used when binding parameters to prepared statements. * * This method should return one of the {@see ParameterType} constants. * * @return int */ public function getBindingType() { return ParameterType::STRING; } /** * Gets the types array map which holds all registered types and the corresponding * type class * * @return array<string, string> */ public static function getTypesMap() { return array_map( static function (Type $type): string { return get_class($type); }, self::getTypeRegistry()->getMap(), ); } /** * Does working with this column require SQL conversion functions? * * This is a metadata function that is required for example in the ORM. * Usage of {@see convertToDatabaseValueSQL} and * {@see convertToPHPValueSQL} works for any type and mostly * does nothing. This method can additionally be used for optimization purposes. * * @deprecated Consumers should call {@see convertToDatabaseValueSQL} and {@see convertToPHPValueSQL} * regardless of the type. * * @return bool */ public function canRequireSQLConversion() { return false; } /** * Modifies the SQL expression (identifier, parameter) to convert to a database value. * * @param string $sqlExpr * * @return string */ public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform) { return $sqlExpr; } /** * Modifies the SQL expression (identifier, parameter) to convert to a PHP value. * * @param string $sqlExpr * @param AbstractPlatform $platform * * @return string */ public function convertToPHPValueSQL($sqlExpr, $platform) { return $sqlExpr; } /** * Gets an array of database types that map to this Doctrine type. * * @return string[] */ public function getMappedDatabaseTypes(AbstractPlatform $platform) { return []; } /** * If this Doctrine Type maps to an already mapped database type, * reverse schema engineering can't tell them apart. You need to mark * one of those types as commented, which will have Doctrine use an SQL * comment to typehint the actual Doctrine Type. * * @deprecated * * @return bool */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return false; } } Types/StringType.php 0000755 00000000742 00000000000 0010440 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL VARCHAR to a PHP string. */ class StringType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getStringTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getName() { return Types::STRING; } } Types/SimpleArrayType.php 0000755 00000003450 00000000000 0011421 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function count; use function explode; use function implode; use function is_array; use function is_resource; use function stream_get_contents; /** * Array Type which can be used for simple values. * * Only use this type if you are sure that your values cannot contain a ",". */ class SimpleArrayType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param mixed $value * * @return string|null */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if (! is_array($value) || count($value) === 0) { return null; } return implode(',', $value); } /** * {@inheritDoc} * * @param mixed $value * * @return list<string> */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return []; } $value = is_resource($value) ? stream_get_contents($value) : $value; return explode(',', $value); } /** * {@inheritDoc} */ public function getName() { return Types::SIMPLE_ARRAY; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } Types/JsonType.php 0000755 00000004222 00000000000 0010100 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use JsonException; use function is_resource; use function json_decode; use function json_encode; use function stream_get_contents; use const JSON_PRESERVE_ZERO_FRACTION; use const JSON_THROW_ON_ERROR; /** * Type generating json objects values */ class JsonType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getJsonTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } try { return json_encode($value, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION); } catch (JsonException $e) { throw ConversionException::conversionFailedSerialization($value, 'json', $e->getMessage(), $e); } } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value === '') { return null; } if (is_resource($value)) { $value = stream_get_contents($value); } try { return json_decode($value, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { throw ConversionException::conversionFailed($value, $this->getName(), $e); } } /** * {@inheritDoc} */ public function getName() { return Types::JSON; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return ! $platform->hasNativeJsonType(); } } Types/DateImmutableType.php 0000755 00000004002 00000000000 0011700 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; /** * Immutable type of {@see DateType}. */ class DateImmutableType extends DateType { /** * {@inheritDoc} */ public function getName() { return Types::DATE_IMMUTABLE; } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { return $value->format($platform->getDateFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeImmutable) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat('!' . $platform->getDateFormatString(), $value); if ($dateTime === false) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateFormatString(), ); } return $dateTime; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } Types/DateIntervalType.php 0000755 00000004653 00000000000 0011561 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateInterval; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use Throwable; use function substr; /** * Type that maps interval string to a PHP DateInterval Object. */ class DateIntervalType extends Type { public const FORMAT = '%RP%YY%MM%DDT%HH%IM%SS'; /** * {@inheritDoc} */ public function getName() { return Types::DATEINTERVAL; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { $column['length'] = 255; return $platform->getStringTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } if ($value instanceof DateInterval) { return $value->format(self::FORMAT); } throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', DateInterval::class]); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateInterval) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateInterval) { return $value; } $negative = false; if (isset($value[0]) && ($value[0] === '+' || $value[0] === '-')) { $negative = $value[0] === '-'; $value = substr($value, 1); } try { $interval = new DateInterval($value); if ($negative) { $interval->invert = 1; } return $interval; } catch (Throwable $exception) { throw ConversionException::conversionFailedFormat($value, $this->getName(), self::FORMAT, $exception); } } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } Types/IntegerType.php 0000755 00000001673 00000000000 0010573 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL INT to a PHP integer. */ class IntegerType extends Type implements PhpIntegerMappingType { /** * {@inheritDoc} */ public function getName() { return Types::INTEGER; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : int) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (int) $value; } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::INTEGER; } } Types/PhpDateTimeMappingType.php 0000755 00000000267 00000000000 0012654 0 ustar 00 <?php namespace Doctrine\DBAL\Types; /** * Implementations should map a database type to a PHP DateTimeInterface instance. * * @internal */ interface PhpDateTimeMappingType { } Types/TypeRegistry.php 0000755 00000006276 00000000000 0011012 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Exception; use function spl_object_id; /** * The type registry is responsible for holding a map of all known DBAL types. * The types are stored using the flyweight pattern so that one type only exists as exactly one instance. */ final class TypeRegistry { /** @var array<string, Type> Map of type names and their corresponding flyweight objects. */ private array $instances; /** @var array<int, string> */ private array $instancesReverseIndex; /** @param array<string, Type> $instances */ public function __construct(array $instances = []) { $this->instances = []; $this->instancesReverseIndex = []; foreach ($instances as $name => $type) { $this->register($name, $type); } } /** * Finds a type by the given name. * * @throws Exception */ public function get(string $name): Type { $type = $this->instances[$name] ?? null; if ($type === null) { throw Exception::unknownColumnType($name); } return $type; } /** * Finds a name for the given type. * * @throws Exception */ public function lookupName(Type $type): string { $name = $this->findTypeName($type); if ($name === null) { throw Exception::typeNotRegistered($type); } return $name; } /** * Checks if there is a type of the given name. */ public function has(string $name): bool { return isset($this->instances[$name]); } /** * Registers a custom type to the type map. * * @throws Exception */ public function register(string $name, Type $type): void { if (isset($this->instances[$name])) { throw Exception::typeExists($name); } if ($this->findTypeName($type) !== null) { throw Exception::typeAlreadyRegistered($type); } $this->instances[$name] = $type; $this->instancesReverseIndex[spl_object_id($type)] = $name; } /** * Overrides an already defined type to use a different implementation. * * @throws Exception */ public function override(string $name, Type $type): void { $origType = $this->instances[$name] ?? null; if ($origType === null) { throw Exception::typeNotFound($name); } if (($this->findTypeName($type) ?? $name) !== $name) { throw Exception::typeAlreadyRegistered($type); } unset($this->instancesReverseIndex[spl_object_id($origType)]); $this->instances[$name] = $type; $this->instancesReverseIndex[spl_object_id($type)] = $name; } /** * Gets the map of all registered types and their corresponding type instances. * * @internal * * @return array<string, Type> */ public function getMap(): array { return $this->instances; } private function findTypeName(Type $type): ?string { return $this->instancesReverseIndex[spl_object_id($type)] ?? null; } } Types/ArrayType.php 0000755 00000004066 00000000000 0010253 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function is_resource; use function restore_error_handler; use function serialize; use function set_error_handler; use function stream_get_contents; use function unserialize; use const E_DEPRECATED; use const E_USER_DEPRECATED; /** * Type that maps a PHP array to a clob SQL type. * * @deprecated Use {@link JsonType} instead. */ class ArrayType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { // @todo 3.0 - $value === null check to save real NULL in database return serialize($value); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $value = is_resource($value) ? stream_get_contents($value) : $value; set_error_handler(function (int $code, string $message): bool { if ($code === E_DEPRECATED || $code === E_USER_DEPRECATED) { return false; } throw ConversionException::conversionFailedUnserialization($this->getName(), $message); }); try { return unserialize($value); } finally { restore_error_handler(); } } /** * {@inheritDoc} */ public function getName() { return Types::ARRAY; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } Configuration.php 0000755 00000016256 00000000000 0010042 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Logging\SQLLogger; use Doctrine\DBAL\Schema\SchemaManagerFactory; use Doctrine\Deprecations\Deprecation; use Psr\Cache\CacheItemPoolInterface; use function func_num_args; /** * Configuration container for the Doctrine DBAL. */ class Configuration { /** @var Middleware[] */ private array $middlewares = []; /** * The SQL logger in use. If null, SQL logging is disabled. * * @var SQLLogger|null */ protected $sqlLogger; /** * The cache driver implementation that is used for query result caching. */ private ?CacheItemPoolInterface $resultCache = null; /** * The cache driver implementation that is used for query result caching. * * @deprecated Use {@see $resultCache} instead. * * @var Cache|null */ protected $resultCacheImpl; /** * The callable to use to filter schema assets. * * @var callable|null */ protected $schemaAssetsFilter; /** * The default auto-commit mode for connections. * * @var bool */ protected $autoCommit = true; /** * Whether type comments should be disabled to provide the same DB schema than * will be obtained with DBAL 4.x. This is useful when relying only on the * platform-aware schema comparison (which does not need those type comments) * rather than the deprecated legacy tooling. */ private bool $disableTypeComments = false; private ?SchemaManagerFactory $schemaManagerFactory = null; public function __construct() { $this->schemaAssetsFilter = static function (): bool { return true; }; } /** * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. * * @deprecated Use {@see setMiddlewares()} and {@see \Doctrine\DBAL\Logging\Middleware} instead. */ public function setSQLLogger(?SQLLogger $logger = null): void { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4967', '%s is deprecated, use setMiddlewares() and Logging\\Middleware instead.', __METHOD__, ); $this->sqlLogger = $logger; } /** * Gets the SQL logger that is used. * * @deprecated */ public function getSQLLogger(): ?SQLLogger { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4967', '%s is deprecated.', __METHOD__, ); return $this->sqlLogger; } /** * Gets the cache driver implementation that is used for query result caching. */ public function getResultCache(): ?CacheItemPoolInterface { return $this->resultCache; } /** * Gets the cache driver implementation that is used for query result caching. * * @deprecated Use {@see getResultCache()} instead. */ public function getResultCacheImpl(): ?Cache { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4620', '%s is deprecated, call getResultCache() instead.', __METHOD__, ); return $this->resultCacheImpl; } /** * Sets the cache driver implementation that is used for query result caching. */ public function setResultCache(CacheItemPoolInterface $cache): void { $this->resultCacheImpl = DoctrineProvider::wrap($cache); $this->resultCache = $cache; } /** * Sets the cache driver implementation that is used for query result caching. * * @deprecated Use {@see setResultCache()} instead. */ public function setResultCacheImpl(Cache $cacheImpl): void { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4620', '%s is deprecated, call setResultCache() instead.', __METHOD__, ); $this->resultCacheImpl = $cacheImpl; $this->resultCache = CacheAdapter::wrap($cacheImpl); } /** * Sets the callable to use to filter schema assets. */ public function setSchemaAssetsFilter(?callable $callable = null): void { if (func_num_args() < 1) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5483', 'Not passing an argument to %s is deprecated.', __METHOD__, ); } elseif ($callable === null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5483', 'Using NULL as a schema asset filter is deprecated.' . ' Use a callable that always returns true instead.', ); } $this->schemaAssetsFilter = $callable; } /** * Returns the callable to use to filter schema assets. */ public function getSchemaAssetsFilter(): ?callable { return $this->schemaAssetsFilter; } /** * Sets the default auto-commit mode for connections. * * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either * the method commit or the method rollback. By default, new connections are in auto-commit mode. * * @see getAutoCommit * * @param bool $autoCommit True to enable auto-commit mode; false to disable it */ public function setAutoCommit(bool $autoCommit): void { $this->autoCommit = $autoCommit; } /** * Returns the default auto-commit mode for connections. * * @see setAutoCommit * * @return bool True if auto-commit mode is enabled by default for connections, false otherwise. */ public function getAutoCommit(): bool { return $this->autoCommit; } /** * @param Middleware[] $middlewares * * @return $this */ public function setMiddlewares(array $middlewares): self { $this->middlewares = $middlewares; return $this; } /** @return Middleware[] */ public function getMiddlewares(): array { return $this->middlewares; } public function getSchemaManagerFactory(): ?SchemaManagerFactory { return $this->schemaManagerFactory; } /** @return $this */ public function setSchemaManagerFactory(SchemaManagerFactory $schemaManagerFactory): self { $this->schemaManagerFactory = $schemaManagerFactory; return $this; } public function getDisableTypeComments(): bool { return $this->disableTypeComments; } /** @return $this */ public function setDisableTypeComments(bool $disableTypeComments): self { $this->disableTypeComments = $disableTypeComments; return $this; } } Platforms/SQLServerPlatform.php 0000755 00000167674 00000000000 0012550 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception\InvalidLockMode; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Platforms\SQLServer\SQL\Builder\SQLServerSelectSQLBuilder; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\SQLServerSchemaManager; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use function array_merge; use function array_unique; use function array_values; use function count; use function crc32; use function dechex; use function explode; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function is_array; use function is_bool; use function is_numeric; use function is_string; use function preg_match; use function preg_match_all; use function sprintf; use function str_ends_with; use function str_replace; use function str_starts_with; use function strpos; use function strtoupper; use function substr; use function substr_count; use const PREG_OFFSET_CAPTURE; /** * Provides the behavior, features and SQL dialect of the Microsoft SQL Server database platform * of the oldest supported version. */ class SQLServerPlatform extends AbstractPlatform { public function createSelectSQLBuilder(): SelectSQLBuilder { return new SQLServerSelectSQLBuilder($this); } /** * {@inheritDoc} */ public function getCurrentDateSQL() { return $this->getConvertExpression('date', 'GETDATE()'); } /** * {@inheritDoc} */ public function getCurrentTimeSQL() { return $this->getConvertExpression('time', 'GETDATE()'); } /** * Returns an expression that converts an expression of one data type to another. * * @param string $dataType The target native data type. Alias data types cannot be used. * @param string $expression The SQL expression to convert. */ private function getConvertExpression($dataType, $expression): string { return sprintf('CONVERT(%s, %s)', $dataType, $expression); } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { $factorClause = ''; if ($operator === '-') { $factorClause = '-1 * '; } return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')'; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return 'DATEDIFF(day, ' . $date2 . ',' . $date1 . ')'; } /** * {@inheritDoc} * * Microsoft SQL Server prefers "autoincrement" identity columns * since sequences can only be emulated with a table. * * @deprecated */ public function prefersIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/1519', 'SQLServerPlatform::prefersIdentityColumns() is deprecated.', ); return true; } /** * {@inheritDoc} * * Microsoft SQL Server supports this through AUTO_INCREMENT columns. */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} */ public function supportsReleaseSavepoints() { return false; } /** * {@inheritDoc} */ public function supportsSchemas() { return true; } /** * {@inheritDoc} * * @deprecated */ public function getDefaultSchemaName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return 'dbo'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsColumnCollation() { return true; } public function supportsSequences(): bool { return true; } public function getAlterSequenceSQL(Sequence $sequence): string { return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize(); } public function getCreateSequenceSQL(Sequence $sequence): string { return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . ' START WITH ' . $sequence->getInitialValue() . ' INCREMENT BY ' . $sequence->getAllocationSize() . ' MINVALUE ' . $sequence->getInitialValue(); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListSequencesSQL($database) { return 'SELECT seq.name, CAST( seq.increment AS VARCHAR(MAX) ) AS increment, -- CAST avoids driver error for sql_variant type CAST( seq.start_value AS VARCHAR(MAX) ) AS start_value -- CAST avoids driver error for sql_variant type FROM sys.sequences AS seq'; } /** * {@inheritDoc} */ public function getSequenceNextValSQL($sequence) { return 'SELECT NEXT VALUE FOR ' . $sequence; } /** * {@inheritDoc} * * @deprecated */ public function hasNativeGuidType() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } /** * {@inheritDoc} */ public function getDropForeignKeySQL($foreignKey, $table) { if (! $foreignKey instanceof ForeignKeyConstraint) { $foreignKey = new Identifier($foreignKey); } if (! $table instanceof Table) { $table = new Identifier($table); } $foreignKey = $foreignKey->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; } /** * {@inheritDoc} */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $index = $index->getQuotedName($this); } elseif (! is_string($index)) { throw new InvalidArgumentException( __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.', ); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as an Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } elseif (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', ); } return 'DROP INDEX ' . $index . ' ON ' . $table; } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $defaultConstraintsSql = []; $commentsSql = []; $tableComment = $options['comment'] ?? null; if ($tableComment !== null) { $commentsSql[] = $this->getCommentOnTableSQL($name, $tableComment); } // @todo does other code breaks because of this? // force primary keys to be not null foreach ($columns as &$column) { if (! empty($column['primary'])) { $column['notnull'] = true; } // Build default constraints SQL statements. if (isset($column['default'])) { $defaultConstraintsSql[] = 'ALTER TABLE ' . $name . ' ADD' . $this->getDefaultConstraintDeclarationSQL($name, $column); } if (empty($column['comment']) && ! is_numeric($column['comment'])) { continue; } $commentsSql[] = $this->getCreateColumnCommentSQL($name, $column['name'], $column['comment']); } $columnListSql = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $constraintName => $definition) { $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition); } } if (isset($options['primary']) && ! empty($options['primary'])) { $flags = ''; if (isset($options['primary_index']) && $options['primary_index']->hasFlag('nonclustered')) { $flags = ' NONCLUSTERED'; } $columnListSql .= ', PRIMARY KEY' . $flags . ' (' . implode(', ', array_unique(array_values($options['primary']))) . ')'; } $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; $check = $this->getCheckDeclarationSQL($columns); if (! empty($check)) { $query .= ', ' . $check; } $query .= ')'; $sql = [$query]; if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index) { $sql[] = $this->getCreateIndexSQL($index, $name); } } if (isset($options['foreignKeys'])) { foreach ($options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } return array_merge($sql, $commentsSql, $defaultConstraintsSql); } /** * {@inheritDoc} */ public function getCreatePrimaryKeySQL(Index $index, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $identifier = $table->getQuotedName($this); } else { $identifier = $table; } $sql = 'ALTER TABLE ' . $identifier . ' ADD PRIMARY KEY'; if ($index->hasFlag('nonclustered')) { $sql .= ' NONCLUSTERED'; } return $sql . ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; } private function unquoteSingleIdentifier(string $possiblyQuotedName): string { return str_starts_with($possiblyQuotedName, '[') && str_ends_with($possiblyQuotedName, ']') ? substr($possiblyQuotedName, 1, -1) : $possiblyQuotedName; } /** * Returns the SQL statement for creating a column comment. * * SQL Server does not support native column comments, * therefore the extended properties functionality is used * as a workaround to store them. * The property name used to store column comments is "MS_Description" * which provides compatibility with SQL Server Management Studio, * as column comments are stored in the same property there when * specifying a column's "Description" attribute. * * @param string $tableName The quoted table name to which the column belongs. * @param string $columnName The quoted column name to create the comment for. * @param string|null $comment The column's comment. * * @return string */ protected function getCreateColumnCommentSQL($tableName, $columnName, $comment) { if (strpos($tableName, '.') !== false) { [$schemaName, $tableName] = explode('.', $tableName); } else { $schemaName = 'dbo'; } return $this->getAddExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), 'TABLE', $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), 'COLUMN', $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), ); } /** * Returns the SQL snippet for declaring a default constraint. * * @internal The method should be only used from within the SQLServerPlatform class hierarchy. * * @param string $table Name of the table to return the default constraint declaration for. * @param mixed[] $column Column definition. * * @return string * * @throws InvalidArgumentException */ public function getDefaultConstraintDeclarationSQL($table, array $column) { if (! isset($column['default'])) { throw new InvalidArgumentException("Incomplete column definition. 'default' required."); } $columnName = new Identifier($column['name']); return ' CONSTRAINT ' . $this->generateDefaultConstraintName($table, $column['name']) . $this->getDefaultValueDeclarationSQL($column) . ' FOR ' . $columnName->getQuotedName($this); } /** * {@inheritDoc} */ public function getCreateIndexSQL(Index $index, $table) { $constraint = parent::getCreateIndexSQL($index, $table); if ($index->isUnique() && ! $index->isPrimary()) { $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index); } return $constraint; } /** * {@inheritDoc} */ protected function getCreateIndexSQLFlags(Index $index) { $type = ''; if ($index->isUnique()) { $type .= 'UNIQUE '; } if ($index->hasFlag('clustered')) { $type .= 'CLUSTERED '; } elseif ($index->hasFlag('nonclustered')) { $type .= 'NONCLUSTERED '; } return $type; } /** * Extend unique key constraint with required filters * * @param string $sql */ private function _appendUniqueConstraintDefinition($sql, Index $index): string { $fields = []; foreach ($index->getQuotedColumns($this) as $field) { $fields[] = $field . ' IS NOT NULL'; } return $sql . ' WHERE ' . implode(' AND ', $fields); } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $queryParts = []; $sql = []; $columnSql = []; $commentsSql = []; $table = $diff->getOldTable() ?? $diff->getName($this); $tableName = $table->getName(); foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columnProperties = $column->toArray(); $addColumnSql = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnProperties); if (isset($columnProperties['default'])) { $addColumnSql .= ' CONSTRAINT ' . $this->generateDefaultConstraintName( $tableName, $column->getQuotedName($this), ) . $this->getDefaultValueDeclarationSQL($columnProperties); } $queryParts[] = $addColumnSql; $comment = $this->getColumnComment($column); if (empty($comment) && ! is_numeric($comment)) { continue; } $commentsSql[] = $this->getCreateColumnCommentSQL( $tableName, $column->getQuotedName($this), $comment, ); } foreach ($diff->getDroppedColumns() as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); } foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $newColumn = $columnDiff->getNewColumn(); $newComment = $this->getColumnComment($newColumn); $hasNewComment = ! empty($newComment) || is_numeric($newComment); $oldColumn = $columnDiff->getOldColumn(); if ($oldColumn instanceof Column) { $oldComment = $this->getColumnComment($oldColumn); $hasOldComment = ! empty($oldComment) || is_numeric($oldComment); if ($hasOldComment && $hasNewComment && $oldComment !== $newComment) { $commentsSql[] = $this->getAlterColumnCommentSQL( $tableName, $newColumn->getQuotedName($this), $newComment, ); } elseif ($hasOldComment && ! $hasNewComment) { $commentsSql[] = $this->getDropColumnCommentSQL( $tableName, $newColumn->getQuotedName($this), ); } elseif (! $hasOldComment && $hasNewComment) { $commentsSql[] = $this->getCreateColumnCommentSQL( $tableName, $newColumn->getQuotedName($this), $newComment, ); } } // Do not add query part if only comment has changed. if ($columnDiff->hasCommentChanged() && count($columnDiff->changedProperties) === 1) { continue; } $requireDropDefaultConstraint = $this->alterColumnRequiresDropDefaultConstraint($columnDiff); if ($requireDropDefaultConstraint) { $oldColumn = $columnDiff->getOldColumn(); if ($oldColumn !== null) { $oldColumnName = $oldColumn->getName(); } else { $oldColumnName = $columnDiff->oldColumnName; } $queryParts[] = $this->getAlterTableDropDefaultConstraintClause($tableName, $oldColumnName); } $columnProperties = $newColumn->toArray(); $queryParts[] = 'ALTER COLUMN ' . $this->getColumnDeclarationSQL($newColumn->getQuotedName($this), $columnProperties); if ( ! isset($columnProperties['default']) || (! $requireDropDefaultConstraint && ! $columnDiff->hasDefaultChanged()) ) { continue; } $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($tableName, $newColumn); } $tableNameSQL = $table->getQuotedName($this); foreach ($diff->getRenamedColumns() as $oldColumnName => $newColumn) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $newColumn, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $sql[] = "sp_rename '" . $tableNameSQL . '.' . $oldColumnName->getQuotedName($this) . "', '" . $newColumn->getQuotedName($this) . "', 'COLUMN'"; // Recreate default constraint with new column name if necessary (for future reference). if ($newColumn->getDefault() === null) { continue; } $queryParts[] = $this->getAlterTableDropDefaultConstraintClause( $tableName, $oldColumnName->getQuotedName($this), ); $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($tableName, $newColumn); } $tableSql = []; if ($this->onSchemaAlterTable($diff, $tableSql)) { return array_merge($tableSql, $columnSql); } foreach ($queryParts as $query) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } $sql = array_merge($sql, $commentsSql); $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $sql = array_merge($sql, $this->getRenameTableSQL($tableName, $newName->getName())); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff), ); return array_merge($sql, $tableSql, $columnSql); } /** * {@inheritDoc} */ public function getRenameTableSQL(string $oldName, string $newName): array { return [ sprintf('sp_rename %s, %s', $this->quoteStringLiteral($oldName), $this->quoteStringLiteral($newName)), /* Rename table's default constraints names * to match the new table name. * This is necessary to ensure that the default * constraints can be referenced in future table * alterations as the table name is encoded in * default constraints' names. */ sprintf( <<<'SQL' DECLARE @sql NVARCHAR(MAX) = N''; SELECT @sql += N'EXEC sp_rename N''' + dc.name + ''', N''' + REPLACE(dc.name, '%s', '%s') + ''', ''OBJECT'';' FROM sys.default_constraints dc JOIN sys.tables tbl ON dc.parent_object_id = tbl.object_id WHERE tbl.name = %s; EXEC sp_executesql @sql SQL, $this->generateIdentifierName($oldName), $this->generateIdentifierName($newName), $this->quoteStringLiteral($newName), ), ]; } /** * Returns the SQL clause for adding a default constraint in an ALTER TABLE statement. * * @param string $tableName The name of the table to generate the clause for. * @param Column $column The column to generate the clause for. */ private function getAlterTableAddDefaultConstraintClause($tableName, Column $column): string { $columnDef = $column->toArray(); $columnDef['name'] = $column->getQuotedName($this); return 'ADD' . $this->getDefaultConstraintDeclarationSQL($tableName, $columnDef); } /** * Returns the SQL clause for dropping an existing default constraint in an ALTER TABLE statement. * * @param string $tableName The name of the table to generate the clause for. * @param string $columnName The name of the column to generate the clause for. */ private function getAlterTableDropDefaultConstraintClause($tableName, $columnName): string { return 'DROP CONSTRAINT ' . $this->generateDefaultConstraintName($tableName, $columnName); } /** * Checks whether a column alteration requires dropping its default constraint first. * * Different to other database vendors SQL Server implements column default values * as constraints and therefore changes in a column's default value as well as changes * in a column's type require dropping the default constraint first before being to * alter the particular column to the new definition. */ private function alterColumnRequiresDropDefaultConstraint(ColumnDiff $columnDiff): bool { $oldColumn = $columnDiff->getOldColumn(); // We can only decide whether to drop an existing default constraint // if we know the original default value. if (! $oldColumn instanceof Column) { return false; } // We only need to drop an existing default constraint if we know the // column was defined with a default value before. if ($oldColumn->getDefault() === null) { return false; } // We need to drop an existing default constraint if the column was // defined with a default value before and it has changed. if ($columnDiff->hasDefaultChanged()) { return true; } // We need to drop an existing default constraint if the column was // defined with a default value before and the native column type has changed. return $columnDiff->hasTypeChanged() || $columnDiff->hasFixedChanged(); } /** * Returns the SQL statement for altering a column comment. * * SQL Server does not support native column comments, * therefore the extended properties functionality is used * as a workaround to store them. * The property name used to store column comments is "MS_Description" * which provides compatibility with SQL Server Management Studio, * as column comments are stored in the same property there when * specifying a column's "Description" attribute. * * @param string $tableName The quoted table name to which the column belongs. * @param string $columnName The quoted column name to alter the comment for. * @param string|null $comment The column's comment. * * @return string */ protected function getAlterColumnCommentSQL($tableName, $columnName, $comment) { if (strpos($tableName, '.') !== false) { [$schemaName, $tableName] = explode('.', $tableName); } else { $schemaName = 'dbo'; } return $this->getUpdateExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), 'TABLE', $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), 'COLUMN', $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), ); } /** * Returns the SQL statement for dropping a column comment. * * SQL Server does not support native column comments, * therefore the extended properties functionality is used * as a workaround to store them. * The property name used to store column comments is "MS_Description" * which provides compatibility with SQL Server Management Studio, * as column comments are stored in the same property there when * specifying a column's "Description" attribute. * * @param string $tableName The quoted table name to which the column belongs. * @param string $columnName The quoted column name to drop the comment for. * * @return string */ protected function getDropColumnCommentSQL($tableName, $columnName) { if (strpos($tableName, '.') !== false) { [$schemaName, $tableName] = explode('.', $tableName); } else { $schemaName = 'dbo'; } return $this->getDropExtendedPropertySQL( 'MS_Description', 'SCHEMA', $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), 'TABLE', $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), 'COLUMN', $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), ); } /** * {@inheritDoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { return [sprintf( "EXEC sp_rename N'%s.%s', N'%s', N'INDEX'", $tableName, $oldIndexName, $index->getQuotedName($this), ), ]; } /** * Returns the SQL statement for adding an extended property to a database object. * * @internal The method should be only used from within the SQLServerPlatform class hierarchy. * * @link http://msdn.microsoft.com/en-us/library/ms180047%28v=sql.90%29.aspx * * @param string $name The name of the property to add. * @param string|null $value The value of the property to add. * @param string|null $level0Type The type of the object at level 0 the property belongs to. * @param string|null $level0Name The name of the object at level 0 the property belongs to. * @param string|null $level1Type The type of the object at level 1 the property belongs to. * @param string|null $level1Name The name of the object at level 1 the property belongs to. * @param string|null $level2Type The type of the object at level 2 the property belongs to. * @param string|null $level2Name The name of the object at level 2 the property belongs to. * * @return string */ public function getAddExtendedPropertySQL( $name, $value = null, $level0Type = null, $level0Name = null, $level1Type = null, $level1Name = null, $level2Type = null, $level2Name = null ) { return 'EXEC sp_addextendedproperty ' . 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' . 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . ($level2Type !== null || $level2Name !== null ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name : '' ); } /** * Returns the SQL statement for dropping an extended property from a database object. * * @internal The method should be only used from within the SQLServerPlatform class hierarchy. * * @link http://technet.microsoft.com/en-gb/library/ms178595%28v=sql.90%29.aspx * * @param string $name The name of the property to drop. * @param string|null $level0Type The type of the object at level 0 the property belongs to. * @param string|null $level0Name The name of the object at level 0 the property belongs to. * @param string|null $level1Type The type of the object at level 1 the property belongs to. * @param string|null $level1Name The name of the object at level 1 the property belongs to. * @param string|null $level2Type The type of the object at level 2 the property belongs to. * @param string|null $level2Name The name of the object at level 2 the property belongs to. * * @return string */ public function getDropExtendedPropertySQL( $name, $level0Type = null, $level0Name = null, $level1Type = null, $level1Name = null, $level2Type = null, $level2Name = null ) { return 'EXEC sp_dropextendedproperty ' . 'N' . $this->quoteStringLiteral($name) . ', ' . 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . ($level2Type !== null || $level2Name !== null ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name : '' ); } /** * Returns the SQL statement for updating an extended property of a database object. * * @internal The method should be only used from within the SQLServerPlatform class hierarchy. * * @link http://msdn.microsoft.com/en-us/library/ms186885%28v=sql.90%29.aspx * * @param string $name The name of the property to update. * @param string|null $value The value of the property to update. * @param string|null $level0Type The type of the object at level 0 the property belongs to. * @param string|null $level0Name The name of the object at level 0 the property belongs to. * @param string|null $level1Type The type of the object at level 1 the property belongs to. * @param string|null $level1Name The name of the object at level 1 the property belongs to. * @param string|null $level2Type The type of the object at level 2 the property belongs to. * @param string|null $level2Name The name of the object at level 2 the property belongs to. * * @return string */ public function getUpdateExtendedPropertySQL( $name, $value = null, $level0Type = null, $level0Name = null, $level1Type = null, $level1Name = null, $level2Type = null, $level2Name = null ) { return 'EXEC sp_updateextendedproperty ' . 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' . 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . ($level2Type !== null || $level2Name !== null ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name : '' ); } /** * {@inheritDoc} */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { // "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams // Category 2 must be ignored as it is "MS SQL Server 'pseudo-system' object[s]" for replication return 'SELECT name, SCHEMA_NAME (uid) AS schema_name FROM sysobjects' . " WHERE type = 'U' AND name != 'sysdiagrams' AND category != 2 ORDER BY name"; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { return "SELECT col.name, type.name AS type, col.max_length AS length, ~col.is_nullable AS notnull, def.definition AS [default], col.scale, col.precision, col.is_identity AS autoincrement, col.collation_name AS collation, CAST(prop.value AS NVARCHAR(MAX)) AS comment -- CAST avoids driver error for sql_variant type FROM sys.columns AS col JOIN sys.types AS type ON col.user_type_id = type.user_type_id JOIN sys.objects AS obj ON col.object_id = obj.object_id JOIN sys.schemas AS scm ON obj.schema_id = scm.schema_id LEFT JOIN sys.default_constraints def ON col.default_object_id = def.object_id AND col.object_id = def.parent_object_id LEFT JOIN sys.extended_properties AS prop ON obj.object_id = prop.major_id AND col.column_id = prop.minor_id AND prop.name = 'MS_Description' WHERE obj.type = 'U' AND " . $this->getTableWhereClause($table, 'scm.name', 'obj.name'); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { return 'SELECT f.name AS ForeignKey, SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, OBJECT_NAME (f.parent_object_id) AS TableName, COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, f.delete_referential_action_desc, f.update_referential_action_desc FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id ON f.OBJECT_ID = fc.constraint_object_id WHERE ' . $this->getTableWhereClause($table, 'SCHEMA_NAME (f.schema_id)', 'OBJECT_NAME (f.parent_object_id)') . ' ORDER BY fc.constraint_column_id'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) { return "SELECT idx.name AS key_name, col.name AS column_name, ~idx.is_unique AS non_unique, idx.is_primary_key AS [primary], CASE idx.type WHEN '1' THEN 'clustered' WHEN '2' THEN 'nonclustered' ELSE NULL END AS flags FROM sys.tables AS tbl JOIN sys.schemas AS scm ON tbl.schema_id = scm.schema_id JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id WHERE " . $this->getTableWhereClause($table, 'scm.name', 'tbl.name') . ' ORDER BY idx.index_id ASC, idxcol.key_ordinal ASC'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return "SELECT name, definition FROM sysobjects INNER JOIN sys.sql_modules ON sysobjects.id = sys.sql_modules.object_id WHERE type = 'V' ORDER BY name"; } /** * Returns the where clause to filter schema and table name in a query. * * @param string $table The full qualified name of the table. * @param string $schemaColumn The name of the column to compare the schema to in the where clause. * @param string $tableColumn The name of the column to compare the table to in the where clause. */ private function getTableWhereClause($table, $schemaColumn, $tableColumn): string { if (strpos($table, '.') !== false) { [$schema, $table] = explode('.', $table); $schema = $this->quoteStringLiteral($schema); $table = $this->quoteStringLiteral($table); } else { $schema = 'SCHEMA_NAME()'; $table = $this->quoteStringLiteral($table); } return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'CHARINDEX(' . $substr . ', ' . $str . ')'; } return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')'; } /** * {@inheritDoc} */ public function getModExpression($expression1, $expression2) { return $expression1 . ' % ' . $expression2; } /** * {@inheritDoc} */ public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) { if ($char === false) { switch ($mode) { case TrimMode::LEADING: $trimFn = 'LTRIM'; break; case TrimMode::TRAILING: $trimFn = 'RTRIM'; break; default: return 'LTRIM(RTRIM(' . $str . '))'; } return $trimFn . '(' . $str . ')'; } $pattern = "'%[^' + " . $char . " + ']%'"; if ($mode === TrimMode::LEADING) { return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)'; } if ($mode === TrimMode::TRAILING) { return 'reverse(stuff(reverse(' . $str . '), 1, ' . 'patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))'; } return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)), 1, ' . 'patindex(' . $pattern . ', reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null))) - 1, null))'; } /** * {@inheritDoc} */ public function getConcatExpression() { return sprintf('CONCAT(%s)', implode(', ', func_get_args())); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListDatabasesSQL() { return 'SELECT * FROM sys.databases'; } /** * {@inheritDoc} * * @deprecated Use {@see SQLServerSchemaManager::listSchemaNames()} instead. */ public function getListNamespacesSQL() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'SQLServerPlatform::getListNamespacesSQL() is deprecated,' . ' use SQLServerSchemaManager::listSchemaNames() instead.', ); return "SELECT name FROM sys.schemas WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys')"; } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length !== null) { return 'SUBSTRING(' . $string . ', ' . $start . ', ' . $length . ')'; } return 'SUBSTRING(' . $string . ', ' . $start . ', LEN(' . $string . ') - ' . $start . ' + 1)'; } /** * {@inheritDoc} */ public function getLengthExpression($column) { return 'LEN(' . $column . ')'; } public function getCurrentDatabaseExpression(): string { return 'DB_NAME()'; } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getGuidTypeDeclarationSQL(array $column) { return 'UNIQUEIDENTIFIER'; } /** * {@inheritDoc} */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return 'DATETIMEOFFSET(6)'; } /** * {@inheritDoc} */ public function getAsciiStringTypeDeclarationSQL(array $column): string { $length = $column['length'] ?? null; if (empty($column['fixed'])) { return sprintf('VARCHAR(%d)', $length ?? 255); } return sprintf('CHAR(%d)', $length ?? 255); } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default string column length on SQL Server is deprecated' . ', specify the length explicitly.', ); } return $fixed ? 'NCHAR(' . ($length > 0 ? $length : 255) . ')' : 'NVARCHAR(' . ($length > 0 ? $length : 255) . ')'; } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length on SQL Server is deprecated' . ', specify the length explicitly.', ); } return $fixed ? 'BINARY(' . ($length > 0 ? $length : 255) . ')' : 'VARBINARY(' . ($length > 0 ? $length : 255) . ')'; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'SQLServerPlatform::getBinaryMaxLength() is deprecated.', ); return 8000; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'VARCHAR(MAX)'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { return ! empty($column['autoincrement']) ? ' IDENTITY' : ''; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { // 3 - microseconds precision length // http://msdn.microsoft.com/en-us/library/ms187819.aspx return 'DATETIME2(6)'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME(0)'; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'BIT'; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit === null && $offset <= 0) { return $query; } if ($this->shouldAddOrderBy($query)) { if (preg_match('/^SELECT\s+DISTINCT/im', $query) > 0) { // SQL Server won't let us order by a non-selected column in a DISTINCT query, // so we have to do this madness. This says, order by the first column in the // result. SQL Server's docs say that a nonordered query's result order is non- // deterministic anyway, so this won't do anything that a bunch of update and // deletes to the table wouldn't do anyway. $query .= ' ORDER BY 1'; } else { // In another DBMS, we could do ORDER BY 0, but SQL Server gets angry if you // use constant expressions in the order by list. $query .= ' ORDER BY (SELECT 0)'; } } // This looks somewhat like MYSQL, but limit/offset are in inverse positions // Supposedly SQL:2008 core standard. // Per TSQL spec, FETCH NEXT n ROWS ONLY is not valid without OFFSET n ROWS. $query .= sprintf(' OFFSET %d ROWS', $offset); if ($limit !== null) { $query .= sprintf(' FETCH NEXT %d ROWS ONLY', $limit); } return $query; } /** * {@inheritDoc} */ public function convertBooleans($item) { if (is_array($item)) { foreach ($item as $key => $value) { if (! is_bool($value) && ! is_numeric($value)) { continue; } $item[$key] = (int) (bool) $value; } } elseif (is_bool($item) || is_numeric($item)) { $item = (int) (bool) $item; } return $item; } /** * {@inheritDoc} */ public function getCreateTemporaryTableSnippetSQL() { return 'CREATE TABLE'; } /** * {@inheritDoc} */ public function getTemporaryTableName($tableName) { return '#' . $tableName; } /** * {@inheritDoc} */ public function getDateTimeFormatString() { return 'Y-m-d H:i:s.u'; } /** * {@inheritDoc} */ public function getDateFormatString() { return 'Y-m-d'; } /** * {@inheritDoc} */ public function getTimeFormatString() { return 'H:i:s'; } /** * {@inheritDoc} */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:s.u P'; } /** * {@inheritDoc} */ public function getName() { return 'mssql'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'bigint' => Types::BIGINT, 'binary' => Types::BINARY, 'bit' => Types::BOOLEAN, 'blob' => Types::BLOB, 'char' => Types::STRING, 'date' => Types::DATE_MUTABLE, 'datetime' => Types::DATETIME_MUTABLE, 'datetime2' => Types::DATETIME_MUTABLE, 'datetimeoffset' => Types::DATETIMETZ_MUTABLE, 'decimal' => Types::DECIMAL, 'double' => Types::FLOAT, 'double precision' => Types::FLOAT, 'float' => Types::FLOAT, 'image' => Types::BLOB, 'int' => Types::INTEGER, 'money' => Types::INTEGER, 'nchar' => Types::STRING, 'ntext' => Types::TEXT, 'numeric' => Types::DECIMAL, 'nvarchar' => Types::STRING, 'real' => Types::FLOAT, 'smalldatetime' => Types::DATETIME_MUTABLE, 'smallint' => Types::SMALLINT, 'smallmoney' => Types::INTEGER, 'sysname' => Types::STRING, 'text' => Types::TEXT, 'time' => Types::TIME_MUTABLE, 'tinyint' => Types::SMALLINT, 'uniqueidentifier' => Types::GUID, 'varbinary' => Types::BINARY, 'varchar' => Types::STRING, 'xml' => Types::TEXT, ]; } /** * {@inheritDoc} */ public function createSavePoint($savepoint) { return 'SAVE TRANSACTION ' . $savepoint; } /** * {@inheritDoc} */ public function releaseSavePoint($savepoint) { return ''; } /** * {@inheritDoc} */ public function rollbackSavePoint($savepoint) { return 'ROLLBACK TRANSACTION ' . $savepoint; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getForeignKeyReferentialActionSQL($action) { // RESTRICT is not supported, therefore falling back to NO ACTION. if (strtoupper($action) === 'RESTRICT') { return 'NO ACTION'; } return parent::getForeignKeyReferentialActionSQL($action); } public function appendLockHint(string $fromClause, int $lockMode): string { switch ($lockMode) { case LockMode::NONE: case LockMode::OPTIMISTIC: return $fromClause; case LockMode::PESSIMISTIC_READ: return $fromClause . ' WITH (HOLDLOCK, ROWLOCK)'; case LockMode::PESSIMISTIC_WRITE: return $fromClause . ' WITH (UPDLOCK, ROWLOCK)'; default: throw InvalidLockMode::fromLockMode($lockMode); } } /** * {@inheritDoc} * * @deprecated This API is not portable. */ public function getForUpdateSQL() { return ' '; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'SQLServerPlatform::getReservedKeywordsClass() is deprecated,' . ' use SQLServerPlatform::createReservedKeywordsList() instead.', ); return Keywords\SQLServer2012Keywords::class; } /** * {@inheritDoc} */ public function quoteSingleIdentifier($str) { return '[' . str_replace(']', ']]', $str) . ']'; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'VARBINARY(MAX)'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getColumnDeclarationSQL($name, array $column) { if (isset($column['columnDefinition'])) { $columnDef = $this->getCustomTypeDeclarationSQL($column); } else { $collation = ! empty($column['collation']) ? ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : ''; $notnull = ! empty($column['notnull']) ? ' NOT NULL' : ''; if (! empty($column['unique'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "unique" column property is deprecated. Use unique constraints instead.', ); $unique = ' ' . $this->getUniqueFieldDeclarationSQL(); } else { $unique = ''; } if (! empty($column['check'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "check" column property is deprecated.', ); $check = ' ' . $column['check']; } else { $check = ''; } $typeDecl = $column['type']->getSQLDeclaration($column, $this); $columnDef = $typeDecl . $collation . $notnull . $unique . $check; } return $name . ' ' . $columnDef; } /** * {@inheritDoc} * * SQL Server does not support quoting collation identifiers. */ public function getColumnCollationDeclarationSQL($collation) { return 'COLLATE ' . $collation; } public function columnsEqual(Column $column1, Column $column2): bool { if (! parent::columnsEqual($column1, $column2)) { return false; } return $this->getDefaultValueDeclarationSQL($column1->toArray()) === $this->getDefaultValueDeclarationSQL($column2->toArray()); } protected function getLikeWildcardCharacters(): string { return parent::getLikeWildcardCharacters() . '[]^'; } /** * Returns a unique default constraint name for a table and column. * * @param string $table Name of the table to generate the unique default constraint name for. * @param string $column Name of the column in the table to generate the unique default constraint name for. */ private function generateDefaultConstraintName($table, $column): string { return 'DF_' . $this->generateIdentifierName($table) . '_' . $this->generateIdentifierName($column); } /** * Returns a hash value for a given identifier. * * @param string $identifier Identifier to generate a hash value for. */ private function generateIdentifierName($identifier): string { // Always generate name for unquoted identifiers to ensure consistency. $identifier = new Identifier($identifier); return strtoupper(dechex(crc32($identifier->getName()))); } protected function getCommentOnTableSQL(string $tableName, ?string $comment): string { return $this->getAddExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', $this->quoteStringLiteral('dbo'), 'TABLE', $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), ); } /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ public function getListTableMetadataSQL(string $table): string { return sprintf( <<<'SQL' SELECT p.value AS [table_comment] FROM sys.tables AS tbl INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1 WHERE (tbl.name=N%s and SCHEMA_NAME(tbl.schema_id)=N'dbo' and p.name=N'MS_Description') SQL , $this->quoteStringLiteral($table), ); } /** @param string $query */ private function shouldAddOrderBy($query): bool { // Find the position of the last instance of ORDER BY and ensure it is not within a parenthetical statement // but can be in a newline $matches = []; $matchesCount = preg_match_all('/[\\s]+order\\s+by\\s/im', $query, $matches, PREG_OFFSET_CAPTURE); if ($matchesCount === 0) { return true; } // ORDER BY instance may be in a subquery after ORDER BY // e.g. SELECT col1 FROM test ORDER BY (SELECT col2 from test ORDER BY col2) // if in the searched query ORDER BY clause was found where // number of open parentheses after the occurrence of the clause is equal to // number of closed brackets after the occurrence of the clause, // it means that ORDER BY is included in the query being checked while ($matchesCount > 0) { $orderByPos = $matches[0][--$matchesCount][1]; $openBracketsCount = substr_count($query, '(', $orderByPos); $closedBracketsCount = substr_count($query, ')', $orderByPos); if ($openBracketsCount === $closedBracketsCount) { return false; } } return true; } public function createSchemaManager(Connection $connection): SQLServerSchemaManager { return new SQLServerSchemaManager($connection, $this); } } Platforms/AbstractPlatform.php 0000755 00000422137 00000000000 0012451 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\Common\EventManager; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Event\SchemaAlterTableAddColumnEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableChangeColumnEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableRenameColumnEventArgs; use Doctrine\DBAL\Event\SchemaCreateTableColumnEventArgs; use Doctrine\DBAL\Event\SchemaCreateTableEventArgs; use Doctrine\DBAL\Event\SchemaDropTableEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\InvalidLockMode; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Platforms\Keywords\KeywordList; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\Constraint; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\SchemaDiff; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\Schema\UniqueConstraint; use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\SQL\Parser; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use UnexpectedValueException; use function addcslashes; use function array_map; use function array_merge; use function array_unique; use function array_values; use function assert; use function count; use function explode; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function in_array; use function is_array; use function is_bool; use function is_int; use function is_string; use function preg_quote; use function preg_replace; use function sprintf; use function str_replace; use function strlen; use function strpos; use function strtolower; use function strtoupper; /** * Base class for all DatabasePlatforms. The DatabasePlatforms are the central * point of abstraction of platform-specific behaviors, features and SQL dialects. * They are a passive source of information. * * @todo Remove any unnecessary methods. */ abstract class AbstractPlatform { public const CREATE_INDEXES = 1; public const CREATE_FOREIGNKEYS = 2; /** @var string[]|null */ protected $doctrineTypeMapping; /** * Contains a list of all columns that should generate parseable column comments for type-detection * in reverse engineering scenarios. * * @deprecated This property is deprecated and will be removed in Doctrine DBAL 4.0. * * @var string[]|null */ protected $doctrineTypeComments; /** * @deprecated * * @var EventManager|null */ protected $_eventManager; /** * Holds the KeywordList instance for the current platform. * * @var KeywordList|null */ protected $_keywords; private bool $disableTypeComments = false; /** @internal */ final public function setDisableTypeComments(bool $value): void { $this->disableTypeComments = $value; } /** * Sets the EventManager used by the Platform. * * @deprecated * * @return void */ public function setEventManager(EventManager $eventManager) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', '%s is deprecated.', __METHOD__, ); $this->_eventManager = $eventManager; } /** * Gets the EventManager used by the Platform. * * @deprecated * * @return EventManager|null */ public function getEventManager() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', '%s is deprecated.', __METHOD__, ); return $this->_eventManager; } /** * Returns the SQL snippet that declares a boolean column. * * @param mixed[] $column * * @return string */ abstract public function getBooleanTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares a 4 byte integer column. * * @param mixed[] $column * * @return string */ abstract public function getIntegerTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares an 8 byte integer column. * * @param mixed[] $column * * @return string */ abstract public function getBigIntTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares a 2 byte integer column. * * @param mixed[] $column * * @return string */ abstract public function getSmallIntTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares common properties of an integer column. * * @param mixed[] $column * * @return string */ abstract protected function _getCommonIntegerTypeDeclarationSQL(array $column); /** * Lazy load Doctrine Type Mappings. * * @return void */ abstract protected function initializeDoctrineTypeMappings(); /** * Initializes Doctrine Type Mappings with the platform defaults * and with all additional type mappings. */ private function initializeAllDoctrineTypeMappings(): void { $this->initializeDoctrineTypeMappings(); foreach (Type::getTypesMap() as $typeName => $className) { foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) { $dbType = strtolower($dbType); $this->doctrineTypeMapping[$dbType] = $typeName; } } } /** * Returns the SQL snippet used to declare a column that can * store characters in the ASCII character set * * @param mixed[] $column */ public function getAsciiStringTypeDeclarationSQL(array $column): string { return $this->getStringTypeDeclarationSQL($column); } /** * Returns the SQL snippet used to declare a VARCHAR column type. * * @deprecated Use {@link getStringTypeDeclarationSQL()} instead. * * @param mixed[] $column * * @return string */ public function getVarcharTypeDeclarationSQL(array $column) { if (isset($column['length'])) { $lengthOmitted = false; } else { $column['length'] = $this->getVarcharDefaultLength(); $lengthOmitted = true; } $fixed = $column['fixed'] ?? false; $maxLength = $fixed ? $this->getCharMaxLength() : $this->getVarcharMaxLength(); if ($column['length'] > $maxLength) { return $this->getClobTypeDeclarationSQL($column); } return $this->getVarcharTypeDeclarationSQLSnippet($column['length'], $fixed, $lengthOmitted); } /** * Returns the SQL snippet used to declare a string column type. * * @param mixed[] $column * * @return string */ public function getStringTypeDeclarationSQL(array $column) { return $this->getVarcharTypeDeclarationSQL($column); } /** * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. * * @param mixed[] $column The column definition. * * @return string */ public function getBinaryTypeDeclarationSQL(array $column) { if (isset($column['length'])) { $lengthOmitted = false; } else { $column['length'] = $this->getBinaryDefaultLength(); $lengthOmitted = true; } $fixed = $column['fixed'] ?? false; $maxLength = $this->getBinaryMaxLength(); if ($column['length'] > $maxLength) { if ($maxLength > 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3187', 'Binary column length %d is greater than supported by the platform (%d).' . ' Reduce the column length or use a BLOB column instead.', $column['length'], $maxLength, ); } return $this->getBlobTypeDeclarationSQL($column); } return $this->getBinaryTypeDeclarationSQLSnippet($column['length'], $fixed, $lengthOmitted); } /** * Returns the SQL snippet to declare a GUID/UUID column. * * By default this maps directly to a CHAR(36) and only maps to more * special datatypes when the underlying databases support this datatype. * * @param mixed[] $column * * @return string */ public function getGuidTypeDeclarationSQL(array $column) { $column['length'] = 36; $column['fixed'] = true; return $this->getStringTypeDeclarationSQL($column); } /** * Returns the SQL snippet to declare a JSON column. * * By default this maps directly to a CLOB and only maps to more * special datatypes when the underlying databases support this datatype. * * @param mixed[] $column * * @return string */ public function getJsonTypeDeclarationSQL(array $column) { return $this->getClobTypeDeclarationSQL($column); } /** * @param int|false $length * @param bool $fixed * * @return string * * @throws Exception If not supported on this platform. */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { throw Exception::notSupported('VARCHARs not supported by Platform.'); } /** * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. * * @param int|false $length The length of the column. * @param bool $fixed Whether the column length is fixed. * * @return string * * @throws Exception If not supported on this platform. */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { throw Exception::notSupported('BINARY/VARBINARY column types are not supported by this platform.'); } /** * Returns the SQL snippet used to declare a CLOB column type. * * @param mixed[] $column * * @return string */ abstract public function getClobTypeDeclarationSQL(array $column); /** * Returns the SQL Snippet used to declare a BLOB column type. * * @param mixed[] $column * * @return string */ abstract public function getBlobTypeDeclarationSQL(array $column); /** * Gets the name of the platform. * * @deprecated Identify platforms by their class. * * @return string */ abstract public function getName(); /** * Registers a doctrine type to be used in conjunction with a column type of this platform. * * @param string $dbType * @param string $doctrineType * * @return void * * @throws Exception If the type is not found. */ public function registerDoctrineTypeMapping($dbType, $doctrineType) { if ($this->doctrineTypeMapping === null) { $this->initializeAllDoctrineTypeMappings(); } if (! Types\Type::hasType($doctrineType)) { throw Exception::typeNotFound($doctrineType); } $dbType = strtolower($dbType); $this->doctrineTypeMapping[$dbType] = $doctrineType; $doctrineType = Type::getType($doctrineType); if (! $doctrineType->requiresSQLCommentHint($this)) { return; } $this->markDoctrineTypeCommented($doctrineType); } /** * Gets the Doctrine type that is mapped for the given database column type. * * @param string $dbType * * @return string * * @throws Exception */ public function getDoctrineTypeMapping($dbType) { if ($this->doctrineTypeMapping === null) { $this->initializeAllDoctrineTypeMappings(); } $dbType = strtolower($dbType); if (! isset($this->doctrineTypeMapping[$dbType])) { throw new Exception( 'Unknown database type ' . $dbType . ' requested, ' . static::class . ' may not support it.', ); } return $this->doctrineTypeMapping[$dbType]; } /** * Checks if a database type is currently supported by this platform. * * @param string $dbType * * @return bool */ public function hasDoctrineTypeMappingFor($dbType) { if ($this->doctrineTypeMapping === null) { $this->initializeAllDoctrineTypeMappings(); } $dbType = strtolower($dbType); return isset($this->doctrineTypeMapping[$dbType]); } /** * Initializes the Doctrine Type comments instance variable for in_array() checks. * * @deprecated This API will be removed in Doctrine DBAL 4.0. * * @return void */ protected function initializeCommentedDoctrineTypes() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5058', '%s is deprecated and will be removed in Doctrine DBAL 4.0.', __METHOD__, ); $this->doctrineTypeComments = []; foreach (Type::getTypesMap() as $typeName => $className) { $type = Type::getType($typeName); if (! $type->requiresSQLCommentHint($this)) { continue; } $this->doctrineTypeComments[] = $typeName; } } /** * Is it necessary for the platform to add a parsable type comment to allow reverse engineering the given type? * * @deprecated Use {@link Type::requiresSQLCommentHint()} instead. * * @return bool */ public function isCommentedDoctrineType(Type $doctrineType) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5058', '%s is deprecated and will be removed in Doctrine DBAL 4.0. Use Type::requiresSQLCommentHint() instead.', __METHOD__, ); if ($this->doctrineTypeComments === null) { $this->initializeCommentedDoctrineTypes(); } return $doctrineType->requiresSQLCommentHint($this); } /** * Marks this type as to be commented in ALTER TABLE and CREATE TABLE statements. * * @param string|Type $doctrineType * * @return void */ public function markDoctrineTypeCommented($doctrineType) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5058', '%s is deprecated and will be removed in Doctrine DBAL 4.0. Use Type::requiresSQLCommentHint() instead.', __METHOD__, ); if ($this->doctrineTypeComments === null) { $this->initializeCommentedDoctrineTypes(); } assert(is_array($this->doctrineTypeComments)); $this->doctrineTypeComments[] = $doctrineType instanceof Type ? $doctrineType->getName() : $doctrineType; } /** * Gets the comment to append to a column comment that helps parsing this type in reverse engineering. * * @deprecated This method will be removed without replacement. * * @return string */ public function getDoctrineTypeComment(Type $doctrineType) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5107', '%s is deprecated and will be removed in Doctrine DBAL 4.0.', __METHOD__, ); return '(DC2Type:' . $doctrineType->getName() . ')'; } /** * Gets the comment of a passed column modified by potential doctrine type comment hints. * * @deprecated This method will be removed without replacement. * * @return string|null */ protected function getColumnComment(Column $column) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5107', '%s is deprecated and will be removed in Doctrine DBAL 4.0.', __METHOD__, ); $comment = $column->getComment(); if (! $this->disableTypeComments && $column->getType()->requiresSQLCommentHint($this)) { $comment .= $this->getDoctrineTypeComment($column->getType()); } return $comment; } /** * Gets the character used for identifier quoting. * * @deprecated Use {@see quoteIdentifier()} to quote identifiers instead. * * @return string */ public function getIdentifierQuoteCharacter() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5388', 'AbstractPlatform::getIdentifierQuoteCharacter() is deprecated. Use quoteIdentifier() instead.', ); return '"'; } /** * Gets the string portion that starts an SQL comment. * * @deprecated * * @return string */ public function getSqlCommentStartString() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getSqlCommentStartString() is deprecated.', ); return '--'; } /** * Gets the string portion that ends an SQL comment. * * @deprecated * * @return string */ public function getSqlCommentEndString() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getSqlCommentEndString() is deprecated.', ); return "\n"; } /** * Gets the maximum length of a char column. * * @deprecated */ public function getCharMaxLength(): int { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'AbstractPlatform::getCharMaxLength() is deprecated.', ); return $this->getVarcharMaxLength(); } /** * Gets the maximum length of a varchar column. * * @deprecated * * @return int */ public function getVarcharMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'AbstractPlatform::getVarcharMaxLength() is deprecated.', ); return 4000; } /** * Gets the default length of a varchar column. * * @deprecated * * @return int */ public function getVarcharDefaultLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default varchar column length is deprecated, specify the length explicitly.', ); return 255; } /** * Gets the maximum length of a binary column. * * @deprecated * * @return int */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'AbstractPlatform::getBinaryMaxLength() is deprecated.', ); return 4000; } /** * Gets the default length of a binary column. * * @deprecated * * @return int */ public function getBinaryDefaultLength() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length is deprecated, specify the length explicitly.', ); return 255; } /** * Gets all SQL wildcard characters of the platform. * * @deprecated Use {@see AbstractPlatform::getLikeWildcardCharacters()} instead. * * @return string[] */ public function getWildcards() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getWildcards() is deprecated.' . ' Use AbstractPlatform::getLikeWildcardCharacters() instead.', ); return ['%', '_']; } /** * Returns the regular expression operator. * * @return string * * @throws Exception If not supported on this platform. */ public function getRegexpExpression() { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL snippet to get the average value of a column. * * @deprecated Use AVG() in SQL instead. * * @param string $column The column to use. * * @return string Generated SQL including an AVG aggregate function. */ public function getAvgExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getAvgExpression() is deprecated. Use AVG() in SQL instead.', ); return 'AVG(' . $column . ')'; } /** * Returns the SQL snippet to get the number of rows (without a NULL value) of a column. * * If a '*' is used instead of a column the number of selected rows is returned. * * @deprecated Use COUNT() in SQL instead. * * @param string|int $column The column to use. * * @return string Generated SQL including a COUNT aggregate function. */ public function getCountExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getCountExpression() is deprecated. Use COUNT() in SQL instead.', ); return 'COUNT(' . $column . ')'; } /** * Returns the SQL snippet to get the highest value of a column. * * @deprecated Use MAX() in SQL instead. * * @param string $column The column to use. * * @return string Generated SQL including a MAX aggregate function. */ public function getMaxExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getMaxExpression() is deprecated. Use MAX() in SQL instead.', ); return 'MAX(' . $column . ')'; } /** * Returns the SQL snippet to get the lowest value of a column. * * @deprecated Use MIN() in SQL instead. * * @param string $column The column to use. * * @return string Generated SQL including a MIN aggregate function. */ public function getMinExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getMinExpression() is deprecated. Use MIN() in SQL instead.', ); return 'MIN(' . $column . ')'; } /** * Returns the SQL snippet to get the total sum of a column. * * @deprecated Use SUM() in SQL instead. * * @param string $column The column to use. * * @return string Generated SQL including a SUM aggregate function. */ public function getSumExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getSumExpression() is deprecated. Use SUM() in SQL instead.', ); return 'SUM(' . $column . ')'; } // scalar functions /** * Returns the SQL snippet to get the md5 sum of a column. * * Note: Not SQL92, but common functionality. * * @deprecated * * @param string $column * * @return string */ public function getMd5Expression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getMd5Expression() is deprecated.', ); return 'MD5(' . $column . ')'; } /** * Returns the SQL snippet to get the length of a text column in characters. * * @param string $column * * @return string */ public function getLengthExpression($column) { return 'LENGTH(' . $column . ')'; } /** * Returns the SQL snippet to get the squared value of a column. * * @deprecated Use SQRT() in SQL instead. * * @param string $column The column to use. * * @return string Generated SQL including an SQRT aggregate function. */ public function getSqrtExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getSqrtExpression() is deprecated. Use SQRT() in SQL instead.', ); return 'SQRT(' . $column . ')'; } /** * Returns the SQL snippet to round a numeric column to the number of decimals specified. * * @deprecated Use ROUND() in SQL instead. * * @param string $column * @param string|int $decimals * * @return string */ public function getRoundExpression($column, $decimals = 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getRoundExpression() is deprecated. Use ROUND() in SQL instead.', ); return 'ROUND(' . $column . ', ' . $decimals . ')'; } /** * Returns the SQL snippet to get the remainder of the division operation $expression1 / $expression2. * * @param string $expression1 * @param string $expression2 * * @return string */ public function getModExpression($expression1, $expression2) { return 'MOD(' . $expression1 . ', ' . $expression2 . ')'; } /** * Returns the SQL snippet to trim a string. * * @param string $str The expression to apply the trim to. * @param int $mode The position of the trim (leading/trailing/both). * @param string|bool $char The char to trim, has to be quoted already. Defaults to space. * * @return string */ public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) { $expression = ''; switch ($mode) { case TrimMode::LEADING: $expression = 'LEADING '; break; case TrimMode::TRAILING: $expression = 'TRAILING '; break; case TrimMode::BOTH: $expression = 'BOTH '; break; } if ($char !== false) { $expression .= $char . ' '; } if ($mode !== TrimMode::UNSPECIFIED || $char !== false) { $expression .= 'FROM '; } return 'TRIM(' . $expression . $str . ')'; } /** * Returns the SQL snippet to trim trailing space characters from the expression. * * @deprecated Use RTRIM() in SQL instead. * * @param string $str Literal string or column name. * * @return string */ public function getRtrimExpression($str) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getRtrimExpression() is deprecated. Use RTRIM() in SQL instead.', ); return 'RTRIM(' . $str . ')'; } /** * Returns the SQL snippet to trim leading space characters from the expression. * * @deprecated Use LTRIM() in SQL instead. * * @param string $str Literal string or column name. * * @return string */ public function getLtrimExpression($str) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getLtrimExpression() is deprecated. Use LTRIM() in SQL instead.', ); return 'LTRIM(' . $str . ')'; } /** * Returns the SQL snippet to change all characters from the expression to uppercase, * according to the current character set mapping. * * @deprecated Use UPPER() in SQL instead. * * @param string $str Literal string or column name. * * @return string */ public function getUpperExpression($str) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getUpperExpression() is deprecated. Use UPPER() in SQL instead.', ); return 'UPPER(' . $str . ')'; } /** * Returns the SQL snippet to change all characters from the expression to lowercase, * according to the current character set mapping. * * @deprecated Use LOWER() in SQL instead. * * @param string $str Literal string or column name. * * @return string */ public function getLowerExpression($str) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getLowerExpression() is deprecated. Use LOWER() in SQL instead.', ); return 'LOWER(' . $str . ')'; } /** * Returns the SQL snippet to get the position of the first occurrence of substring $substr in string $str. * * @param string $str Literal string. * @param string $substr Literal string to find. * @param string|int|false $startPos Position to start at, beginning of string by default. * * @return string * * @throws Exception If not supported on this platform. */ public function getLocateExpression($str, $substr, $startPos = false) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL snippet to get the current system date. * * @deprecated Generate dates within the application. * * @return string */ public function getNowExpression() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4753', 'AbstractPlatform::getNowExpression() is deprecated. Generate dates within the application.', ); return 'NOW()'; } /** * Returns a SQL snippet to get a substring inside an SQL statement. * * Note: Not SQL92, but common functionality. * * SQLite only supports the 2 parameter variant of this function. * * @param string $string An sql string literal or column name/alias. * @param string|int $start Where to start the substring portion. * @param string|int|null $length The substring portion length. * * @return string */ public function getSubstringExpression($string, $start, $length = null) { if ($length === null) { return 'SUBSTRING(' . $string . ' FROM ' . $start . ')'; } return 'SUBSTRING(' . $string . ' FROM ' . $start . ' FOR ' . $length . ')'; } /** * Returns a SQL snippet to concatenate the given expressions. * * Accepts an arbitrary number of string parameters. Each parameter must contain an expression. * * @return string */ public function getConcatExpression() { return implode(' || ', func_get_args()); } /** * Returns the SQL for a logical not. * * Example: * <code> * $q = new Doctrine_Query(); * $e = $q->expr; * $q->select('*')->from('table') * ->where($e->eq('id', $e->not('null')); * </code> * * @deprecated Use NOT() in SQL instead. * * @param string $expression * * @return string The logical expression. */ public function getNotExpression($expression) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getNotExpression() is deprecated. Use NOT() in SQL instead.', ); return 'NOT(' . $expression . ')'; } /** * Returns the SQL that checks if an expression is null. * * @deprecated Use IS NULL in SQL instead. * * @param string $expression The expression that should be compared to null. * * @return string The logical expression. */ public function getIsNullExpression($expression) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getIsNullExpression() is deprecated. Use IS NULL in SQL instead.', ); return $expression . ' IS NULL'; } /** * Returns the SQL that checks if an expression is not null. * * @deprecated Use IS NOT NULL in SQL instead. * * @param string $expression The expression that should be compared to null. * * @return string The logical expression. */ public function getIsNotNullExpression($expression) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getIsNotNullExpression() is deprecated. Use IS NOT NULL in SQL instead.', ); return $expression . ' IS NOT NULL'; } /** * Returns the SQL that checks if an expression evaluates to a value between two values. * * The parameter $expression is checked if it is between $value1 and $value2. * * Note: There is a slight difference in the way BETWEEN works on some databases. * http://www.w3schools.com/sql/sql_between.asp. If you want complete database * independence you should avoid using between(). * * @deprecated Use BETWEEN in SQL instead. * * @param string $expression The value to compare to. * @param string $value1 The lower value to compare with. * @param string $value2 The higher value to compare with. * * @return string The logical expression. */ public function getBetweenExpression($expression, $value1, $value2) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getBetweenExpression() is deprecated. Use BETWEEN in SQL instead.', ); return $expression . ' BETWEEN ' . $value1 . ' AND ' . $value2; } /** * Returns the SQL to get the arccosine of a value. * * @deprecated Use ACOS() in SQL instead. * * @param string $value * * @return string */ public function getAcosExpression($value) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getAcosExpression() is deprecated. Use ACOS() in SQL instead.', ); return 'ACOS(' . $value . ')'; } /** * Returns the SQL to get the sine of a value. * * @deprecated Use SIN() in SQL instead. * * @param string $value * * @return string */ public function getSinExpression($value) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getSinExpression() is deprecated. Use SIN() in SQL instead.', ); return 'SIN(' . $value . ')'; } /** * Returns the SQL to get the PI value. * * @deprecated Use PI() in SQL instead. * * @return string */ public function getPiExpression() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getPiExpression() is deprecated. Use PI() in SQL instead.', ); return 'PI()'; } /** * Returns the SQL to get the cosine of a value. * * @deprecated Use COS() in SQL instead. * * @param string $value * * @return string */ public function getCosExpression($value) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getCosExpression() is deprecated. Use COS() in SQL instead.', ); return 'COS(' . $value . ')'; } /** * Returns the SQL to calculate the difference in days between the two passed dates. * * Computes diff = date1 - date2. * * @param string $date1 * @param string $date2 * * @return string * * @throws Exception If not supported on this platform. */ public function getDateDiffExpression($date1, $date2) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to add the number of given seconds to a date. * * @param string $date * @param int|string $seconds * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddSecondsExpression($date, $seconds) { if (is_int($seconds)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $seconds as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $seconds, DateIntervalUnit::SECOND); } /** * Returns the SQL to subtract the number of given seconds from a date. * * @param string $date * @param int|string $seconds * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubSecondsExpression($date, $seconds) { if (is_int($seconds)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $seconds as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $seconds, DateIntervalUnit::SECOND); } /** * Returns the SQL to add the number of given minutes to a date. * * @param string $date * @param int|string $minutes * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddMinutesExpression($date, $minutes) { if (is_int($minutes)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $minutes as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $minutes, DateIntervalUnit::MINUTE); } /** * Returns the SQL to subtract the number of given minutes from a date. * * @param string $date * @param int|string $minutes * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubMinutesExpression($date, $minutes) { if (is_int($minutes)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $minutes as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $minutes, DateIntervalUnit::MINUTE); } /** * Returns the SQL to add the number of given hours to a date. * * @param string $date * @param int|string $hours * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddHourExpression($date, $hours) { if (is_int($hours)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $hours as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $hours, DateIntervalUnit::HOUR); } /** * Returns the SQL to subtract the number of given hours to a date. * * @param string $date * @param int|string $hours * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubHourExpression($date, $hours) { if (is_int($hours)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $hours as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $hours, DateIntervalUnit::HOUR); } /** * Returns the SQL to add the number of given days to a date. * * @param string $date * @param int|string $days * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddDaysExpression($date, $days) { if (is_int($days)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $days as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $days, DateIntervalUnit::DAY); } /** * Returns the SQL to subtract the number of given days to a date. * * @param string $date * @param int|string $days * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubDaysExpression($date, $days) { if (is_int($days)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $days as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $days, DateIntervalUnit::DAY); } /** * Returns the SQL to add the number of given weeks to a date. * * @param string $date * @param int|string $weeks * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddWeeksExpression($date, $weeks) { if (is_int($weeks)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $weeks as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $weeks, DateIntervalUnit::WEEK); } /** * Returns the SQL to subtract the number of given weeks from a date. * * @param string $date * @param int|string $weeks * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubWeeksExpression($date, $weeks) { if (is_int($weeks)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $weeks as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $weeks, DateIntervalUnit::WEEK); } /** * Returns the SQL to add the number of given months to a date. * * @param string $date * @param int|string $months * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddMonthExpression($date, $months) { if (is_int($months)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $months as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $months, DateIntervalUnit::MONTH); } /** * Returns the SQL to subtract the number of given months to a date. * * @param string $date * @param int|string $months * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubMonthExpression($date, $months) { if (is_int($months)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $months as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $months, DateIntervalUnit::MONTH); } /** * Returns the SQL to add the number of given quarters to a date. * * @param string $date * @param int|string $quarters * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddQuartersExpression($date, $quarters) { if (is_int($quarters)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $quarters as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $quarters, DateIntervalUnit::QUARTER); } /** * Returns the SQL to subtract the number of given quarters from a date. * * @param string $date * @param int|string $quarters * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubQuartersExpression($date, $quarters) { if (is_int($quarters)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $quarters as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $quarters, DateIntervalUnit::QUARTER); } /** * Returns the SQL to add the number of given years to a date. * * @param string $date * @param int|string $years * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddYearsExpression($date, $years) { if (is_int($years)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $years as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $years, DateIntervalUnit::YEAR); } /** * Returns the SQL to subtract the number of given years from a date. * * @param string $date * @param int|string $years * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubYearsExpression($date, $years) { if (is_int($years)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $years as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $years, DateIntervalUnit::YEAR); } /** * Returns the SQL for a date arithmetic expression. * * @param string $date The column or literal representing a date * to perform the arithmetic operation on. * @param string $operator The arithmetic operator (+ or -). * @param int|string $interval The interval that shall be calculated into the date. * @param string $unit The unit of the interval that shall be calculated into the date. * One of the {@see DateIntervalUnit} constants. * * @return string * * @throws Exception If not supported on this platform. */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { throw Exception::notSupported(__METHOD__); } /** * Generates the SQL expression which represents the given date interval multiplied by a number * * @param string $interval SQL expression describing the interval value * @param int $multiplier Interval multiplier */ protected function multiplyInterval(string $interval, int $multiplier): string { return sprintf('(%s * %d)', $interval, $multiplier); } /** * Returns the SQL bit AND comparison expression. * * @param string $value1 * @param string $value2 * * @return string */ public function getBitAndComparisonExpression($value1, $value2) { return '(' . $value1 . ' & ' . $value2 . ')'; } /** * Returns the SQL bit OR comparison expression. * * @param string $value1 * @param string $value2 * * @return string */ public function getBitOrComparisonExpression($value1, $value2) { return '(' . $value1 . ' | ' . $value2 . ')'; } /** * Returns the SQL expression which represents the currently selected database. */ abstract public function getCurrentDatabaseExpression(): string; /** * Returns the FOR UPDATE expression. * * @deprecated This API is not portable. Use {@link QueryBuilder::forUpdate()}` instead. * * @return string */ public function getForUpdateSQL() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6191', '%s is deprecated as non-portable.', __METHOD__, ); return 'FOR UPDATE'; } /** * Honors that some SQL vendors such as MsSql use table hints for locking instead of the * ANSI SQL FOR UPDATE specification. * * @param string $fromClause The FROM clause to append the hint for the given lock mode to * @param int $lockMode One of the Doctrine\DBAL\LockMode::* constants * @psalm-param LockMode::* $lockMode */ public function appendLockHint(string $fromClause, int $lockMode): string { switch ($lockMode) { case LockMode::NONE: case LockMode::OPTIMISTIC: case LockMode::PESSIMISTIC_READ: case LockMode::PESSIMISTIC_WRITE: return $fromClause; default: throw InvalidLockMode::fromLockMode($lockMode); } } /** * Returns the SQL snippet to append to any SELECT statement which locks rows in shared read lock. * * This defaults to the ANSI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database * vendors allow to lighten this constraint up to be a real read lock. * * @deprecated This API is not portable. * * @return string */ public function getReadLockSQL() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6191', '%s is deprecated as non-portable.', __METHOD__, ); return $this->getForUpdateSQL(); } /** * Returns the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows. * * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ANSI SQL standard. * * @deprecated This API is not portable. * * @return string */ public function getWriteLockSQL() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6191', '%s is deprecated as non-portable.', __METHOD__, ); return $this->getForUpdateSQL(); } /** * Returns the SQL snippet to drop an existing table. * * @param Table|string $table * * @return string * * @throws InvalidArgumentException */ public function getDropTableSQL($table) { $tableArg = $table; if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } if (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', ); } if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaDropTable)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaDropTable, ); $eventArgs = new SchemaDropTableEventArgs($tableArg, $this); $this->_eventManager->dispatchEvent(Events::onSchemaDropTable, $eventArgs); if ($eventArgs->isDefaultPrevented()) { $sql = $eventArgs->getSql(); if ($sql === null) { throw new UnexpectedValueException('Default implementation of DROP TABLE was overridden with NULL'); } return $sql; } } return 'DROP TABLE ' . $table; } /** * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction. * * @param Table|string $table * * @return string */ public function getDropTemporaryTableSQL($table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } return $this->getDropTableSQL($table); } /** * Returns the SQL to drop an index from a table. * * @param Index|string $index * @param Table|string|null $table * * @return string * * @throws InvalidArgumentException */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $index = $index->getQuotedName($this); } elseif (! is_string($index)) { throw new InvalidArgumentException( __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.', ); } return 'DROP INDEX ' . $index; } /** * Returns the SQL to drop a constraint. * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. * * @param Constraint|string $constraint * @param Table|string $table * * @return string */ public function getDropConstraintSQL($constraint, $table) { if ($constraint instanceof Constraint) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $constraint as a Constraint object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); } else { $constraint = new Identifier($constraint); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); } else { $table = new Identifier($table); } $constraint = $constraint->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint; } /** * Returns the SQL to drop a foreign key. * * @param ForeignKeyConstraint|string $foreignKey * @param Table|string $table * * @return string */ public function getDropForeignKeySQL($foreignKey, $table) { if ($foreignKey instanceof ForeignKeyConstraint) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $foreignKey as a ForeignKeyConstraint object to %s is deprecated.' . ' Pass it as a quoted name instead.', __METHOD__, ); } else { $foreignKey = new Identifier($foreignKey); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); } else { $table = new Identifier($table); } $foreignKey = $foreignKey->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey; } /** * Returns the SQL to drop a unique constraint. */ public function getDropUniqueConstraintSQL(string $name, string $tableName): string { return $this->getDropConstraintSQL($name, $tableName); } /** * Returns the SQL statement(s) to create a table with the specified name, columns and constraints * on this platform. * * @param int $createFlags * @psalm-param int-mask-of<self::CREATE_*> $createFlags * * @return list<string> The list of SQL statements. * * @throws Exception * @throws InvalidArgumentException */ public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES) { if (! is_int($createFlags)) { throw new InvalidArgumentException( 'Second argument of AbstractPlatform::getCreateTableSQL() has to be integer.', ); } if (($createFlags & self::CREATE_INDEXES) === 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5416', 'Unsetting the CREATE_INDEXES flag in AbstractPlatform::getCreateTableSQL() is deprecated.', ); } if (($createFlags & self::CREATE_FOREIGNKEYS) === 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5416', 'Not setting the CREATE_FOREIGNKEYS flag in AbstractPlatform::getCreateTableSQL()' . ' is deprecated. In order to build the statements that create multiple tables' . ' referencing each other via foreign keys, use AbstractPlatform::getCreateTablesSQL().', ); } return $this->buildCreateTableSQL( $table, ($createFlags & self::CREATE_INDEXES) > 0, ($createFlags & self::CREATE_FOREIGNKEYS) > 0, ); } public function createSelectSQLBuilder(): SelectSQLBuilder { return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', 'SKIP LOCKED'); } /** * @internal * * @return list<string> * * @throws Exception */ final protected function getCreateTableWithoutForeignKeysSQL(Table $table): array { return $this->buildCreateTableSQL($table, true, false); } /** * @return list<string> * * @throws Exception */ private function buildCreateTableSQL(Table $table, bool $createIndexes, bool $createForeignKeys): array { if (count($table->getColumns()) === 0) { throw Exception::noColumnsSpecifiedForTable($table->getName()); } $tableName = $table->getQuotedName($this); $options = $table->getOptions(); $options['uniqueConstraints'] = []; $options['indexes'] = []; $options['primary'] = []; if ($createIndexes) { foreach ($table->getIndexes() as $index) { if (! $index->isPrimary()) { $options['indexes'][$index->getQuotedName($this)] = $index; continue; } $options['primary'] = $index->getQuotedColumns($this); $options['primary_index'] = $index; } foreach ($table->getUniqueConstraints() as $uniqueConstraint) { $options['uniqueConstraints'][$uniqueConstraint->getQuotedName($this)] = $uniqueConstraint; } } if ($createForeignKeys) { $options['foreignKeys'] = []; foreach ($table->getForeignKeys() as $fkConstraint) { $options['foreignKeys'][] = $fkConstraint; } } $columnSql = []; $columns = []; foreach ($table->getColumns() as $column) { if ( $this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn) ) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaCreateTableColumn, ); $eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this); $this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); if ($eventArgs->isDefaultPrevented()) { continue; } } $columnData = $this->columnToArray($column); if (in_array($column->getName(), $options['primary'], true)) { $columnData['primary'] = true; } $columns[$columnData['name']] = $columnData; } if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaCreateTable, ); $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this); $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs); if ($eventArgs->isDefaultPrevented()) { return array_merge($eventArgs->getSql(), $columnSql); } } $sql = $this->_getCreateTableSQL($tableName, $columns, $options); if ($this->supportsCommentOnStatement()) { if ($table->hasOption('comment')) { $sql[] = $this->getCommentOnTableSQL($tableName, $table->getOption('comment')); } foreach ($table->getColumns() as $column) { $comment = $this->getColumnComment($column); if ($comment === null || $comment === '') { continue; } $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getQuotedName($this), $comment); } } return array_merge($sql, $columnSql); } /** * @param list<Table> $tables * * @return list<string> * * @throws Exception */ public function getCreateTablesSQL(array $tables): array { $sql = []; foreach ($tables as $table) { $sql = array_merge($sql, $this->getCreateTableWithoutForeignKeysSQL($table)); } foreach ($tables as $table) { foreach ($table->getForeignKeys() as $foreignKey) { $sql[] = $this->getCreateForeignKeySQL( $foreignKey, $table->getQuotedName($this), ); } } return $sql; } /** * @param list<Table> $tables * * @return list<string> */ public function getDropTablesSQL(array $tables): array { $sql = []; foreach ($tables as $table) { foreach ($table->getForeignKeys() as $foreignKey) { $sql[] = $this->getDropForeignKeySQL( $foreignKey->getQuotedName($this), $table->getQuotedName($this), ); } } foreach ($tables as $table) { $sql[] = $this->getDropTableSQL($table->getQuotedName($this)); } return $sql; } protected function getCommentOnTableSQL(string $tableName, ?string $comment): string { $tableName = new Identifier($tableName); return sprintf( 'COMMENT ON TABLE %s IS %s', $tableName->getQuotedName($this), $this->quoteStringLiteral((string) $comment), ); } /** * @param string $tableName * @param string $columnName * @param string|null $comment * * @return string */ public function getCommentOnColumnSQL($tableName, $columnName, $comment) { $tableName = new Identifier($tableName); $columnName = new Identifier($columnName); return sprintf( 'COMMENT ON COLUMN %s.%s IS %s', $tableName->getQuotedName($this), $columnName->getQuotedName($this), $this->quoteStringLiteral((string) $comment), ); } /** * Returns the SQL to create inline comment on a column. * * @param string $comment * * @return string * * @throws Exception If not supported on this platform. */ public function getInlineColumnCommentSQL($comment) { if (! $this->supportsInlineColumnComments()) { throw Exception::notSupported(__METHOD__); } return 'COMMENT ' . $this->quoteStringLiteral($comment); } /** * Returns the SQL used to create a table. * * @param string $name * @param mixed[][] $columns * @param mixed[] $options * * @return string[] */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $columnListSql = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $index => $definition) { $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); } } if (isset($options['primary']) && ! empty($options['primary'])) { $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')'; } if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index => $definition) { $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition); } } $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; $check = $this->getCheckDeclarationSQL($columns); if (! empty($check)) { $query .= ', ' . $check; } $query .= ')'; $sql = [$query]; if (isset($options['foreignKeys'])) { foreach ($options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } return $sql; } /** @return string */ public function getCreateTemporaryTableSnippetSQL() { return 'CREATE TEMPORARY TABLE'; } /** * Generates SQL statements that can be used to apply the diff. * * @return list<string> */ public function getAlterSchemaSQL(SchemaDiff $diff): array { return $diff->toSql($this); } /** * Returns the SQL to create a sequence on this platform. * * @return string * * @throws Exception If not supported on this platform. */ public function getCreateSequenceSQL(Sequence $sequence) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to change a sequence on this platform. * * @return string * * @throws Exception If not supported on this platform. */ public function getAlterSequenceSQL(Sequence $sequence) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL snippet to drop an existing sequence. * * @param Sequence|string $sequence * * @return string * * @throws Exception If not supported on this platform. */ public function getDropSequenceSQL($sequence) { if (! $this->supportsSequences()) { throw Exception::notSupported(__METHOD__); } if ($sequence instanceof Sequence) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $sequence as a Sequence object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $sequence = $sequence->getQuotedName($this); } return 'DROP SEQUENCE ' . $sequence; } /** * Returns the SQL to create a constraint on a table on this platform. * * @deprecated Use {@see getCreateIndexSQL()}, {@see getCreateForeignKeySQL()} * or {@see getCreateUniqueConstraintSQL()} instead. * * @param Table|string $table * * @return string * * @throws InvalidArgumentException */ public function getCreateConstraintSQL(Constraint $constraint, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this); $columnList = '(' . implode(', ', $constraint->getQuotedColumns($this)) . ')'; $referencesClause = ''; if ($constraint instanceof Index) { if ($constraint->isPrimary()) { $query .= ' PRIMARY KEY'; } elseif ($constraint->isUnique()) { $query .= ' UNIQUE'; } else { throw new InvalidArgumentException( 'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().', ); } } elseif ($constraint instanceof UniqueConstraint) { $query .= ' UNIQUE'; } elseif ($constraint instanceof ForeignKeyConstraint) { $query .= ' FOREIGN KEY'; $referencesClause = ' REFERENCES ' . $constraint->getQuotedForeignTableName($this) . ' (' . implode(', ', $constraint->getQuotedForeignColumns($this)) . ')'; } $query .= ' ' . $columnList . $referencesClause; return $query; } /** * Returns the SQL to create an index on a table on this platform. * * @param Table|string $table The name of the table on which the index is to be created. * * @return string * * @throws InvalidArgumentException */ public function getCreateIndexSQL(Index $index, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } $name = $index->getQuotedName($this); $columns = $index->getColumns(); if (count($columns) === 0) { throw new InvalidArgumentException(sprintf( 'Incomplete or invalid index definition %s on table %s', $name, $table, )); } if ($index->isPrimary()) { return $this->getCreatePrimaryKeySQL($index, $table); } $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; $query .= ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); return $query; } /** * Adds condition for partial index. * * @return string */ protected function getPartialIndexSQL(Index $index) { if ($this->supportsPartialIndexes() && $index->hasOption('where')) { return ' WHERE ' . $index->getOption('where'); } return ''; } /** * Adds additional flags for index generation. * * @return string */ protected function getCreateIndexSQLFlags(Index $index) { return $index->isUnique() ? 'UNIQUE ' : ''; } /** * Returns the SQL to create an unnamed primary key constraint. * * @param Table|string $table * * @return string */ public function getCreatePrimaryKeySQL(Index $index, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; } /** * Returns the SQL to create a named schema. * * @param string $schemaName * * @return string * * @throws Exception If not supported on this platform. */ public function getCreateSchemaSQL($schemaName) { if (! $this->supportsSchemas()) { throw Exception::notSupported(__METHOD__); } return 'CREATE SCHEMA ' . $schemaName; } /** * Returns the SQL to create a unique constraint on a table on this platform. */ public function getCreateUniqueConstraintSQL(UniqueConstraint $constraint, string $tableName): string { return $this->getCreateConstraintSQL($constraint, $tableName); } /** * Returns the SQL snippet to drop a schema. * * @throws Exception If not supported on this platform. */ public function getDropSchemaSQL(string $schemaName): string { if (! $this->supportsSchemas()) { throw Exception::notSupported(__METHOD__); } return 'DROP SCHEMA ' . $schemaName; } /** * Quotes a string so that it can be safely used as a table or column name, * even if it is a reserved word of the platform. This also detects identifier * chains separated by dot and quotes them independently. * * NOTE: Just because you CAN use quoted identifiers doesn't mean * you SHOULD use them. In general, they end up causing way more * problems than they solve. * * @param string $str The identifier name to be quoted. * * @return string The quoted identifier string. */ public function quoteIdentifier($str) { if (strpos($str, '.') !== false) { $parts = array_map([$this, 'quoteSingleIdentifier'], explode('.', $str)); return implode('.', $parts); } return $this->quoteSingleIdentifier($str); } /** * Quotes a single identifier (no dot chain separation). * * @param string $str The identifier name to be quoted. * * @return string The quoted identifier string. */ public function quoteSingleIdentifier($str) { $c = $this->getIdentifierQuoteCharacter(); return $c . str_replace($c, $c . $c, $str) . $c; } /** * Returns the SQL to create a new foreign key. * * @param ForeignKeyConstraint $foreignKey The foreign key constraint. * @param Table|string $table The name of the table on which the foreign key is to be created. * * @return string */ public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } return 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey); } /** * Gets the SQL statements for altering an existing table. * * This method returns an array of SQL statements, since some platforms need several statements. * * @return list<string> * * @throws Exception If not supported on this platform. */ public function getAlterTableSQL(TableDiff $diff) { throw Exception::notSupported(__METHOD__); } /** @return list<string> */ public function getRenameTableSQL(string $oldName, string $newName): array { return [ sprintf('ALTER TABLE %s RENAME TO %s', $oldName, $newName), ]; } /** * @param mixed[] $columnSql * * @return bool */ protected function onSchemaAlterTableAddColumn(Column $column, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableAddColumn)) { return false; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaAlterTableAddColumn, ); $eventArgs = new SchemaAlterTableAddColumnEventArgs($column, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableAddColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string[] $columnSql * * @return bool */ protected function onSchemaAlterTableRemoveColumn(Column $column, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRemoveColumn)) { return false; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaAlterTableRemoveColumn, ); $eventArgs = new SchemaAlterTableRemoveColumnEventArgs($column, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRemoveColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string[] $columnSql * * @return bool */ protected function onSchemaAlterTableChangeColumn(ColumnDiff $columnDiff, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) { return false; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaAlterTableChangeColumn, ); $eventArgs = new SchemaAlterTableChangeColumnEventArgs($columnDiff, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableChangeColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string $oldColumnName * @param string[] $columnSql * * @return bool */ protected function onSchemaAlterTableRenameColumn($oldColumnName, Column $column, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRenameColumn)) { return false; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaAlterTableRenameColumn, ); $eventArgs = new SchemaAlterTableRenameColumnEventArgs($oldColumnName, $column, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRenameColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string[] $sql * * @return bool */ protected function onSchemaAlterTable(TableDiff $diff, &$sql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTable)) { return false; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaAlterTable, ); $eventArgs = new SchemaAlterTableEventArgs($diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTable, $eventArgs); $sql = array_merge($sql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** @return string[] */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); $sql = []; if ($this->supportsForeignKeyConstraints()) { foreach ($diff->getDroppedForeignKeys() as $foreignKey) { if ($foreignKey instanceof ForeignKeyConstraint) { $foreignKey = $foreignKey->getQuotedName($this); } $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableNameSQL); } foreach ($diff->getModifiedForeignKeys() as $foreignKey) { $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL); } } foreach ($diff->getDroppedIndexes() as $index) { $sql[] = $this->getDropIndexSQL($index->getQuotedName($this), $tableNameSQL); } foreach ($diff->getModifiedIndexes() as $index) { $sql[] = $this->getDropIndexSQL($index->getQuotedName($this), $tableNameSQL); } return $sql; } /** @return string[] */ protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) { $sql = []; $newName = $diff->getNewName(); if ($newName !== false) { $tableNameSQL = $newName->getQuotedName($this); } else { $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); } if ($this->supportsForeignKeyConstraints()) { foreach ($diff->getAddedForeignKeys() as $foreignKey) { $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL); } foreach ($diff->getModifiedForeignKeys() as $foreignKey) { $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL); } } foreach ($diff->getAddedIndexes() as $index) { $sql[] = $this->getCreateIndexSQL($index, $tableNameSQL); } foreach ($diff->getModifiedIndexes() as $index) { $sql[] = $this->getCreateIndexSQL($index, $tableNameSQL); } foreach ($diff->getRenamedIndexes() as $oldIndexName => $index) { $oldIndexName = new Identifier($oldIndexName); $sql = array_merge( $sql, $this->getRenameIndexSQL($oldIndexName->getQuotedName($this), $index, $tableNameSQL), ); } return $sql; } /** * Returns the SQL for renaming an index on a table. * * @param string $oldIndexName The name of the index to rename from. * @param Index $index The definition of the index to rename to. * @param string $tableName The table to rename the given index on. * * @return string[] The sequence of SQL statements for renaming the given index. */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { return [ $this->getDropIndexSQL($oldIndexName, $tableName), $this->getCreateIndexSQL($index, $tableName), ]; } /** * Gets declaration of a number of columns in bulk. * * @param mixed[][] $columns A multidimensional associative array. * The first dimension determines the column name, while the second * dimension is keyed with the name of the properties * of the column being declared as array indexes. Currently, the types * of supported column properties are as follows: * * length * Integer value that determines the maximum length of the text * column. If this argument is missing the column should be * declared to have the longest length allowed by the DBMS. * * default * Text value to be used as default for this column. * * notnull * Boolean flag that indicates whether this column is constrained * to not be set to null. * charset * Text value with the default CHARACTER SET for this column. * collation * Text value with the default COLLATION for this column. * unique * unique constraint * * @return string */ public function getColumnDeclarationListSQL(array $columns) { $declarations = []; foreach ($columns as $name => $column) { $declarations[] = $this->getColumnDeclarationSQL($name, $column); } return implode(', ', $declarations); } /** * Obtains DBMS specific SQL code portion needed to declare a generic type * column to be used in statements like CREATE TABLE. * * @param string $name The name the column to be declared. * @param mixed[] $column An associative array with the name of the properties * of the column being declared as array indexes. Currently, the types * of supported column properties are as follows: * * length * Integer value that determines the maximum length of the text * column. If this argument is missing the column should be * declared to have the longest length allowed by the DBMS. * * default * Text value to be used as default for this column. * * notnull * Boolean flag that indicates whether this column is constrained * to not be set to null. * charset * Text value with the default CHARACTER SET for this column. * collation * Text value with the default COLLATION for this column. * unique * unique constraint * check * column check constraint * columnDefinition * a string that defines the complete column * * @return string DBMS specific SQL code portion that should be used to declare the column. * * @throws Exception */ public function getColumnDeclarationSQL($name, array $column) { if (isset($column['columnDefinition'])) { $declaration = $this->getCustomTypeDeclarationSQL($column); } else { $default = $this->getDefaultValueDeclarationSQL($column); $charset = ! empty($column['charset']) ? ' ' . $this->getColumnCharsetDeclarationSQL($column['charset']) : ''; $collation = ! empty($column['collation']) ? ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : ''; $notnull = ! empty($column['notnull']) ? ' NOT NULL' : ''; if (! empty($column['unique'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "unique" column property is deprecated. Use unique constraints instead.', ); $unique = ' ' . $this->getUniqueFieldDeclarationSQL(); } else { $unique = ''; } if (! empty($column['check'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "check" column property is deprecated.', ); $check = ' ' . $column['check']; } else { $check = ''; } $typeDecl = $column['type']->getSQLDeclaration($column, $this); $declaration = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation; if ($this->supportsInlineColumnComments() && isset($column['comment']) && $column['comment'] !== '') { $declaration .= ' ' . $this->getInlineColumnCommentSQL($column['comment']); } } return $name . ' ' . $declaration; } /** * Returns the SQL snippet that declares a floating point column of arbitrary precision. * * @param mixed[] $column * * @return string */ public function getDecimalTypeDeclarationSQL(array $column) { if (empty($column['precision'])) { if (! isset($column['precision'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5637', 'Relying on the default decimal column precision is deprecated' . ', specify the precision explicitly.', ); } $precision = 10; } else { $precision = $column['precision']; } if (empty($column['scale'])) { if (! isset($column['scale'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5637', 'Relying on the default decimal column scale is deprecated' . ', specify the scale explicitly.', ); } $scale = 0; } else { $scale = $column['scale']; } return 'NUMERIC(' . $precision . ', ' . $scale . ')'; } /** * Obtains DBMS specific SQL code portion needed to set a default value * declaration to be used in statements like CREATE TABLE. * * @param mixed[] $column The column definition array. * * @return string DBMS specific SQL code portion needed to set a default value. */ public function getDefaultValueDeclarationSQL($column) { if (! isset($column['default'])) { return empty($column['notnull']) ? ' DEFAULT NULL' : ''; } $default = $column['default']; if (! isset($column['type'])) { return " DEFAULT '" . $default . "'"; } $type = $column['type']; if ($type instanceof Types\PhpIntegerMappingType) { return ' DEFAULT ' . $default; } if ($type instanceof Types\PhpDateTimeMappingType && $default === $this->getCurrentTimestampSQL()) { return ' DEFAULT ' . $this->getCurrentTimestampSQL(); } if ($type instanceof Types\TimeType && $default === $this->getCurrentTimeSQL()) { return ' DEFAULT ' . $this->getCurrentTimeSQL(); } if ($type instanceof Types\DateType && $default === $this->getCurrentDateSQL()) { return ' DEFAULT ' . $this->getCurrentDateSQL(); } if ($type instanceof Types\BooleanType) { return ' DEFAULT ' . $this->convertBooleans($default); } return ' DEFAULT ' . $this->quoteStringLiteral($default); } /** * Obtains DBMS specific SQL code portion needed to set a CHECK constraint * declaration to be used in statements like CREATE TABLE. * * @param string[]|mixed[][] $definition The check definition. * * @return string DBMS specific SQL code portion needed to set a CHECK constraint. */ public function getCheckDeclarationSQL(array $definition) { $constraints = []; foreach ($definition as $column => $def) { if (is_string($def)) { $constraints[] = 'CHECK (' . $def . ')'; } else { if (isset($def['min'])) { $constraints[] = 'CHECK (' . $column . ' >= ' . $def['min'] . ')'; } if (isset($def['max'])) { $constraints[] = 'CHECK (' . $column . ' <= ' . $def['max'] . ')'; } } } return implode(', ', $constraints); } /** * Obtains DBMS specific SQL code portion needed to set a unique * constraint declaration to be used in statements like CREATE TABLE. * * @param string $name The name of the unique constraint. * @param UniqueConstraint $constraint The unique constraint definition. * * @return string DBMS specific SQL code portion needed to set a constraint. * * @throws InvalidArgumentException */ public function getUniqueConstraintDeclarationSQL($name, UniqueConstraint $constraint) { $columns = $constraint->getQuotedColumns($this); $name = new Identifier($name); if (count($columns) === 0) { throw new InvalidArgumentException("Incomplete definition. 'columns' required."); } $constraintFlags = array_merge(['UNIQUE'], array_map('strtoupper', $constraint->getFlags())); $constraintName = $name->getQuotedName($this); $columnListNames = $this->getColumnsFieldDeclarationListSQL($columns); return sprintf('CONSTRAINT %s %s (%s)', $constraintName, implode(' ', $constraintFlags), $columnListNames); } /** * Obtains DBMS specific SQL code portion needed to set an index * declaration to be used in statements like CREATE TABLE. * * @param string $name The name of the index. * @param Index $index The index definition. * * @return string DBMS specific SQL code portion needed to set an index. * * @throws InvalidArgumentException */ public function getIndexDeclarationSQL($name, Index $index) { $columns = $index->getColumns(); $name = new Identifier($name); if (count($columns) === 0) { throw new InvalidArgumentException("Incomplete definition. 'columns' required."); } return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name->getQuotedName($this) . ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); } /** * Obtains SQL code portion needed to create a custom column, * e.g. when a column has the "columnDefinition" keyword. * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate. * * @deprecated * * @param mixed[] $column * * @return string */ public function getCustomTypeDeclarationSQL(array $column) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5527', '%s is deprecated.', __METHOD__, ); return $column['columnDefinition']; } /** * Obtains DBMS specific SQL code portion needed to set an index * declaration to be used in statements like CREATE TABLE. * * @deprecated */ public function getIndexFieldDeclarationListSQL(Index $index): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5527', '%s is deprecated.', __METHOD__, ); return implode(', ', $index->getQuotedColumns($this)); } /** * Obtains DBMS specific SQL code portion needed to set an index * declaration to be used in statements like CREATE TABLE. * * @deprecated * * @param mixed[] $columns */ public function getColumnsFieldDeclarationListSQL(array $columns): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5527', '%s is deprecated.', __METHOD__, ); $ret = []; foreach ($columns as $column => $definition) { if (is_array($definition)) { $ret[] = $column; } else { $ret[] = $definition; } } return implode(', ', $ret); } /** * Returns the required SQL string that fits between CREATE ... TABLE * to create the table as a temporary table. * * Should be overridden in driver classes to return the correct string for the * specific database type. * * The default is to return the string "TEMPORARY" - this will result in a * SQL error for any database that does not support temporary tables, or that * requires a different SQL command from "CREATE TEMPORARY TABLE". * * @deprecated * * @return string The string required to be placed between "CREATE" and "TABLE" * to generate a temporary table, if possible. */ public function getTemporaryTableSQL() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getTemporaryTableSQL() is deprecated.', ); return 'TEMPORARY'; } /** * Some vendors require temporary table names to be qualified specially. * * @param string $tableName * * @return string */ public function getTemporaryTableName($tableName) { return $tableName; } /** * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a column declaration to be used in statements like CREATE TABLE. * * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a column declaration. */ public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) { $sql = $this->getForeignKeyBaseDeclarationSQL($foreignKey); $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey); return $sql; } /** * Returns the FOREIGN KEY query section dealing with non-standard options * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... * * @param ForeignKeyConstraint $foreignKey The foreign key definition. * * @return string */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = ''; if ($foreignKey->hasOption('onUpdate')) { $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate')); } if ($foreignKey->hasOption('onDelete')) { $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); } return $query; } /** * Returns the given referential action in uppercase if valid, otherwise throws an exception. * * @param string $action The foreign key referential action. * * @return string * * @throws InvalidArgumentException If unknown referential action given. */ public function getForeignKeyReferentialActionSQL($action) { $upper = strtoupper($action); switch ($upper) { case 'CASCADE': case 'SET NULL': case 'NO ACTION': case 'RESTRICT': case 'SET DEFAULT': return $upper; default: throw new InvalidArgumentException('Invalid foreign key action: ' . $upper); } } /** * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a column declaration to be used in statements like CREATE TABLE. * * @return string * * @throws InvalidArgumentException */ public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) { $sql = ''; if (strlen($foreignKey->getName()) > 0) { $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' '; } $sql .= 'FOREIGN KEY ('; if (count($foreignKey->getLocalColumns()) === 0) { throw new InvalidArgumentException("Incomplete definition. 'local' required."); } if (count($foreignKey->getForeignColumns()) === 0) { throw new InvalidArgumentException("Incomplete definition. 'foreign' required."); } if (strlen($foreignKey->getForeignTableName()) === 0) { throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required."); } return $sql . implode(', ', $foreignKey->getQuotedLocalColumns($this)) . ') REFERENCES ' . $foreignKey->getQuotedForeignTableName($this) . ' (' . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')'; } /** * Obtains DBMS specific SQL code portion needed to set the UNIQUE constraint * of a column declaration to be used in statements like CREATE TABLE. * * @deprecated Use UNIQUE in SQL instead. * * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint * of a column declaration. */ public function getUniqueFieldDeclarationSQL() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getUniqueFieldDeclarationSQL() is deprecated. Use UNIQUE in SQL instead.', ); return 'UNIQUE'; } /** * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET * of a column declaration to be used in statements like CREATE TABLE. * * @param string $charset The name of the charset. * * @return string DBMS specific SQL code portion needed to set the CHARACTER SET * of a column declaration. */ public function getColumnCharsetDeclarationSQL($charset) { return ''; } /** * Obtains DBMS specific SQL code portion needed to set the COLLATION * of a column declaration to be used in statements like CREATE TABLE. * * @param string $collation The name of the collation. * * @return string DBMS specific SQL code portion needed to set the COLLATION * of a column declaration. */ public function getColumnCollationDeclarationSQL($collation) { return $this->supportsColumnCollation() ? 'COLLATE ' . $this->quoteSingleIdentifier($collation) : ''; } /** * Whether the platform prefers identity columns (eg. autoincrement) for ID generation. * Subclasses should override this method to return TRUE if they prefer identity columns. * * @deprecated * * @return bool */ public function prefersIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/1519', 'AbstractPlatform::prefersIdentityColumns() is deprecated.', ); return false; } /** * Some platforms need the boolean values to be converted. * * The default conversion in this implementation converts to integers (false => 0, true => 1). * * Note: if the input is not a boolean the original input might be returned. * * There are two contexts when converting booleans: Literals and Prepared Statements. * This method should handle the literal case * * @param mixed $item A boolean or an array of them. * * @return mixed A boolean database value or an array of them. */ public function convertBooleans($item) { if (is_array($item)) { foreach ($item as $k => $value) { if (! is_bool($value)) { continue; } $item[$k] = (int) $value; } } elseif (is_bool($item)) { $item = (int) $item; } return $item; } /** * Some platforms have boolean literals that needs to be correctly converted * * The default conversion tries to convert value into bool "(bool)$item" * * @param T $item * * @return (T is null ? null : bool) * * @template T */ public function convertFromBoolean($item) { return $item === null ? null : (bool) $item; } /** * This method should handle the prepared statements case. When there is no * distinction, it's OK to use the same method. * * Note: if the input is not a boolean the original input might be returned. * * @param mixed $item A boolean or an array of them. * * @return mixed A boolean database value or an array of them. */ public function convertBooleansToDatabaseValue($item) { return $this->convertBooleans($item); } /** * Returns the SQL specific for the platform to get the current date. * * @return string */ public function getCurrentDateSQL() { return 'CURRENT_DATE'; } /** * Returns the SQL specific for the platform to get the current time. * * @return string */ public function getCurrentTimeSQL() { return 'CURRENT_TIME'; } /** * Returns the SQL specific for the platform to get the current timestamp * * @return string */ public function getCurrentTimestampSQL() { return 'CURRENT_TIMESTAMP'; } /** * Returns the SQL for a given transaction isolation level Connection constant. * * @param int $level * * @return string * * @throws InvalidArgumentException */ protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case TransactionIsolationLevel::READ_UNCOMMITTED: return 'READ UNCOMMITTED'; case TransactionIsolationLevel::READ_COMMITTED: return 'READ COMMITTED'; case TransactionIsolationLevel::REPEATABLE_READ: return 'REPEATABLE READ'; case TransactionIsolationLevel::SERIALIZABLE: return 'SERIALIZABLE'; default: throw new InvalidArgumentException('Invalid isolation level:' . $level); } } /** * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. * * @return string * * @throws Exception If not supported on this platform. */ public function getListDatabasesSQL() { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL statement for retrieving the namespaces defined in the database. * * @deprecated Use {@see AbstractSchemaManager::listSchemaNames()} instead. * * @return string * * @throws Exception If not supported on this platform. */ public function getListNamespacesSQL() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'AbstractPlatform::getListNamespacesSQL() is deprecated,' . ' use AbstractSchemaManager::listSchemaNames() instead.', ); throw Exception::notSupported(__METHOD__); } /** * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. * * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListSequencesSQL($database) { throw Exception::notSupported(__METHOD__); } /** * @deprecated * * @param string $table * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableConstraintsSQL($table) { throw Exception::notSupported(__METHOD__); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableColumnsSQL($table, $database = null) { throw Exception::notSupported(__METHOD__); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @return string * * @throws Exception If not supported on this platform. */ public function getListTablesSQL() { throw Exception::notSupported(__METHOD__); } /** * @deprecated * * @return string * * @throws Exception If not supported on this platform. */ public function getListUsersSQL() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getListUsersSQL() is deprecated.', ); throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to list all views of a database or user. * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. * * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListViewsSQL($database) { throw Exception::notSupported(__METHOD__); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * Returns the list of indexes for the current database. * * The current database parameter is optional but will always be passed * when using the SchemaManager API and is the database the given table is in. * * Attention: Some platforms only support currentDatabase when they * are connected with that database. Cross-database information schema * requests may be impossible. * * @param string $table * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableIndexesSQL($table, $database = null) { throw Exception::notSupported(__METHOD__); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableForeignKeysSQL($table) { throw Exception::notSupported(__METHOD__); } /** * @param string $name * @param string $sql * * @return string */ public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } /** * @param string $name * * @return string */ public function getDropViewSQL($name) { return 'DROP VIEW ' . $name; } /** * @param string $sequence * * @return string * * @throws Exception If not supported on this platform. */ public function getSequenceNextValSQL($sequence) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to create a new database. * * @param string $name The name of the database that should be created. * * @return string * * @throws Exception If not supported on this platform. */ public function getCreateDatabaseSQL($name) { if (! $this->supportsCreateDropDatabase()) { throw Exception::notSupported(__METHOD__); } return 'CREATE DATABASE ' . $name; } /** * Returns the SQL snippet to drop an existing database. * * @param string $name The name of the database that should be dropped. * * @return string */ public function getDropDatabaseSQL($name) { if (! $this->supportsCreateDropDatabase()) { throw Exception::notSupported(__METHOD__); } return 'DROP DATABASE ' . $name; } /** * Returns the SQL to set the transaction isolation level. * * @param int $level * * @return string * * @throws Exception If not supported on this platform. */ public function getSetTransactionIsolationSQL($level) { throw Exception::notSupported(__METHOD__); } /** * Obtains DBMS specific SQL to be used to create datetime columns in * statements like CREATE TABLE. * * @param mixed[] $column * * @return string * * @throws Exception If not supported on this platform. */ public function getDateTimeTypeDeclarationSQL(array $column) { throw Exception::notSupported(__METHOD__); } /** * Obtains DBMS specific SQL to be used to create datetime with timezone offset columns. * * @param mixed[] $column * * @return string */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return $this->getDateTimeTypeDeclarationSQL($column); } /** * Obtains DBMS specific SQL to be used to create date columns in statements * like CREATE TABLE. * * @param mixed[] $column * * @return string * * @throws Exception If not supported on this platform. */ public function getDateTypeDeclarationSQL(array $column) { throw Exception::notSupported(__METHOD__); } /** * Obtains DBMS specific SQL to be used to create time columns in statements * like CREATE TABLE. * * @param mixed[] $column * * @return string * * @throws Exception If not supported on this platform. */ public function getTimeTypeDeclarationSQL(array $column) { throw Exception::notSupported(__METHOD__); } /** * @param mixed[] $column * * @return string */ public function getFloatDeclarationSQL(array $column) { return 'DOUBLE PRECISION'; } /** * Gets the default transaction isolation level of the platform. * * @see TransactionIsolationLevel * * @return TransactionIsolationLevel::* The default isolation level. */ public function getDefaultTransactionIsolationLevel() { return TransactionIsolationLevel::READ_COMMITTED; } /* supports*() methods */ /** * Whether the platform supports sequences. * * @return bool */ public function supportsSequences() { return false; } /** * Whether the platform supports identity columns. * * Identity columns are columns that receive an auto-generated value from the * database on insert of a row. * * @return bool */ public function supportsIdentityColumns() { return false; } /** * Whether the platform emulates identity columns through sequences. * * Some platforms that do not support identity columns natively * but support sequences can emulate identity columns by using * sequences. * * @deprecated * * @return bool */ public function usesSequenceEmulatedIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return false; } /** * Returns the name of the sequence for a particular identity column in a particular table. * * @deprecated * * @see usesSequenceEmulatedIdentityColumns * * @param string $tableName The name of the table to return the sequence name for. * @param string $columnName The name of the identity column in the table to return the sequence name for. * * @return string * * @throws Exception If not supported on this platform. */ public function getIdentitySequenceName($tableName, $columnName) { throw Exception::notSupported(__METHOD__); } /** * Whether the platform supports indexes. * * @deprecated * * @return bool */ public function supportsIndexes() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsIndexes() is deprecated.', ); return true; } /** * Whether the platform supports partial indexes. * * @return bool */ public function supportsPartialIndexes() { return false; } /** * Whether the platform supports indexes with column length definitions. */ public function supportsColumnLengthIndexes(): bool { return false; } /** * Whether the platform supports altering tables. * * @deprecated All platforms must implement altering tables. * * @return bool */ public function supportsAlterTable() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsAlterTable() is deprecated. All platforms must implement altering tables.', ); return true; } /** * Whether the platform supports transactions. * * @deprecated * * @return bool */ public function supportsTransactions() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsTransactions() is deprecated.', ); return true; } /** * Whether the platform supports savepoints. * * @return bool */ public function supportsSavepoints() { return true; } /** * Whether the platform supports releasing savepoints. * * @return bool */ public function supportsReleaseSavepoints() { return $this->supportsSavepoints(); } /** * Whether the platform supports primary key constraints. * * @deprecated * * @return bool */ public function supportsPrimaryConstraints() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsPrimaryConstraints() is deprecated.', ); return true; } /** * Whether the platform supports foreign key constraints. * * @deprecated All platforms should support foreign key constraints. * * @return bool */ public function supportsForeignKeyConstraints() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5409', 'AbstractPlatform::supportsForeignKeyConstraints() is deprecated.', ); return true; } /** * Whether the platform supports database schemas. * * @return bool */ public function supportsSchemas() { return false; } /** * Whether this platform can emulate schemas. * * @deprecated * * Platforms that either support or emulate schemas don't automatically * filter a schema for the namespaced elements in {@see AbstractManager::introspectSchema()}. * * @return bool */ public function canEmulateSchemas() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4805', 'AbstractPlatform::canEmulateSchemas() is deprecated.', ); return false; } /** * Returns the default schema name. * * @deprecated * * @return string * * @throws Exception If not supported on this platform. */ public function getDefaultSchemaName() { throw Exception::notSupported(__METHOD__); } /** * Whether this platform supports create database. * * Some databases don't allow to create and drop databases at all or only with certain tools. * * @deprecated * * @return bool */ public function supportsCreateDropDatabase() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return true; } /** * Whether the platform supports getting the affected rows of a recent update/delete type query. * * @deprecated * * @return bool */ public function supportsGettingAffectedRows() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsGettingAffectedRows() is deprecated.', ); return true; } /** * Whether this platform support to add inline column comments as postfix. * * @return bool */ public function supportsInlineColumnComments() { return false; } /** * Whether this platform support the proprietary syntax "COMMENT ON asset". * * @return bool */ public function supportsCommentOnStatement() { return false; } /** * Does this platform have native guid type. * * @deprecated * * @return bool */ public function hasNativeGuidType() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return false; } /** * Does this platform have native JSON type. * * @deprecated * * @return bool */ public function hasNativeJsonType() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return false; } /** * Whether this platform supports views. * * @deprecated All platforms must implement support for views. * * @return bool */ public function supportsViews() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsViews() is deprecated. All platforms must implement support for views.', ); return true; } /** * Does this platform support column collation? * * @return bool */ public function supportsColumnCollation() { return false; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored datetime value of this platform. * * @return string The format string. */ public function getDateTimeFormatString() { return 'Y-m-d H:i:s'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored datetime with timezone value of this platform. * * @return string The format string. */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:s'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored date value of this platform. * * @return string The format string. */ public function getDateFormatString() { return 'Y-m-d'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored time value of this platform. * * @return string The format string. */ public function getTimeFormatString() { return 'H:i:s'; } /** * Adds an driver-specific LIMIT clause to the query. * * @param string $query * @param int|null $limit * @param int $offset * * @throws Exception */ final public function modifyLimitQuery($query, $limit, $offset = 0): string { if ($offset < 0) { throw new Exception(sprintf( 'Offset must be a positive integer or zero, %d given', $offset, )); } if ($offset > 0 && ! $this->supportsLimitOffset()) { throw new Exception(sprintf( 'Platform %s does not support offset values in limit queries.', $this->getName(), )); } if ($limit !== null) { $limit = (int) $limit; } return $this->doModifyLimitQuery($query, $limit, (int) $offset); } /** * Adds an platform-specific LIMIT clause to the query. * * @param string $query * @param int|null $limit * @param int $offset * * @return string */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit !== null) { $query .= sprintf(' LIMIT %d', $limit); } if ($offset > 0) { $query .= sprintf(' OFFSET %d', $offset); } return $query; } /** * Whether the database platform support offsets in modify limit clauses. * * @deprecated All platforms must implement support for offsets in modify limit clauses. * * @return bool */ public function supportsLimitOffset() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsViews() is deprecated.' . ' All platforms must implement support for offsets in modify limit clauses.', ); return true; } /** * Maximum length of any given database identifier, like tables or column names. * * @return int */ public function getMaxIdentifierLength() { return 63; } /** * Returns the insert SQL for an empty insert statement. * * @param string $quotedTableName * @param string $quotedIdentifierColumnName * * @return string */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (null)'; } /** * Generates a Truncate Table SQL statement for a given table. * * Cascade is not supported on many platforms but would optionally cascade the truncate by * following the foreign keys. * * @param string $tableName * @param bool $cascade * * @return string */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); } /** * This is for test reasons, many vendors have special requirements for dummy statements. * * @return string */ public function getDummySelectSQL() { $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; return sprintf('SELECT %s', $expression); } /** * Returns the SQL to create a new savepoint. * * @param string $savepoint * * @return string */ public function createSavePoint($savepoint) { return 'SAVEPOINT ' . $savepoint; } /** * Returns the SQL to release a savepoint. * * @param string $savepoint * * @return string */ public function releaseSavePoint($savepoint) { return 'RELEASE SAVEPOINT ' . $savepoint; } /** * Returns the SQL to rollback a savepoint. * * @param string $savepoint * * @return string */ public function rollbackSavePoint($savepoint) { return 'ROLLBACK TO SAVEPOINT ' . $savepoint; } /** * Returns the keyword list instance of this platform. * * @throws Exception If no keyword list is specified. */ final public function getReservedKeywordsList(): KeywordList { // Store the instance so it doesn't need to be generated on every request. return $this->_keywords ??= $this->createReservedKeywordsList(); } /** * Creates an instance of the reserved keyword list of this platform. * * This method will become @abstract in DBAL 4.0.0. * * @throws Exception */ protected function createReservedKeywordsList(): KeywordList { $class = $this->getReservedKeywordsClass(); $keywords = new $class(); if (! $keywords instanceof KeywordList) { throw Exception::notSupported(__METHOD__); } return $keywords; } /** * Returns the class name of the reserved keywords list. * * @deprecated Implement {@see createReservedKeywordsList()} instead. * * @return string * @psalm-return class-string<KeywordList> * * @throws Exception If not supported on this platform. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'AbstractPlatform::getReservedKeywordsClass() is deprecated,' . ' use AbstractPlatform::createReservedKeywordsList() instead.', ); throw Exception::notSupported(__METHOD__); } /** * Quotes a literal string. * This method is NOT meant to fix SQL injections! * It is only meant to escape this platform's string literal * quote character inside the given literal string. * * @param string $str The literal string to be quoted. * * @return string The quoted literal string. */ public function quoteStringLiteral($str) { $c = $this->getStringLiteralQuoteCharacter(); return $c . str_replace($c, $c . $c, $str) . $c; } /** * Gets the character used for string literal quoting. * * @deprecated Use {@see quoteStringLiteral()} to quote string literals instead. * * @return string */ public function getStringLiteralQuoteCharacter() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5388', 'AbstractPlatform::getStringLiteralQuoteCharacter() is deprecated.' . ' Use quoteStringLiteral() instead.', ); return "'"; } /** * Escapes metacharacters in a string intended to be used with a LIKE * operator. * * @param string $inputString a literal, unquoted string * @param string $escapeChar should be reused by the caller in the LIKE * expression. */ final public function escapeStringForLike(string $inputString, string $escapeChar): string { return preg_replace( '~([' . preg_quote($this->getLikeWildcardCharacters() . $escapeChar, '~') . '])~u', addcslashes($escapeChar, '\\') . '$1', $inputString, ); } /** * @return array<string,mixed> An associative array with the name of the properties * of the column being declared as array indexes. */ private function columnToArray(Column $column): array { $name = $column->getQuotedName($this); return array_merge($column->toArray(), [ 'name' => $name, 'version' => $column->hasPlatformOption('version') ? $column->getPlatformOption('version') : false, 'comment' => $this->getColumnComment($column), ]); } /** @internal */ public function createSQLParser(): Parser { return new Parser(false); } protected function getLikeWildcardCharacters(): string { return '%_'; } /** * Compares the definitions of the given columns in the context of this platform. * * @throws Exception */ public function columnsEqual(Column $column1, Column $column2): bool { $column1Array = $this->columnToArray($column1); $column2Array = $this->columnToArray($column2); // ignore explicit columnDefinition since it's not set on the Column generated by the SchemaManager unset($column1Array['columnDefinition']); unset($column2Array['columnDefinition']); if ( $this->getColumnDeclarationSQL('', $column1Array) !== $this->getColumnDeclarationSQL('', $column2Array) ) { return false; } if (! $this->columnDeclarationsMatch($column1, $column2)) { return false; } // If the platform supports inline comments, all comparison is already done above if ($this->supportsInlineColumnComments()) { return true; } if ($column1->getComment() !== $column2->getComment()) { return false; } // If disableTypeComments is true, we do not need to check types, all comparison is already done above if ($this->disableTypeComments) { return true; } return $column1->getType() === $column2->getType(); } /** * Whether the database data type matches that expected for the doctrine type for the given colunms. */ private function columnDeclarationsMatch(Column $column1, Column $column2): bool { return ! ( $column1->hasPlatformOption('declarationMismatch') || $column2->hasPlatformOption('declarationMismatch') ); } /** * Creates the schema manager that can be used to inspect and change the underlying * database schema according to the dialect of the platform. * * @throws Exception * * @abstract */ public function createSchemaManager(Connection $connection): AbstractSchemaManager { throw Exception::notSupported(__METHOD__); } } Platforms/SqlitePlatform.php 0000755 00000132070 00000000000 0012141 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\API\SQLite\UserDefinedFunctions; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Constraint; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\SqliteSchemaManager; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types; use Doctrine\DBAL\Types\IntegerType; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use function array_combine; use function array_keys; use function array_merge; use function array_search; use function array_unique; use function array_values; use function count; use function explode; use function implode; use function is_numeric; use function sprintf; use function sqrt; use function str_replace; use function strlen; use function strpos; use function strtolower; use function trim; /** * The SqlitePlatform class describes the specifics and dialects of the SQLite * database platform. * * @todo Rename: SQLitePlatform */ class SqlitePlatform extends AbstractPlatform { private bool $schemaEmulationEnabled = true; /** * {@inheritDoc} */ public function getRegexpExpression() { return 'REGEXP'; } /** * @deprecated Generate dates within the application. * * @param string $type * * @return string */ public function getNowExpression($type = 'timestamp') { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4753', 'SqlitePlatform::getNowExpression() is deprecated. Generate dates within the application.', ); switch ($type) { case 'time': return 'time(\'now\')'; case 'date': return 'date(\'now\')'; case 'timestamp': default: return 'datetime(\'now\')'; } } /** * {@inheritDoc} */ public function getModExpression($expression1, $expression2) { return $expression1 . ' % ' . $expression2; } /** * {@inheritDoc} */ public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) { $trimChar = $char !== false ? ', ' . $char : ''; switch ($mode) { case TrimMode::LEADING: $trimFn = 'LTRIM'; break; case TrimMode::TRAILING: $trimFn = 'RTRIM'; break; default: $trimFn = 'TRIM'; } return $trimFn . '(' . $str . $trimChar . ')'; } /** * {@inheritDoc} * * SQLite only supports the 2 parameter variant of this function */ public function getSubstringExpression($string, $start, $length = null) { if ($length !== null) { return 'SUBSTR(' . $string . ', ' . $start . ', ' . $length . ')'; } return 'SUBSTR(' . $string . ', ' . $start . ', LENGTH(' . $string . '))'; } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false || $startPos === 1 || $startPos === '1') { return 'INSTR(' . $str . ', ' . $substr . ')'; } return 'CASE WHEN INSTR(SUBSTR(' . $str . ', ' . $startPos . '), ' . $substr . ') > 0 THEN INSTR(SUBSTR(' . $str . ', ' . $startPos . '), ' . $substr . ') + ' . $startPos . ' - 1 ELSE 0 END'; } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { switch ($unit) { case DateIntervalUnit::SECOND: case DateIntervalUnit::MINUTE: case DateIntervalUnit::HOUR: return 'DATETIME(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')"; } switch ($unit) { case DateIntervalUnit::WEEK: $interval = $this->multiplyInterval((string) $interval, 7); $unit = DateIntervalUnit::DAY; break; case DateIntervalUnit::QUARTER: $interval = $this->multiplyInterval((string) $interval, 3); $unit = DateIntervalUnit::MONTH; break; } if (! is_numeric($interval)) { $interval = "' || " . $interval . " || '"; } return 'DATE(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')"; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2); } /** * {@inheritDoc} * * The DBAL doesn't support databases on the SQLite platform. The expression here always returns a fixed string * as an indicator of an implicitly selected database. * * @link https://www.sqlite.org/lang_select.html * @see Connection::getDatabase() */ public function getCurrentDatabaseExpression(): string { return "'main'"; } /** @link https://www2.sqlite.org/cvstrac/wiki?p=UnsupportedSql */ public function createSelectSQLBuilder(): SelectSQLBuilder { return new DefaultSelectSQLBuilder($this, null, null); } /** * {@inheritDoc} */ protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case TransactionIsolationLevel::READ_UNCOMMITTED: return '0'; case TransactionIsolationLevel::READ_COMMITTED: case TransactionIsolationLevel::REPEATABLE_READ: case TransactionIsolationLevel::SERIALIZABLE: return '1'; default: return parent::_getTransactionIsolationLevelSQL($level); } } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} * * @deprecated */ public function prefersIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/1519', 'SqlitePlatform::prefersIdentityColumns() is deprecated.', ); return true; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'BOOLEAN'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { // SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * @deprecated Use {@see getSmallIntTypeDeclarationSQL()} instead. * * @param array<string, mixed> $column * * @return string */ public function getTinyIntTypeDeclarationSQL(array $column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5511', '%s is deprecated. Use getSmallIntTypeDeclarationSQL() instead.', __METHOD__, ); // SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { // SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * @deprecated Use {@see getIntegerTypeDeclarationSQL()} instead. * * @param array<string, mixed> $column * * @return string */ public function getMediumIntTypeDeclarationSQL(array $column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5511', '%s is deprecated. Use getIntegerTypeDeclarationSQL() instead.', __METHOD__, ); // SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { return 'DATETIME'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { // sqlite autoincrement is only possible for the primary key if (! empty($column['autoincrement'])) { return ' PRIMARY KEY AUTOINCREMENT'; } return ! empty($column['unsigned']) ? ' UNSIGNED' : ''; } /** * Disables schema emulation. * * Schema emulation is enabled by default to maintain backwards compatibility. * Disable it to opt-in to the behavior of DBAL 4. * * @deprecated Will be removed in DBAL 4.0. */ public function disableSchemaEmulation(): void { $this->schemaEmulationEnabled = false; } private function emulateSchemaNamespacing(string $tableName): string { return $this->schemaEmulationEnabled ? str_replace('.', '__', $tableName) : $tableName; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) { return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint( $foreignKey->getQuotedLocalColumns($this), $this->emulateSchemaNamespacing($foreignKey->getQuotedForeignTableName($this)), $foreignKey->getQuotedForeignColumns($this), $foreignKey->getName(), $foreignKey->getOptions(), )); } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $name = $this->emulateSchemaNamespacing($name); $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $constraintName => $definition) { $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition); } } $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options); if (isset($options['foreignKeys'])) { foreach ($options['foreignKeys'] as $foreignKey) { $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey); } } $tableComment = ''; if (isset($options['comment'])) { $comment = trim($options['comment'], " '"); $tableComment = $this->getInlineTableCommentSQL($comment); } $query = ['CREATE TABLE ' . $name . ' ' . $tableComment . '(' . $queryFields . ')']; if (isset($options['alter']) && $options['alter'] === true) { return $query; } if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $indexDef) { $query[] = $this->getCreateIndexSQL($indexDef, $name); } } if (isset($options['unique']) && ! empty($options['unique'])) { foreach ($options['unique'] as $indexDef) { $query[] = $this->getCreateIndexSQL($indexDef, $name); } } return $query; } /** * Generate a PRIMARY KEY definition if no autoincrement value is used * * @param mixed[][] $columns * @param mixed[] $options */ private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options): string { if (empty($options['primary'])) { return ''; } $keyColumns = array_unique(array_values($options['primary'])); foreach ($keyColumns as $keyColumn) { if (! empty($columns[$keyColumn]['autoincrement'])) { return ''; } } return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'TEXT'); } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return 'BLOB'; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'SqlitePlatform::getBinaryMaxLength() is deprecated.', ); return 0; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryDefaultLength() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length is deprecated, specify the length explicitly.', ); return 0; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'CLOB'; } /** * @deprecated * * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { $table = $this->emulateSchemaNamespacing($table); return sprintf( "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = %s AND sql NOT NULL ORDER BY name", $this->quoteStringLiteral($table), ); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { $table = $this->emulateSchemaNamespacing($table); return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table)); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) { $table = $this->emulateSchemaNamespacing($table); return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table)); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { return 'SELECT name FROM sqlite_master' . " WHERE type = 'table'" . " AND name != 'sqlite_sequence'" . " AND name != 'geometry_columns'" . " AND name != 'spatial_ref_sys'" . ' UNION ALL SELECT name FROM sqlite_temp_master' . " WHERE type = 'table' ORDER BY name"; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey); if (! $foreignKey->hasOption('deferrable') || $foreignKey->getOption('deferrable') === false) { $query .= ' NOT'; } $query .= ' DEFERRABLE'; $query .= ' INITIALLY'; if ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) { $query .= ' DEFERRED'; } else { $query .= ' IMMEDIATE'; } return $query; } /** * {@inheritDoc} * * @deprecated */ public function supportsCreateDropDatabase() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return false; } /** * {@inheritDoc} */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsColumnCollation() { return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsInlineColumnComments() { return true; } /** * {@inheritDoc} */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4749', 'SqlitePlatform::getName() is deprecated. Identify platforms by their class.', ); return 'sqlite'; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); $tableName = $this->emulateSchemaNamespacing($tableIdentifier->getQuotedName($this)); return 'DELETE FROM ' . $tableName; } /** * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction(). * * @deprecated The driver will use {@see sqrt()} in the next major release. * * @param int|float $value * * @return float */ public static function udfSqrt($value) { return sqrt($value); } /** * User-defined function for Sqlite that implements MOD(a, b). * * @deprecated The driver will use {@see UserDefinedFunctions::mod()} in the next major release. * * @param int $a * @param int $b * * @return int */ public static function udfMod($a, $b) { return UserDefinedFunctions::mod($a, $b); } /** * @deprecated The driver will use {@see UserDefinedFunctions::locate()} in the next major release. * * @param string $str * @param string $substr * @param int $offset * * @return int */ public static function udfLocate($str, $substr, $offset = 0) { return UserDefinedFunctions::locate($str, $substr, $offset); } /** * {@inheritDoc} * * @deprecated This API is not portable. */ public function getForUpdateSQL() { return ''; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getInlineColumnCommentSQL($comment) { return '--' . str_replace("\n", "\n--", $comment) . "\n"; } private function getInlineTableCommentSQL(string $comment): string { return $this->getInlineColumnCommentSQL($comment); } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'bigint' => Types\Types::BIGINT, 'bigserial' => Types\Types::BIGINT, 'blob' => Types\Types::BLOB, 'boolean' => Types\Types::BOOLEAN, 'char' => Types\Types::STRING, 'clob' => Types\Types::TEXT, 'date' => Types\Types::DATE_MUTABLE, 'datetime' => Types\Types::DATETIME_MUTABLE, 'decimal' => Types\Types::DECIMAL, 'double' => Types\Types::FLOAT, 'double precision' => Types\Types::FLOAT, 'float' => Types\Types::FLOAT, 'image' => Types\Types::STRING, 'int' => Types\Types::INTEGER, 'integer' => Types\Types::INTEGER, 'longtext' => Types\Types::TEXT, 'longvarchar' => Types\Types::STRING, 'mediumint' => Types\Types::INTEGER, 'mediumtext' => Types\Types::TEXT, 'ntext' => Types\Types::STRING, 'numeric' => Types\Types::DECIMAL, 'nvarchar' => Types\Types::STRING, 'real' => Types\Types::FLOAT, 'serial' => Types\Types::INTEGER, 'smallint' => Types\Types::SMALLINT, 'text' => Types\Types::TEXT, 'time' => Types\Types::TIME_MUTABLE, 'timestamp' => Types\Types::DATETIME_MUTABLE, 'tinyint' => Types\Types::BOOLEAN, 'tinytext' => Types\Types::TEXT, 'varchar' => Types\Types::STRING, 'varchar2' => Types\Types::STRING, ]; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'SqlitePlatform::getReservedKeywordsClass() is deprecated,' . ' use SqlitePlatform::createReservedKeywordsList() instead.', ); return Keywords\SQLiteKeywords::class; } /** * {@inheritDoc} */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { return []; } /** * {@inheritDoc} */ protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) { $table = $diff->getOldTable(); if (! $table instanceof Table) { throw new Exception( 'Sqlite platform requires for alter table the table diff with reference to original table schema', ); } $sql = []; $tableName = $diff->getNewName(); if ($tableName === false) { $tableName = $diff->getName($this); } foreach ($this->getIndexesInAlteredTable($diff, $table) as $index) { if ($index->isPrimary()) { continue; } $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this)); } return $sql; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit === null && $offset > 0) { return sprintf('%s LIMIT -1 OFFSET %d', $query, $offset); } return parent::doModifyLimitQuery($query, $limit, $offset); } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'BLOB'; } /** * {@inheritDoc} */ public function getTemporaryTableName($tableName) { $tableName = $this->emulateSchemaNamespacing($tableName); return $tableName; } /** * {@inheritDoc} * * @deprecated * * Sqlite Platform emulates schema by underscoring each dot and generating tables * into the default database. * * This hack is implemented to be able to use SQLite as testdriver when * using schema supporting databases. */ public function canEmulateSchemas() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4805', 'SqlitePlatform::canEmulateSchemas() is deprecated.', ); return $this->schemaEmulationEnabled; } /** * {@inheritDoc} */ public function getCreateTablesSQL(array $tables): array { $sql = []; foreach ($tables as $table) { $sql = array_merge($sql, $this->getCreateTableSQL($table)); } return $sql; } /** * {@inheritDoc} */ public function getCreateIndexSQL(Index $index, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } $name = $index->getQuotedName($this); $columns = $index->getColumns(); if (count($columns) === 0) { throw new InvalidArgumentException(sprintf( 'Incomplete or invalid index definition %s on table %s', $name, $table, )); } if ($index->isPrimary()) { return $this->getCreatePrimaryKeySQL($index, $table); } if (strpos($table, '.') !== false) { [$schema, $table] = explode('.', $table, 2); $name = $schema . '.' . $name; } $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; $query .= ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); return $query; } /** * {@inheritDoc} */ public function getDropTablesSQL(array $tables): array { $sql = []; foreach ($tables as $table) { $sql[] = $this->getDropTableSQL($table->getQuotedName($this)); } return $sql; } /** * {@inheritDoc} */ public function getCreatePrimaryKeySQL(Index $index, $table) { throw new Exception('Sqlite platform does not support alter primary key.'); } /** * {@inheritDoc} */ public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) { throw new Exception('Sqlite platform does not support alter foreign key.'); } /** * {@inheritDoc} */ public function getDropForeignKeySQL($foreignKey, $table) { throw new Exception('Sqlite platform does not support alter foreign key.'); } /** * {@inheritDoc} * * @deprecated */ public function getCreateConstraintSQL(Constraint $constraint, $table) { throw new Exception('Sqlite platform does not support alter constraint.'); } /** * {@inheritDoc} * * @param int|null $createFlags * @psalm-param int-mask-of<AbstractPlatform::CREATE_*>|null $createFlags */ public function getCreateTableSQL(Table $table, $createFlags = null) { $createFlags = $createFlags ?? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS; return parent::getCreateTableSQL($table, $createFlags); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { $table = $this->emulateSchemaNamespacing($table); return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table)); } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = $this->getSimpleAlterTableSQL($diff); if ($sql !== false) { return $sql; } $table = $diff->getOldTable(); if (! $table instanceof Table) { throw new Exception( 'Sqlite platform requires for alter table the table diff with reference to original table schema', ); } $columns = []; $oldColumnNames = []; $newColumnNames = []; $columnSql = []; foreach ($table->getColumns() as $columnName => $column) { $columnName = strtolower($columnName); $columns[$columnName] = $column; $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this); } foreach ($diff->getDroppedColumns() as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $columnName = strtolower($column->getName()); if (! isset($columns[$columnName])) { continue; } unset( $columns[$columnName], $oldColumnNames[$columnName], $newColumnNames[$columnName], ); } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = strtolower($oldColumnName); $columns = $this->replaceColumn( $table->getName(), $columns, $oldColumnName, $column, ); if (! isset($newColumnNames[$oldColumnName])) { continue; } $newColumnNames[$oldColumnName] = $column->getQuotedName($this); } foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); $oldColumnName = strtolower($oldColumn->getName()); $columns = $this->replaceColumn( $table->getName(), $columns, $oldColumnName, $columnDiff->getNewColumn(), ); if (! isset($newColumnNames[$oldColumnName])) { continue; } $newColumnNames[$oldColumnName] = $columnDiff->getNewColumn()->getQuotedName($this); } foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columns[strtolower($column->getName())] = $column; } $sql = []; $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { $tableName = $table->getName(); if (strpos($tableName, '.') !== false) { [, $tableName] = explode('.', $tableName, 2); } $dataTable = new Table('__temp__' . $tableName); $newTable = new Table( $table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff, $table), [], $this->getForeignKeysInAlteredTable($diff, $table), $table->getOptions(), ); $newTable->addOption('alter', true); $sql = $this->getPreAlterTableIndexForeignKeySQL($diff); $sql[] = sprintf( 'CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this), ); $sql[] = $this->getDropTableSQL($table); $sql = array_merge($sql, $this->getCreateTableSQL($newTable)); $sql[] = sprintf( 'INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this), ); $sql[] = $this->getDropTableSQL($dataTable->getQuotedName($this)); $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $sql[] = sprintf( 'ALTER TABLE %s RENAME TO %s', $newTable->getQuotedName($this), $newName->getQuotedName($this), ); } $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff)); } return array_merge($sql, $tableSql, $columnSql); } /** * Replace the column with the given name with the new column. * * @param string $tableName * @param array<string,Column> $columns * @param string $columnName * * @return array<string,Column> * * @throws Exception */ private function replaceColumn($tableName, array $columns, $columnName, Column $column): array { $keys = array_keys($columns); $index = array_search($columnName, $keys, true); if ($index === false) { throw SchemaException::columnDoesNotExist($columnName, $tableName); } $values = array_values($columns); $keys[$index] = strtolower($column->getName()); $values[$index] = $column; return array_combine($keys, $values); } /** * @return string[]|false * * @throws Exception */ private function getSimpleAlterTableSQL(TableDiff $diff) { // Suppress changes on integer type autoincrement columns. foreach ($diff->getModifiedColumns() as $columnDiff) { $oldColumn = $columnDiff->getOldColumn(); if ($oldColumn === null) { continue; } $newColumn = $columnDiff->getNewColumn(); if (! $newColumn->getAutoincrement() || ! $newColumn->getType() instanceof IntegerType) { continue; } $oldColumnName = $oldColumn->getName(); if (! $columnDiff->hasTypeChanged() && $columnDiff->hasUnsignedChanged()) { unset($diff->changedColumns[$oldColumnName]); continue; } $fromColumnType = $oldColumn->getType(); if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) { continue; } unset($diff->changedColumns[$oldColumnName]); } if ( count($diff->getModifiedColumns()) > 0 || count($diff->getDroppedColumns()) > 0 || count($diff->getRenamedColumns()) > 0 || count($diff->getAddedIndexes()) > 0 || count($diff->getModifiedIndexes()) > 0 || count($diff->getDroppedIndexes()) > 0 || count($diff->getRenamedIndexes()) > 0 || count($diff->getAddedForeignKeys()) > 0 || count($diff->getModifiedForeignKeys()) > 0 || count($diff->getDroppedForeignKeys()) > 0 ) { return false; } $table = $diff->getOldTable() ?? $diff->getName($this); $sql = []; $tableSql = []; $columnSql = []; foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $definition = array_merge([ 'unique' => null, 'autoincrement' => null, 'default' => null, ], $column->toArray()); $type = $definition['type']; switch (true) { case isset($definition['columnDefinition']) || $definition['autoincrement'] || $definition['unique']: case $type instanceof Types\DateTimeType && $definition['default'] === $this->getCurrentTimestampSQL(): case $type instanceof Types\DateType && $definition['default'] === $this->getCurrentDateSQL(): case $type instanceof Types\TimeType && $definition['default'] === $this->getCurrentTimeSQL(): return false; } $definition['name'] = $column->getQuotedName($this); if ($type instanceof Types\StringType) { $definition['length'] ??= 255; } $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' . $this->getColumnDeclarationSQL($definition['name'], $definition); } if (! $this->onSchemaAlterTable($diff, $tableSql)) { if ($diff->newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of SQL that renames a table using %s is deprecated.' . ' Use getRenameTableSQL() instead.', __METHOD__, ); $newTable = new Identifier($diff->newName); $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this); } } return array_merge($sql, $tableSql, $columnSql); } /** @return string[] */ private function getColumnNamesInAlteredTable(TableDiff $diff, Table $fromTable): array { $columns = []; foreach ($fromTable->getColumns() as $columnName => $column) { $columns[strtolower($columnName)] = $column->getName(); } foreach ($diff->getDroppedColumns() as $column) { $columnName = strtolower($column->getName()); if (! isset($columns[$columnName])) { continue; } unset($columns[$columnName]); } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { $columnName = $column->getName(); $columns[strtolower($oldColumnName)] = $columnName; $columns[strtolower($columnName)] = $columnName; } foreach ($diff->getModifiedColumns() as $columnDiff) { $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); $oldColumnName = $oldColumn->getName(); $newColumnName = $columnDiff->getNewColumn()->getName(); $columns[strtolower($oldColumnName)] = $newColumnName; $columns[strtolower($newColumnName)] = $newColumnName; } foreach ($diff->getAddedColumns() as $column) { $columnName = $column->getName(); $columns[strtolower($columnName)] = $columnName; } return $columns; } /** @return Index[] */ private function getIndexesInAlteredTable(TableDiff $diff, Table $fromTable): array { $indexes = $fromTable->getIndexes(); $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable); foreach ($indexes as $key => $index) { foreach ($diff->getRenamedIndexes() as $oldIndexName => $renamedIndex) { if (strtolower($key) !== strtolower($oldIndexName)) { continue; } unset($indexes[$key]); } $changed = false; $indexColumns = []; foreach ($index->getColumns() as $columnName) { $normalizedColumnName = strtolower($columnName); if (! isset($columnNames[$normalizedColumnName])) { unset($indexes[$key]); continue 2; } $indexColumns[] = $columnNames[$normalizedColumnName]; if ($columnName === $columnNames[$normalizedColumnName]) { continue; } $changed = true; } if (! $changed) { continue; } $indexes[$key] = new Index( $index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags(), ); } foreach ($diff->getDroppedIndexes() as $index) { $indexName = strtolower($index->getName()); if (strlen($indexName) === 0 || ! isset($indexes[$indexName])) { continue; } unset($indexes[$indexName]); } foreach ( array_merge( $diff->getModifiedIndexes(), $diff->getAddedIndexes(), $diff->getRenamedIndexes(), ) as $index ) { $indexName = strtolower($index->getName()); if (strlen($indexName) > 0) { $indexes[$indexName] = $index; } else { $indexes[] = $index; } } return $indexes; } /** @return ForeignKeyConstraint[] */ private function getForeignKeysInAlteredTable(TableDiff $diff, Table $fromTable): array { $foreignKeys = $fromTable->getForeignKeys(); $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable); foreach ($foreignKeys as $key => $constraint) { $changed = false; $localColumns = []; foreach ($constraint->getLocalColumns() as $columnName) { $normalizedColumnName = strtolower($columnName); if (! isset($columnNames[$normalizedColumnName])) { unset($foreignKeys[$key]); continue 2; } $localColumns[] = $columnNames[$normalizedColumnName]; if ($columnName === $columnNames[$normalizedColumnName]) { continue; } $changed = true; } if (! $changed) { continue; } $foreignKeys[$key] = new ForeignKeyConstraint( $localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions(), ); } foreach ($diff->getDroppedForeignKeys() as $constraint) { if (! $constraint instanceof ForeignKeyConstraint) { $constraint = new Identifier($constraint); } $constraintName = strtolower($constraint->getName()); if (strlen($constraintName) === 0 || ! isset($foreignKeys[$constraintName])) { continue; } unset($foreignKeys[$constraintName]); } foreach (array_merge($diff->getModifiedForeignKeys(), $diff->getAddedForeignKeys()) as $constraint) { $constraintName = strtolower($constraint->getName()); if (strlen($constraintName) > 0) { $foreignKeys[$constraintName] = $constraint; } else { $foreignKeys[] = $constraint; } } return $foreignKeys; } /** @return Index[] */ private function getPrimaryIndexInAlteredTable(TableDiff $diff, Table $fromTable): array { $primaryIndex = []; foreach ($this->getIndexesInAlteredTable($diff, $fromTable) as $index) { if (! $index->isPrimary()) { continue; } $primaryIndex = [$index->getName() => $index]; } return $primaryIndex; } public function createSchemaManager(Connection $connection): SqliteSchemaManager { return new SqliteSchemaManager($connection, $this); } } Platforms/TrimMode.php 0000755 00000000452 00000000000 0010711 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms; final class TrimMode { public const UNSPECIFIED = 0; public const LEADING = 1; public const TRAILING = 2; public const BOTH = 3; /** @codeCoverageIgnore */ private function __construct() { } } Platforms/MySQLPlatform.php 0000755 00000000346 00000000000 0011645 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; /** * Provides the behavior, features and SQL dialect of the Oracle MySQL database platform * of the oldest supported version. */ class MySQLPlatform extends AbstractMySQLPlatform { } Platforms/MySQL84Platform.php 0000755 00000001370 00000000000 0012017 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\Deprecations\Deprecation; /** * Provides the behavior, features and SQL dialect of the MySQL 8.4 database platform. */ class MySQL84Platform extends MySQL80Platform { /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'MySQL84Platform::getReservedKeywordsClass() is deprecated,' . ' use MySQL84Platform::createReservedKeywordsList() instead.', ); return Keywords\MySQL84Keywords::class; } } Platforms/MariaDBPlatform.php 0000755 00000003163 00000000000 0012137 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; /** * Provides the behavior, features and SQL dialect of the MariaDB database platform of the oldest supported version. */ class MariaDBPlatform extends MySQLPlatform { /** * {@inheritDoc} * * Hop over the {@see AbstractMySQLPlatform} implementation until 4.0.x * where {@see MariaDBPlatform} no longer extends {@see MySQLPlatform}. * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getDefaultValueDeclarationSQL($column) { return AbstractPlatform::getDefaultValueDeclarationSQL($column); } /** * {@inheritDoc} * * @link https://mariadb.com/kb/en/library/json-data-type/ */ public function getJsonTypeDeclarationSQL(array $column): string { return 'LONGTEXT'; } /** @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass(): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'MariaDb1027Platform::getReservedKeywordsClass() is deprecated,' . ' use MariaDb1027Platform::createReservedKeywordsList() instead.', ); return Keywords\MariaDb102Keywords::class; } protected function initializeDoctrineTypeMappings(): void { parent::initializeDoctrineTypeMappings(); $this->doctrineTypeMapping['json'] = Types::JSON; } } Platforms/AbstractMySQLPlatform.php 0000755 00000133046 00000000000 0013335 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\AbstractAsset; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\MySQLSchemaManager; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types\BlobType; use Doctrine\DBAL\Types\TextType; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use function array_diff_key; use function array_merge; use function array_unique; use function array_values; use function count; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function in_array; use function is_numeric; use function is_string; use function sprintf; use function str_replace; use function strcasecmp; use function strtolower; use function strtoupper; use function trim; /** * Provides the base implementation for the lowest versions of supported MySQL-like database platforms. */ abstract class AbstractMySQLPlatform extends AbstractPlatform { public const LENGTH_LIMIT_TINYTEXT = 255; public const LENGTH_LIMIT_TEXT = 65535; public const LENGTH_LIMIT_MEDIUMTEXT = 16777215; public const LENGTH_LIMIT_TINYBLOB = 255; public const LENGTH_LIMIT_BLOB = 65535; public const LENGTH_LIMIT_MEDIUMBLOB = 16777215; /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit !== null) { $query .= sprintf(' LIMIT %d', $limit); if ($offset > 0) { $query .= sprintf(' OFFSET %d', $offset); } } elseif ($offset > 0) { // 2^64-1 is the maximum of unsigned BIGINT, the biggest limit possible $query .= sprintf(' LIMIT 18446744073709551615 OFFSET %d', $offset); } return $query; } /** * {@inheritDoc} * * @deprecated Use {@see quoteIdentifier()} to quote identifiers instead. */ public function getIdentifierQuoteCharacter() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5388', 'AbstractMySQLPlatform::getIdentifierQuoteCharacter() is deprecated. Use quoteIdentifier() instead.', ); return '`'; } /** * {@inheritDoc} */ public function getRegexpExpression() { return 'RLIKE'; } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'LOCATE(' . $substr . ', ' . $str . ')'; } return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')'; } /** * {@inheritDoc} */ public function getConcatExpression() { return sprintf('CONCAT(%s)', implode(', ', func_get_args())); } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { $function = $operator === '+' ? 'DATE_ADD' : 'DATE_SUB'; return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit . ')'; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')'; } public function getCurrentDatabaseExpression(): string { return 'DATABASE()'; } /** * {@inheritDoc} */ public function getLengthExpression($column) { return 'CHAR_LENGTH(' . $column . ')'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListDatabasesSQL() { return 'SHOW DATABASES'; } /** * @deprecated * * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { return 'SHOW INDEX FROM ' . $table; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} * * Two approaches to listing the table indexes. The information_schema is * preferred, because it doesn't cause problems with SQL keywords such as "order" or "table". */ public function getListTableIndexesSQL($table, $database = null) { if ($database !== null) { return 'SELECT NON_UNIQUE AS Non_Unique, INDEX_NAME AS Key_name, COLUMN_NAME AS Column_Name,' . ' SUB_PART AS Sub_Part, INDEX_TYPE AS Index_Type' . ' FROM information_schema.STATISTICS WHERE TABLE_NAME = ' . $this->quoteStringLiteral($table) . ' AND TABLE_SCHEMA = ' . $this->quoteStringLiteral($database) . ' ORDER BY SEQ_IN_INDEX ASC'; } return 'SHOW INDEX FROM ' . $table; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return 'SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = ' . $this->quoteStringLiteral($database); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { // The schema name is passed multiple times as a literal in the WHERE clause instead of using a JOIN condition // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions // caused by https://bugs.mysql.com/bug.php?id=81347 return 'SELECT k.CONSTRAINT_NAME, k.COLUMN_NAME, k.REFERENCED_TABLE_NAME, ' . 'k.REFERENCED_COLUMN_NAME /*!50116 , c.UPDATE_RULE, c.DELETE_RULE */ ' . 'FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k /*!50116 ' . 'INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS c ON ' . 'c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND ' . 'c.TABLE_NAME = k.TABLE_NAME */ ' . 'WHERE k.TABLE_NAME = ' . $this->quoteStringLiteral($table) . ' ' . 'AND k.TABLE_SCHEMA = ' . $this->getDatabaseNameSQL($database) . ' /*!50116 ' . 'AND c.CONSTRAINT_SCHEMA = ' . $this->getDatabaseNameSQL($database) . ' */' . 'ORDER BY k.ORDINAL_POSITION'; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default string column length on MySQL is deprecated' . ', specify the length explicitly.', ); } return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length on MySQL is deprecated' . ', specify the length explicitly.', ); } return $fixed ? 'BINARY(' . ($length > 0 ? $length : 255) . ')' : 'VARBINARY(' . ($length > 0 ? $length : 255) . ')'; } /** * Gets the SQL snippet used to declare a CLOB column type. * TINYTEXT : 2 ^ 8 - 1 = 255 * TEXT : 2 ^ 16 - 1 = 65535 * MEDIUMTEXT : 2 ^ 24 - 1 = 16777215 * LONGTEXT : 2 ^ 32 - 1 = 4294967295 * * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { if (! empty($column['length']) && is_numeric($column['length'])) { $length = $column['length']; if ($length <= static::LENGTH_LIMIT_TINYTEXT) { return 'TINYTEXT'; } if ($length <= static::LENGTH_LIMIT_TEXT) { return 'TEXT'; } if ($length <= static::LENGTH_LIMIT_MEDIUMTEXT) { return 'MEDIUMTEXT'; } } return 'LONGTEXT'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { if (isset($column['version']) && $column['version'] === true) { return 'TIMESTAMP'; } return 'DATETIME'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME'; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'TINYINT(1)'; } /** * {@inheritDoc} * * @deprecated * * MySQL prefers "autoincrement" identity columns since sequences can only * be emulated with a table. */ public function prefersIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/1519', 'AbstractMySQLPlatform::prefersIdentityColumns() is deprecated.', ); return true; } /** * {@inheritDoc} * * MySQL supports this through AUTO_INCREMENT columns. */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsInlineColumnComments() { return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsColumnCollation() { return true; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { return "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { return 'SELECT COLUMN_NAME AS Field, COLUMN_TYPE AS Type, IS_NULLABLE AS `Null`, ' . 'COLUMN_KEY AS `Key`, COLUMN_DEFAULT AS `Default`, EXTRA AS Extra, COLUMN_COMMENT AS Comment, ' . 'CHARACTER_SET_NAME AS CharacterSet, COLLATION_NAME AS Collation ' . 'FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ' . $this->getDatabaseNameSQL($database) . ' AND TABLE_NAME = ' . $this->quoteStringLiteral($table) . ' ORDER BY ORDINAL_POSITION ASC'; } /** * @deprecated Use {@see getColumnTypeSQLSnippet()} instead. * * The SQL snippets required to elucidate a column type * * Returns an array of the form [column type SELECT snippet, additional JOIN statement snippet] * * @return array{string, string} */ public function getColumnTypeSQLSnippets(string $tableAlias = 'c'): array { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6202', 'AbstractMySQLPlatform::getColumnTypeSQLSnippets() is deprecated. ' . 'Use AbstractMySQLPlatform::getColumnTypeSQLSnippet() instead.', ); return [$this->getColumnTypeSQLSnippet(...func_get_args()), '']; } /** * The SQL snippet required to elucidate a column type * * Returns a column type SELECT snippet string */ public function getColumnTypeSQLSnippet(string $tableAlias = 'c', ?string $databaseName = null): string { return $tableAlias . '.COLUMN_TYPE'; } /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ public function getListTableMetadataSQL(string $table, ?string $database = null): string { return sprintf( <<<'SQL' SELECT t.ENGINE, t.AUTO_INCREMENT, t.TABLE_COMMENT, t.CREATE_OPTIONS, t.TABLE_COLLATION, ccsa.CHARACTER_SET_NAME FROM information_schema.TABLES t INNER JOIN information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` ccsa ON ccsa.COLLATION_NAME = t.TABLE_COLLATION WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = %s AND TABLE_NAME = %s SQL , $this->getDatabaseNameSQL($database), $this->quoteStringLiteral($table), ); } /** * {@inheritDoc} */ public function getCreateTablesSQL(array $tables): array { $sql = []; foreach ($tables as $table) { $sql = array_merge($sql, $this->getCreateTableWithoutForeignKeysSQL($table)); } foreach ($tables as $table) { if (! $table->hasOption('engine') || $this->engineSupportsForeignKeys($table->getOption('engine'))) { foreach ($table->getForeignKeys() as $foreignKey) { $sql[] = $this->getCreateForeignKeySQL( $foreignKey, $table->getQuotedName($this), ); } } elseif (count($table->getForeignKeys()) > 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5414', 'Relying on the DBAL not generating DDL for foreign keys on MySQL engines' . ' other than InnoDB is deprecated.' . ' Define foreign key constraints only if they are necessary.', ); } } return $sql; } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $constraintName => $definition) { $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition); } } // add all indexes if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $indexName => $definition) { $queryFields .= ', ' . $this->getIndexDeclarationSQL($indexName, $definition); } } // attach all primary keys if (isset($options['primary']) && ! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } $query = 'CREATE '; if (! empty($options['temporary'])) { $query .= 'TEMPORARY '; } $query .= 'TABLE ' . $name . ' (' . $queryFields . ') '; $query .= $this->buildTableOptions($options); $query .= $this->buildPartitionOptions($options); $sql = [$query]; // Propagate foreign key constraints only for InnoDB. if (isset($options['foreignKeys'])) { if (! isset($options['engine']) || $this->engineSupportsForeignKeys($options['engine'])) { foreach ($options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } elseif (count($options['foreignKeys']) > 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5414', 'Relying on the DBAL not generating DDL for foreign keys on MySQL engines' . ' other than InnoDB is deprecated.' . ' Define foreign key constraints only if they are necessary.', ); } } return $sql; } public function createSelectSQLBuilder(): SelectSQLBuilder { return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', null); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getDefaultValueDeclarationSQL($column) { // Unset the default value if the given column definition does not allow default values. if ($column['type'] instanceof TextType || $column['type'] instanceof BlobType) { $column['default'] = null; } return parent::getDefaultValueDeclarationSQL($column); } /** * Build SQL for table options * * @param mixed[] $options */ private function buildTableOptions(array $options): string { if (isset($options['table_options'])) { return $options['table_options']; } $tableOptions = []; // Charset if (! isset($options['charset'])) { $options['charset'] = 'utf8'; } $tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']); if (isset($options['collate'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5214', 'The "collate" option is deprecated in favor of "collation" and will be removed in 4.0.', ); $options['collation'] = $options['collate']; } // Collation if (! isset($options['collation'])) { $options['collation'] = $options['charset'] . '_unicode_ci'; } $tableOptions[] = $this->getColumnCollationDeclarationSQL($options['collation']); // Engine if (! isset($options['engine'])) { $options['engine'] = 'InnoDB'; } $tableOptions[] = sprintf('ENGINE = %s', $options['engine']); // Auto increment if (isset($options['auto_increment'])) { $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']); } // Comment if (isset($options['comment'])) { $tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($options['comment'])); } // Row format if (isset($options['row_format'])) { $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']); } return implode(' ', $tableOptions); } /** * Build SQL for partition options. * * @param mixed[] $options */ private function buildPartitionOptions(array $options): string { return isset($options['partition_options']) ? ' ' . $options['partition_options'] : ''; } private function engineSupportsForeignKeys(string $engine): bool { return strcasecmp(trim($engine), 'InnoDB') === 0; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $columnSql = []; $queryParts = []; $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of SQL that renames a table using %s is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $queryParts[] = 'RENAME TO ' . $newName->getQuotedName($this); } foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columnProperties = array_merge($column->toArray(), [ 'comment' => $this->getColumnComment($column), ]); $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL( $column->getQuotedName($this), $columnProperties, ); } foreach ($diff->getDroppedColumns() as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $queryParts[] = 'DROP ' . $column->getQuotedName($this); } foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $newColumn = $columnDiff->getNewColumn(); $newColumnProperties = array_merge($newColumn->toArray(), [ 'comment' => $this->getColumnComment($newColumn), ]); $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); $queryParts[] = 'CHANGE ' . $oldColumn->getQuotedName($this) . ' ' . $this->getColumnDeclarationSQL($newColumn->getQuotedName($this), $newColumnProperties); } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $columnProperties = array_merge($column->toArray(), [ 'comment' => $this->getColumnComment($column), ]); $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnProperties); } $addedIndexes = $this->indexAssetsByLowerCaseName($diff->getAddedIndexes()); $modifiedIndexes = $this->indexAssetsByLowerCaseName($diff->getModifiedIndexes()); $diffModified = false; if (isset($addedIndexes['primary'])) { $keyColumns = array_unique(array_values($addedIndexes['primary']->getColumns())); $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')'; unset($addedIndexes['primary']); $diffModified = true; } elseif (isset($modifiedIndexes['primary'])) { $addedColumns = $this->indexAssetsByLowerCaseName($diff->getAddedColumns()); // Necessary in case the new primary key includes a new auto_increment column foreach ($modifiedIndexes['primary']->getColumns() as $columnName) { if (isset($addedColumns[$columnName]) && $addedColumns[$columnName]->getAutoincrement()) { $keyColumns = array_unique(array_values($modifiedIndexes['primary']->getColumns())); $queryParts[] = 'DROP PRIMARY KEY'; $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')'; unset($modifiedIndexes['primary']); $diffModified = true; break; } } } if ($diffModified) { $diff = new TableDiff( $diff->name, $diff->getAddedColumns(), $diff->getModifiedColumns(), $diff->getDroppedColumns(), array_values($addedIndexes), array_values($modifiedIndexes), $diff->getDroppedIndexes(), $diff->getOldTable(), $diff->getAddedForeignKeys(), $diff->getModifiedForeignKeys(), $diff->getDroppedForeignKeys(), $diff->getRenamedColumns(), $diff->getRenamedIndexes(), ); } $sql = []; $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { if (count($queryParts) > 0) { $sql[] = 'ALTER TABLE ' . ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this) . ' ' . implode(', ', $queryParts); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff), ); } return array_merge($sql, $tableSql, $columnSql); } /** * {@inheritDoc} */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { $sql = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); foreach ($diff->getModifiedIndexes() as $changedIndex) { $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $changedIndex)); } foreach ($diff->getDroppedIndexes() as $droppedIndex) { $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $droppedIndex)); foreach ($diff->getAddedIndexes() as $addedIndex) { if ($droppedIndex->getColumns() !== $addedIndex->getColumns()) { continue; } $indexClause = 'INDEX ' . $addedIndex->getName(); if ($addedIndex->isPrimary()) { $indexClause = 'PRIMARY KEY'; } elseif ($addedIndex->isUnique()) { $indexClause = 'UNIQUE INDEX ' . $addedIndex->getName(); } $query = 'ALTER TABLE ' . $tableNameSQL . ' DROP INDEX ' . $droppedIndex->getName() . ', '; $query .= 'ADD ' . $indexClause; $query .= ' (' . $this->getIndexFieldDeclarationListSQL($addedIndex) . ')'; $sql[] = $query; $diff->unsetAddedIndex($addedIndex); $diff->unsetDroppedIndex($droppedIndex); break; } } $engine = 'INNODB'; $table = $diff->getOldTable(); if ($table !== null && $table->hasOption('engine')) { $engine = strtoupper(trim($table->getOption('engine'))); } // Suppress foreign key constraint propagation on non-supporting engines. if ($engine !== 'INNODB') { $diff->addedForeignKeys = []; $diff->changedForeignKeys = []; $diff->removedForeignKeys = []; } $sql = array_merge( $sql, $this->getPreAlterTableAlterIndexForeignKeySQL($diff), parent::getPreAlterTableIndexForeignKeySQL($diff), $this->getPreAlterTableRenameIndexForeignKeySQL($diff), ); return $sql; } /** * @return string[] * * @throws Exception */ private function getPreAlterTableAlterPrimaryKeySQL(TableDiff $diff, Index $index): array { if (! $index->isPrimary()) { return []; } $table = $diff->getOldTable(); if ($table === null) { return []; } $sql = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); // Dropping primary keys requires to unset autoincrement attribute on the particular column first. foreach ($index->getColumns() as $columnName) { if (! $table->hasColumn($columnName)) { continue; } $column = $table->getColumn($columnName); if ($column->getAutoincrement() !== true) { continue; } $column->setAutoincrement(false); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); // original autoincrement information might be needed later on by other parts of the table alteration $column->setAutoincrement(true); } return $sql; } /** * @param TableDiff $diff The table diff to gather the SQL for. * * @return string[] * * @throws Exception */ private function getPreAlterTableAlterIndexForeignKeySQL(TableDiff $diff): array { $table = $diff->getOldTable(); if ($table === null) { return []; } $primaryKey = $table->getPrimaryKey(); if ($primaryKey === null) { return []; } $primaryKeyColumns = []; foreach ($primaryKey->getColumns() as $columnName) { if (! $table->hasColumn($columnName)) { continue; } $primaryKeyColumns[] = $table->getColumn($columnName); } if (count($primaryKeyColumns) === 0) { return []; } $sql = []; $tableNameSQL = $table->getQuotedName($this); foreach ($diff->getModifiedIndexes() as $changedIndex) { // Changed primary key if (! $changedIndex->isPrimary()) { continue; } foreach ($primaryKeyColumns as $column) { // Check if an autoincrement column was dropped from the primary key. if (! $column->getAutoincrement() || in_array($column->getName(), $changedIndex->getColumns(), true)) { continue; } // The autoincrement attribute needs to be removed from the dropped column // before we can drop and recreate the primary key. $column->setAutoincrement(false); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); // Restore the autoincrement attribute as it might be needed later on // by other parts of the table alteration. $column->setAutoincrement(true); } } return $sql; } /** * @param TableDiff $diff The table diff to gather the SQL for. * * @return string[] */ protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { $sql = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { if (in_array($foreignKey, $diff->getModifiedForeignKeys(), true)) { continue; } $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL); } return $sql; } /** * Returns the remaining foreign key constraints that require one of the renamed indexes. * * "Remaining" here refers to the diff between the foreign keys currently defined in the associated * table and the foreign keys to be removed. * * @param TableDiff $diff The table diff to evaluate. * * @return ForeignKeyConstraint[] */ private function getRemainingForeignKeyConstraintsRequiringRenamedIndexes(TableDiff $diff): array { if (count($diff->getRenamedIndexes()) === 0) { return []; } $table = $diff->getOldTable(); if ($table === null) { return []; } $foreignKeys = []; /** @var ForeignKeyConstraint[] $remainingForeignKeys */ $remainingForeignKeys = array_diff_key( $table->getForeignKeys(), $diff->getDroppedForeignKeys(), ); foreach ($remainingForeignKeys as $foreignKey) { foreach ($diff->getRenamedIndexes() as $index) { if ($foreignKey->intersectsIndexColumns($index)) { $foreignKeys[] = $foreignKey; break; } } } return $foreignKeys; } /** * {@inheritDoc} */ protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) { return array_merge( parent::getPostAlterTableIndexForeignKeySQL($diff), $this->getPostAlterTableRenameIndexForeignKeySQL($diff), ); } /** * @param TableDiff $diff The table diff to gather the SQL for. * * @return string[] */ protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { $sql = []; $newName = $diff->getNewName(); if ($newName !== false) { $tableNameSQL = $newName->getQuotedName($this); } else { $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); } foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { if (in_array($foreignKey, $diff->getModifiedForeignKeys(), true)) { continue; } $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL); } return $sql; } /** * {@inheritDoc} */ protected function getCreateIndexSQLFlags(Index $index) { $type = ''; if ($index->isUnique()) { $type .= 'UNIQUE '; } elseif ($index->hasFlag('fulltext')) { $type .= 'FULLTEXT '; } elseif ($index->hasFlag('spatial')) { $type .= 'SPATIAL '; } return $type; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getFloatDeclarationSQL(array $column) { return 'DOUBLE PRECISION' . $this->getUnsignedDeclaration($column); } /** * {@inheritDoc} */ public function getDecimalTypeDeclarationSQL(array $column) { return parent::getDecimalTypeDeclarationSQL($column) . $this->getUnsignedDeclaration($column); } /** * Get unsigned declaration for a column. * * @param mixed[] $columnDef */ private function getUnsignedDeclaration(array $columnDef): string { return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : ''; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { $autoinc = ''; if (! empty($column['autoincrement'])) { $autoinc = ' AUTO_INCREMENT'; } return $this->getUnsignedDeclaration($column) . $autoinc; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getColumnCharsetDeclarationSQL($charset) { return 'CHARACTER SET ' . $charset; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = ''; if ($foreignKey->hasOption('match')) { $query .= ' MATCH ' . $foreignKey->getOption('match'); } $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); return $query; } /** * {@inheritDoc} */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $indexName = $index->getQuotedName($this); } elseif (is_string($index)) { $indexName = $index; } else { throw new InvalidArgumentException( __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.', ); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } elseif (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', ); } if ($index instanceof Index && $index->isPrimary()) { // MySQL primary keys are always named "PRIMARY", // so we cannot use them in statements because of them being keyword. return $this->getDropPrimaryKeySQL($table); } return 'DROP INDEX ' . $indexName . ' ON ' . $table; } /** * @param string $table * * @return string */ protected function getDropPrimaryKeySQL($table) { return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'; } /** * The `ALTER TABLE ... DROP CONSTRAINT` syntax is only available as of MySQL 8.0.19. * * @link https://dev.mysql.com/doc/refman/8.0/en/alter-table.html */ public function getDropUniqueConstraintSQL(string $name, string $tableName): string { return $this->getDropIndexSQL($name, $tableName); } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4749', 'AbstractMySQLPlatform::getName() is deprecated. Identify platforms by their class.', ); return 'mysql'; } /** * {@inheritDoc} */ public function getReadLockSQL() { return 'LOCK IN SHARE MODE'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'bigint' => Types::BIGINT, 'binary' => Types::BINARY, 'blob' => Types::BLOB, 'char' => Types::STRING, 'date' => Types::DATE_MUTABLE, 'datetime' => Types::DATETIME_MUTABLE, 'decimal' => Types::DECIMAL, 'double' => Types::FLOAT, 'float' => Types::FLOAT, 'int' => Types::INTEGER, 'integer' => Types::INTEGER, 'longblob' => Types::BLOB, 'longtext' => Types::TEXT, 'mediumblob' => Types::BLOB, 'mediumint' => Types::INTEGER, 'mediumtext' => Types::TEXT, 'numeric' => Types::DECIMAL, 'real' => Types::FLOAT, 'set' => Types::SIMPLE_ARRAY, 'smallint' => Types::SMALLINT, 'string' => Types::STRING, 'text' => Types::TEXT, 'time' => Types::TIME_MUTABLE, 'timestamp' => Types::DATETIME_MUTABLE, 'tinyblob' => Types::BLOB, 'tinyint' => Types::BOOLEAN, 'tinytext' => Types::TEXT, 'varbinary' => Types::BINARY, 'varchar' => Types::STRING, 'year' => Types::DATE_MUTABLE, ]; } /** * {@inheritDoc} * * @deprecated */ public function getVarcharMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'AbstractMySQLPlatform::getVarcharMaxLength() is deprecated.', ); return 65535; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'AbstractMySQLPlatform::getBinaryMaxLength() is deprecated.', ); return 65535; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'AbstractMySQLPlatform::getReservedKeywordsClass() is deprecated,' . ' use AbstractMySQLPlatform::createReservedKeywordsList() instead.', ); return Keywords\MySQLKeywords::class; } /** * {@inheritDoc} * * MySQL commits a transaction implicitly when DROP TABLE is executed, however not * if DROP TEMPORARY TABLE is executed. */ public function getDropTemporaryTableSQL($table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } elseif (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', ); } return 'DROP TEMPORARY TABLE ' . $table; } /** * Gets the SQL Snippet used to declare a BLOB column type. * TINYBLOB : 2 ^ 8 - 1 = 255 * BLOB : 2 ^ 16 - 1 = 65535 * MEDIUMBLOB : 2 ^ 24 - 1 = 16777215 * LONGBLOB : 2 ^ 32 - 1 = 4294967295 * * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { if (! empty($column['length']) && is_numeric($column['length'])) { $length = $column['length']; if ($length <= static::LENGTH_LIMIT_TINYBLOB) { return 'TINYBLOB'; } if ($length <= static::LENGTH_LIMIT_BLOB) { return 'BLOB'; } if ($length <= static::LENGTH_LIMIT_MEDIUMBLOB) { return 'MEDIUMBLOB'; } } return 'LONGBLOB'; } /** * {@inheritDoc} */ public function quoteStringLiteral($str) { $str = str_replace('\\', '\\\\', $str); // MySQL requires backslashes to be escaped return parent::quoteStringLiteral($str); } /** * {@inheritDoc} */ public function getDefaultTransactionIsolationLevel() { return TransactionIsolationLevel::REPEATABLE_READ; } public function supportsColumnLengthIndexes(): bool { return true; } /** @deprecated Will be removed without replacement. */ protected function getDatabaseNameSQL(?string $databaseName): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6215', '%s is deprecated without replacement.', __METHOD__, ); if ($databaseName !== null) { return $this->quoteStringLiteral($databaseName); } return $this->getCurrentDatabaseExpression(); } public function createSchemaManager(Connection $connection): MySQLSchemaManager { return new MySQLSchemaManager($connection, $this); } /** * @param list<T> $assets * * @return array<string,T> * * @template T of AbstractAsset */ private function indexAssetsByLowerCaseName(array $assets): array { $result = []; foreach ($assets as $asset) { $result[strtolower($asset->getName())] = $asset; } return $result; } public function fetchTableOptionsByTable(bool $includeTableName): string { $sql = <<<'SQL' SELECT t.TABLE_NAME, t.ENGINE, t.AUTO_INCREMENT, t.TABLE_COMMENT, t.CREATE_OPTIONS, t.TABLE_COLLATION, ccsa.CHARACTER_SET_NAME FROM information_schema.TABLES t INNER JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY ccsa ON ccsa.COLLATION_NAME = t.TABLE_COLLATION SQL; $conditions = ['t.TABLE_SCHEMA = ?']; if ($includeTableName) { $conditions[] = 't.TABLE_NAME = ?'; } $conditions[] = "t.TABLE_TYPE = 'BASE TABLE'"; return $sql . ' WHERE ' . implode(' AND ', $conditions); } } Platforms/MariaDb1027Platform.php 0000755 00000000547 00000000000 0012514 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; /** * Provides the behavior, features and SQL dialect of the MariaDB 10.2 database platform. * * @deprecated This class will be merged with {@see MariaDBPlatform} in 4.0 because support for MariaDB * releases prior to 10.4.3 will be dropped. */ class MariaDb1027Platform extends MariaDBPlatform { } Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php 0000755 00000004163 00000000000 0017724 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\SQLServer\SQL\Builder; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode; use Doctrine\DBAL\Query\SelectQuery; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use function count; use function implode; final class SQLServerSelectSQLBuilder implements SelectSQLBuilder { private SQLServerPlatform $platform; /** @internal The SQL builder should be instantiated only by database platforms. */ public function __construct(SQLServerPlatform $platform) { $this->platform = $platform; } public function buildSQL(SelectQuery $query): string { $parts = ['SELECT']; if ($query->isDistinct()) { $parts[] = 'DISTINCT'; } $parts[] = implode(', ', $query->getColumns()); $from = $query->getFrom(); if (count($from) > 0) { $parts[] = 'FROM ' . implode(', ', $from); } $forUpdate = $query->getForUpdate(); if ($forUpdate !== null) { $with = ['UPDLOCK', 'ROWLOCK']; if ($forUpdate->getConflictResolutionMode() === ConflictResolutionMode::SKIP_LOCKED) { $with[] = 'READPAST'; } $parts[] = 'WITH (' . implode(', ', $with) . ')'; } $where = $query->getWhere(); if ($where !== null) { $parts[] = 'WHERE ' . $where; } $groupBy = $query->getGroupBy(); if (count($groupBy) > 0) { $parts[] = 'GROUP BY ' . implode(', ', $groupBy); } $having = $query->getHaving(); if ($having !== null) { $parts[] = 'HAVING ' . $having; } $orderBy = $query->getOrderBy(); if (count($orderBy) > 0) { $parts[] = 'ORDER BY ' . implode(', ', $orderBy); } $sql = implode(' ', $parts); $limit = $query->getLimit(); if ($limit->isDefined()) { $sql = $this->platform->modifyLimitQuery($sql, $limit->getMaxResults(), $limit->getFirstResult()); } return $sql; } } Platforms/SQLServer/Comparator.php 0000755 00000003277 00000000000 0013136 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\SQLServer; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Schema\Comparator as BaseComparator; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; /** * Compares schemas in the context of SQL Server platform. * * @link https://docs.microsoft.com/en-us/sql/t-sql/statements/collations?view=sql-server-ver15 */ class Comparator extends BaseComparator { private string $databaseCollation; /** @internal The comparator can be only instantiated by a schema manager. */ public function __construct(SQLServerPlatform $platform, string $databaseCollation) { parent::__construct($platform); $this->databaseCollation = $databaseCollation; } public function compareTables(Table $fromTable, Table $toTable): TableDiff { return parent::compareTables( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } /** * {@inheritDoc} */ public function diffTable(Table $fromTable, Table $toTable) { return parent::diffTable( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } private function normalizeColumns(Table $table): Table { $table = clone $table; foreach ($table->getColumns() as $column) { $options = $column->getPlatformOptions(); if (! isset($options['collation']) || $options['collation'] !== $this->databaseCollation) { continue; } unset($options['collation']); $column->setPlatformOptions($options); } return $table; } } Platforms/DB2Platform.php 0000755 00000075274 00000000000 0011263 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\DB2SchemaManager; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use function array_merge; use function count; use function current; use function explode; use function func_get_arg; use function func_num_args; use function implode; use function sprintf; use function strpos; /** * Provides the behavior, features and SQL dialect of the IBM DB2 database platform of the oldest supported version. */ class DB2Platform extends AbstractPlatform { /** @see https://www.ibm.com/docs/en/db2/11.5?topic=views-syscatcolumns */ private const SYSCAT_COLUMNS_GENERATED_DEFAULT = 'D'; /** @see https://www.ibm.com/docs/en/db2/11.5?topic=views-syscatindexes */ private const SYSCAT_INDEXES_UNIQUERULE_PERMITS_DUPLICATES = 'D'; private const SYSCAT_INDEXES_UNIQUERULE_IMPLEMENTS_PRIMARY_KEY = 'P'; /** @see https://www.ibm.com/docs/en/db2/11.5?topic=views-syscattabconst */ private const SYSCAT_TABCONST_TYPE_PRIMARY_KEY = 'P'; /** @see https://www.ibm.com/docs/en/db2/11.5?topic=views-syscatreferences */ private const SYSCAT_REFERENCES_UPDATERULE_RESTRICT = 'R'; private const SYSCAT_REFERENCES_DELETERULE_CASCADE = 'C'; private const SYSCAT_REFERENCES_DELETERULE_SET_NULL = 'N'; private const SYSCAT_REFERENCES_DELETERULE_RESTRICT = 'R'; /** @see https://www.ibm.com/docs/en/db2-for-zos/11?topic=tables-systables */ private const SYSIBM_SYSTABLES_TYPE_TABLE = 'T'; /** * {@inheritDoc} * * @deprecated */ public function getCharMaxLength(): int { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', '%s() is deprecated.', __METHOD__, ); return 254; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', '%s() is deprecated.', __METHOD__, ); return 32704; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryDefaultLength() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length is deprecated, specify the length explicitly.', ); return 1; } /** * {@inheritDoc} */ public function getVarcharTypeDeclarationSQL(array $column) { // for IBM DB2, the CHAR max length is less than VARCHAR default length if (! isset($column['length']) && ! empty($column['fixed'])) { $column['length'] = $this->getCharMaxLength(); } return parent::getVarcharTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { // todo blob(n) with $column['length']; return 'BLOB(1M)'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'bigint' => Types::BIGINT, 'binary' => Types::BINARY, 'blob' => Types::BLOB, 'character' => Types::STRING, 'clob' => Types::TEXT, 'date' => Types::DATE_MUTABLE, 'decimal' => Types::DECIMAL, 'double' => Types::FLOAT, 'integer' => Types::INTEGER, 'real' => Types::FLOAT, 'smallint' => Types::SMALLINT, 'time' => Types::TIME_MUTABLE, 'timestamp' => Types::DATETIME_MUTABLE, 'varbinary' => Types::BINARY, 'varchar' => Types::STRING, ]; } /** * {@inheritDoc} */ public function isCommentedDoctrineType(Type $doctrineType) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5058', '%s() is deprecated and will be removed in Doctrine DBAL 4.0. Use Type::requiresSQLCommentHint() instead.', __METHOD__, ); if ($doctrineType->getName() === Types::BOOLEAN) { // We require a commented boolean type in order to distinguish between boolean and smallint // as both (have to) map to the same native type. return true; } return parent::isCommentedDoctrineType($doctrineType); } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default string column length on IBM DB2 is deprecated' . ', specify the length explicitly.', ); } return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(254)') : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length on IBM DB2 is deprecated' . ', specify the length explicitly.', ); } return $this->getVarcharTypeDeclarationSQLSnippet($length, $fixed) . ' FOR BIT DATA'; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { // todo clob(n) with $column['length']; return 'CLOB(1M)'; } /** * {@inheritDoc} */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4749', '%s() is deprecated. Identify platforms by their class.', __METHOD__, ); return 'db2'; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'SMALLINT'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { $autoinc = ''; if (! empty($column['autoincrement'])) { $autoinc = ' GENERATED BY DEFAULT AS IDENTITY'; } return $autoinc; } /** * {@inheritDoc} */ public function getBitAndComparisonExpression($value1, $value2) { return 'BITAND(' . $value1 . ', ' . $value2 . ')'; } /** * {@inheritDoc} */ public function getBitOrComparisonExpression($value1, $value2) { return 'BITOR(' . $value1 . ', ' . $value2 . ')'; } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { switch ($unit) { case DateIntervalUnit::WEEK: $interval = $this->multiplyInterval((string) $interval, 7); $unit = DateIntervalUnit::DAY; break; case DateIntervalUnit::QUARTER: $interval = $this->multiplyInterval((string) $interval, 3); $unit = DateIntervalUnit::MONTH; break; } return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { if (isset($column['version']) && $column['version'] === true) { return 'TIMESTAMP(0) WITH DEFAULT'; } return 'TIMESTAMP(0)'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME'; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this) . ' IMMEDIATE'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * This code fragment is originally from the Zend_Db_Adapter_Db2 class, but has been edited. * * @param string $table * @param string $database * * @return string */ public function getListTableColumnsSQL($table, $database = null) { $table = $this->quoteStringLiteral($table); // We do the funky subquery and join syscat.columns.default this crazy way because // as of db2 v10, the column is CLOB(64k) and the distinct operator won't allow a CLOB, // it wants shorter stuff like a varchar. return " SELECT cols.default, subq.* FROM ( SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno, c.typename, c.codepage, c.nulls, c.length, c.scale, c.identity, tc.type AS tabconsttype, c.remarks AS comment, k.colseq, CASE WHEN c.generated = '" . self::SYSCAT_COLUMNS_GENERATED_DEFAULT . "' THEN 1 ELSE 0 END AS autoincrement FROM syscat.columns c LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc ON (k.tabschema = tc.tabschema AND k.tabname = tc.tabname AND tc.type = '" . self::SYSCAT_TABCONST_TYPE_PRIMARY_KEY . "')) ON (c.tabschema = k.tabschema AND c.tabname = k.tabname AND c.colname = k.colname) WHERE UPPER(c.tabname) = UPPER(" . $table . ') ORDER BY c.colno ) subq JOIN syscat.columns cols ON subq.tabschema = cols.tabschema AND subq.tabname = cols.tabname AND subq.colno = cols.colno ORDER BY subq.colno '; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = '" . self::SYSIBM_SYSTABLES_TYPE_TABLE . "'" . ' AND CREATOR = CURRENT_USER'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return 'SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) { $table = $this->quoteStringLiteral($table); return "SELECT idx.INDNAME AS key_name, idxcol.COLNAME AS column_name, CASE WHEN idx.UNIQUERULE = '" . self::SYSCAT_INDEXES_UNIQUERULE_IMPLEMENTS_PRIMARY_KEY . "' THEN 1 ELSE 0 END AS primary, CASE WHEN idx.UNIQUERULE = '" . self::SYSCAT_INDEXES_UNIQUERULE_PERMITS_DUPLICATES . "' THEN 1 ELSE 0 END AS non_unique FROM SYSCAT.INDEXES AS idx JOIN SYSCAT.INDEXCOLUSE AS idxcol ON idx.INDSCHEMA = idxcol.INDSCHEMA AND idx.INDNAME = idxcol.INDNAME WHERE idx.TABNAME = UPPER(" . $table . ') ORDER BY idxcol.COLSEQ ASC'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableForeignKeysSQL($table) { $table = $this->quoteStringLiteral($table); return "SELECT fkcol.COLNAME AS local_column, fk.REFTABNAME AS foreign_table, pkcol.COLNAME AS foreign_column, fk.CONSTNAME AS index_name, CASE WHEN fk.UPDATERULE = '" . self::SYSCAT_REFERENCES_UPDATERULE_RESTRICT . "' THEN 'RESTRICT' ELSE NULL END AS on_update, CASE WHEN fk.DELETERULE = '" . self::SYSCAT_REFERENCES_DELETERULE_CASCADE . "' THEN 'CASCADE' WHEN fk.DELETERULE = '" . self::SYSCAT_REFERENCES_DELETERULE_SET_NULL . "' THEN 'SET NULL' WHEN fk.DELETERULE = '" . self::SYSCAT_REFERENCES_DELETERULE_RESTRICT . "' THEN 'RESTRICT' ELSE NULL END AS on_delete FROM SYSCAT.REFERENCES AS fk JOIN SYSCAT.KEYCOLUSE AS fkcol ON fk.CONSTNAME = fkcol.CONSTNAME AND fk.TABSCHEMA = fkcol.TABSCHEMA AND fk.TABNAME = fkcol.TABNAME JOIN SYSCAT.KEYCOLUSE AS pkcol ON fk.REFKEYNAME = pkcol.CONSTNAME AND fk.REFTABSCHEMA = pkcol.TABSCHEMA AND fk.REFTABNAME = pkcol.TABNAME WHERE fk.TABNAME = UPPER(" . $table . ') ORDER BY fkcol.COLSEQ ASC'; } /** * {@inheritDoc} * * @deprecated */ public function supportsCreateDropDatabase() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s() is deprecated.', __METHOD__, ); return false; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsCommentOnStatement() { return true; } /** * {@inheritDoc} */ public function getCurrentDateSQL() { return 'CURRENT DATE'; } /** * {@inheritDoc} */ public function getCurrentTimeSQL() { return 'CURRENT TIME'; } /** * {@inheritDoc} */ public function getCurrentTimestampSQL() { return 'CURRENT TIMESTAMP'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getIndexDeclarationSQL($name, Index $index) { // Index declaration in statements like CREATE TABLE is not supported. throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $indexes = []; if (isset($options['indexes'])) { $indexes = $options['indexes']; } $options['indexes'] = []; $sqls = parent::_getCreateTableSQL($name, $columns, $options); foreach ($indexes as $definition) { $sqls[] = $this->getCreateIndexSQL($definition, $name); } return $sqls; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = []; $columnSql = []; $commentsSQL = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); $queryParts = []; foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columnDef = $column->toArray(); $queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); // Adding non-nullable columns to a table requires a default value to be specified. if ( ! empty($columnDef['notnull']) && ! isset($columnDef['default']) && empty($columnDef['autoincrement']) ) { $queryPart .= ' WITH DEFAULT'; } $queryParts[] = $queryPart; $comment = $this->getColumnComment($column); if ($comment === null || $comment === '') { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $column->getQuotedName($this), $comment, ); } foreach ($diff->getDroppedColumns() as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); } foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } if ($columnDiff->hasCommentChanged()) { $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $columnDiff->getNewColumn()->getQuotedName($this), $this->getColumnComment($columnDiff->getNewColumn()), ); } $this->gatherAlterColumnSQL( $tableNameSQL, $columnDiff, $sql, $queryParts, ); } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $queryParts[] = 'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); } $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { if (count($queryParts) > 0) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . implode(' ', $queryParts); } // Some table alteration operations require a table reorganization. if (count($diff->getDroppedColumns()) > 0 || count($diff->getModifiedColumns()) > 0) { $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $tableNameSQL . "')"; } $sql = array_merge($sql, $commentsSQL); $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of "rename table" SQL using %s() is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $sql[] = sprintf( 'RENAME TABLE %s TO %s', $tableNameSQL, $newName->getQuotedName($this), ); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff), ); } return array_merge($sql, $tableSql, $columnSql); } /** * {@inheritDoc} */ public function getRenameTableSQL(string $oldName, string $newName): array { return [ sprintf('RENAME TABLE %s TO %s', $oldName, $newName), ]; } /** * Gathers the table alteration SQL for a given column diff. * * @param string $table The table to gather the SQL for. * @param ColumnDiff $columnDiff The column diff to evaluate. * @param string[] $sql The sequence of table alteration statements to fill. * @param mixed[] $queryParts The sequence of column alteration clauses to fill. */ private function gatherAlterColumnSQL( string $table, ColumnDiff $columnDiff, array &$sql, array &$queryParts ): void { $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff); if (empty($alterColumnClauses)) { return; } // If we have a single column alteration, we can append the clause to the main query. if (count($alterColumnClauses) === 1) { $queryParts[] = current($alterColumnClauses); return; } // We have multiple alterations for the same column, // so we need to trigger a complete ALTER TABLE statement // for each ALTER COLUMN clause. foreach ($alterColumnClauses as $alterColumnClause) { $sql[] = 'ALTER TABLE ' . $table . ' ' . $alterColumnClause; } } /** * Returns the ALTER COLUMN SQL clauses for altering a column described by the given column diff. * * @return string[] */ private function getAlterColumnClausesSQL(ColumnDiff $columnDiff): array { $newColumn = $columnDiff->getNewColumn()->toArray(); $alterClause = 'ALTER COLUMN ' . $columnDiff->getNewColumn()->getQuotedName($this); if ($newColumn['columnDefinition'] !== null) { return [$alterClause . ' ' . $newColumn['columnDefinition']]; } $clauses = []; if ( $columnDiff->hasTypeChanged() || $columnDiff->hasLengthChanged() || $columnDiff->hasPrecisionChanged() || $columnDiff->hasScaleChanged() || $columnDiff->hasFixedChanged() ) { $clauses[] = $alterClause . ' SET DATA TYPE ' . $newColumn['type']->getSQLDeclaration($newColumn, $this); } if ($columnDiff->hasNotNullChanged()) { $clauses[] = $newColumn['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL'; } if ($columnDiff->hasDefaultChanged()) { if (isset($newColumn['default'])) { $defaultClause = $this->getDefaultValueDeclarationSQL($newColumn); if ($defaultClause !== '') { $clauses[] = $alterClause . ' SET' . $defaultClause; } } else { $clauses[] = $alterClause . ' DROP DEFAULT'; } } return $clauses; } /** * {@inheritDoc} */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { $sql = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); foreach ($diff->getDroppedIndexes() as $droppedIndex) { foreach ($diff->getAddedIndexes() as $addedIndex) { if ($droppedIndex->getColumns() !== $addedIndex->getColumns()) { continue; } if ($droppedIndex->isPrimary()) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP PRIMARY KEY'; } elseif ($droppedIndex->isUnique()) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP UNIQUE ' . $droppedIndex->getQuotedName($this); } else { $sql[] = $this->getDropIndexSQL($droppedIndex, $tableNameSQL); } $sql[] = $this->getCreateIndexSQL($addedIndex, $tableNameSQL); $diff->unsetAddedIndex($addedIndex); $diff->unsetDroppedIndex($droppedIndex); break; } } return array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff)); } /** * {@inheritDoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { if (strpos($tableName, '.') !== false) { [$schema] = explode('.', $tableName); $oldIndexName = $schema . '.' . $oldIndexName; } return ['RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)]; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getDefaultValueDeclarationSQL($column) { if (! empty($column['autoincrement'])) { return ''; } if (! empty($column['version'])) { if ((string) $column['type'] !== 'DateTime') { $column['default'] = '1'; } } return parent::getDefaultValueDeclarationSQL($column); } /** * {@inheritDoc} */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; } /** * {@inheritDoc} */ public function getCreateTemporaryTableSnippetSQL() { return 'DECLARE GLOBAL TEMPORARY TABLE'; } /** * {@inheritDoc} */ public function getTemporaryTableName($tableName) { return 'SESSION.' . $tableName; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { $where = []; if ($offset > 0) { $where[] = sprintf('db22.DC_ROWNUM >= %d', $offset + 1); } if ($limit !== null) { $where[] = sprintf('db22.DC_ROWNUM <= %d', $offset + $limit); } if (empty($where)) { return $query; } // Todo OVER() needs ORDER BY data! return sprintf( 'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s', $query, implode(' AND ', $where), ); } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'LOCATE(' . $substr . ', ' . $str . ')'; } return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')'; } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length === null) { return 'SUBSTR(' . $string . ', ' . $start . ')'; } return 'SUBSTR(' . $string . ', ' . $start . ', ' . $length . ')'; } /** * {@inheritDoc} */ public function getLengthExpression($column) { return 'LENGTH(' . $column . ', CODEUNITS32)'; } public function getCurrentDatabaseExpression(): string { return 'CURRENT_USER'; } /** * {@inheritDoc} */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} * * @deprecated */ public function prefersIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/1519', '%s() is deprecated.', __METHOD__, ); return true; } public function createSelectSQLBuilder(): SelectSQLBuilder { return new DefaultSelectSQLBuilder($this, 'WITH RR USE AND KEEP UPDATE LOCKS', null); } /** * {@inheritDoc} * * @deprecated This API is not portable. */ public function getForUpdateSQL() { return ' WITH RR USE AND KEEP UPDATE LOCKS'; } /** * {@inheritDoc} */ public function getDummySelectSQL() { $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; return sprintf('SELECT %s FROM sysibm.sysdummy1', $expression); } /** * {@inheritDoc} * * DB2 supports savepoints, but they work semantically different than on other vendor platforms. * * TODO: We have to investigate how to get DB2 up and running with savepoints. */ public function supportsSavepoints() { return false; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', '%s() is deprecated,' . ' use %s::createReservedKeywordsList() instead.', __METHOD__, static::class, ); return Keywords\DB2Keywords::class; } /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ public function getListTableCommentsSQL(string $table): string { return sprintf( <<<'SQL' SELECT REMARKS FROM SYSIBM.SYSTABLES WHERE NAME = UPPER( %s ) SQL , $this->quoteStringLiteral($table), ); } public function createSchemaManager(Connection $connection): DB2SchemaManager { return new DB2SchemaManager($connection, $this); } } Platforms/MariaDb1043Platform.php 0000755 00000011706 00000000000 0012511 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Types\JsonType; use Doctrine\Deprecations\Deprecation; use function sprintf; /** * Provides the behavior, features and SQL dialect of the MariaDB 10.4 database platform. * * Extend deprecated MariaDb1027Platform to ensure correct functions used in MySQLSchemaManager which * tests for MariaDb1027Platform not MariaDBPlatform. * * @deprecated This class will be merged with {@see MariaDBPlatform} in 4.0 because support for MariaDB * releases prior to 10.4.3 will be dropped. */ class MariaDb1043Platform extends MariaDb1027Platform { /** * Use JSON rather than LONGTEXT for json columns. Since it is not a true native type, do not override * hasNativeJsonType() so the DC2Type comment will still be set. * * {@inheritDoc} */ public function getJsonTypeDeclarationSQL(array $column): string { return 'JSON'; } /** * {@inheritDoc} * * From version 10.4.3, MariaDb aliases JSON to LONGTEXT and adds a constraint CHECK (json_valid). Reverse * this process when introspecting tables. * * @see https://mariadb.com/kb/en/information-schema-check_constraints-table/ * @see https://mariadb.com/kb/en/json-data-type/ * @see https://jira.mariadb.org/browse/MDEV-13916 */ public function getListTableColumnsSQL($table, $database = null): string { // @todo 4.0 - call getColumnTypeSQLSnippet() instead [$columnTypeSQL, $joinCheckConstraintSQL] = $this->getColumnTypeSQLSnippets('c', $database); return sprintf( <<<SQL SELECT c.COLUMN_NAME AS Field, $columnTypeSQL AS Type, c.IS_NULLABLE AS `Null`, c.COLUMN_KEY AS `Key`, c.COLUMN_DEFAULT AS `Default`, c.EXTRA AS Extra, c.COLUMN_COMMENT AS Comment, c.CHARACTER_SET_NAME AS CharacterSet, c.COLLATION_NAME AS Collation FROM information_schema.COLUMNS c $joinCheckConstraintSQL WHERE c.TABLE_SCHEMA = %s AND c.TABLE_NAME = %s ORDER BY ORDINAL_POSITION ASC; SQL , $this->getDatabaseNameSQL($database), $this->quoteStringLiteral($table), ); } /** * Generate SQL snippets to reverse the aliasing of JSON to LONGTEXT. * * MariaDb aliases columns specified as JSON to LONGTEXT and sets a CHECK constraint to ensure the column * is valid json. This function generates the SQL snippets which reverse this aliasing i.e. report a column * as JSON where it was originally specified as such instead of LONGTEXT. * * The CHECK constraints are stored in information_schema.CHECK_CONSTRAINTS so query that table. */ public function getColumnTypeSQLSnippet(string $tableAlias = 'c', ?string $databaseName = null): string { if ($this->getJsonTypeDeclarationSQL([]) !== 'JSON') { return parent::getColumnTypeSQLSnippet($tableAlias, $databaseName); } if ($databaseName === null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6215', 'Not passing a database name to methods "getColumnTypeSQLSnippet()", ' . '"getColumnTypeSQLSnippets()", and "getListTableColumnsSQL()" of "%s" is deprecated.', self::class, ); } $subQueryAlias = 'i_' . $tableAlias; $databaseName = $this->getDatabaseNameSQL($databaseName); // The check for `CONSTRAINT_SCHEMA = $databaseName` is mandatory here to prevent performance issues return <<<SQL IF( $tableAlias.COLUMN_TYPE = 'longtext' AND EXISTS( SELECT * from information_schema.CHECK_CONSTRAINTS $subQueryAlias WHERE $subQueryAlias.CONSTRAINT_SCHEMA = $databaseName AND $subQueryAlias.TABLE_NAME = $tableAlias.TABLE_NAME AND $subQueryAlias.CHECK_CLAUSE = CONCAT( 'json_valid(`', $tableAlias.COLUMN_NAME, '`)' ) ), 'json', $tableAlias.COLUMN_TYPE ) SQL; } /** {@inheritDoc} */ public function getColumnDeclarationSQL($name, array $column) { // MariaDb forces column collation to utf8mb4_bin where the column was declared as JSON so ignore // collation and character set for json columns as attempting to set them can cause an error. if ($this->getJsonTypeDeclarationSQL([]) === 'JSON' && ($column['type'] ?? null) instanceof JsonType) { unset($column['collation']); unset($column['charset']); } return parent::getColumnDeclarationSQL($name, $column); } } Platforms/MySQL57Platform.php 0000755 00000004515 00000000000 0012023 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\SQL\Parser; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; /** * Provides the behavior, features and SQL dialect of the MySQL 5.7 database platform. * * @deprecated This class will be merged with {@see MySQLPlatform} in 4.0 because support for MySQL * releases prior to 5.7 will be dropped. */ class MySQL57Platform extends MySQLPlatform { /** * {@inheritDoc} * * @deprecated */ public function hasNativeJsonType() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } /** * {@inheritDoc} */ public function getJsonTypeDeclarationSQL(array $column) { return 'JSON'; } public function createSQLParser(): Parser { return new Parser(true); } /** * {@inheritDoc} */ protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { return []; } /** * {@inheritDoc} */ protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { return []; } /** * {@inheritDoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { return ['ALTER TABLE ' . $tableName . ' RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)]; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'MySQL57Platform::getReservedKeywordsClass() is deprecated,' . ' use MySQL57Platform::createReservedKeywordsList() instead.', ); return Keywords\MySQL57Keywords::class; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { parent::initializeDoctrineTypeMappings(); $this->doctrineTypeMapping['json'] = Types::JSON; } } Platforms/SQLServer2012Platform.php 0000755 00000000452 00000000000 0013031 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; /** * Provides the behavior, features and SQL dialect of the Microsoft SQL Server database platform * of the oldest supported version. * * @deprecated Use {@see SQLServerPlatform} instead. */ class SQLServer2012Platform extends SQLServerPlatform { } Platforms/MariaDb1010Platform.php 0000755 00000002702 00000000000 0012477 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use function implode; /** * Provides the behavior, features and SQL dialect of the MariaDB 10.10 database platform. */ class MariaDb1010Platform extends MariaDb1060Platform { public function createSelectSQLBuilder(): SelectSQLBuilder { return AbstractPlatform::createSelectSQLBuilder(); } public function fetchTableOptionsByTable(bool $includeTableName): string { // MariaDB-10.10.1 added FULL_COLLATION_NAME to the information_schema.COLLATION_CHARACTER_SET_APPLICABILITY. // A base collation like uca1400_ai_ci can refer to multiple character sets. The value in the // information_schema.TABLES.TABLE_COLLATION corresponds to the full collation name. $sql = <<<'SQL' SELECT t.TABLE_NAME, t.ENGINE, t.AUTO_INCREMENT, t.TABLE_COMMENT, t.CREATE_OPTIONS, t.TABLE_COLLATION, ccsa.CHARACTER_SET_NAME FROM information_schema.TABLES t INNER JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY ccsa ON ccsa.FULL_COLLATION_NAME = t.TABLE_COLLATION SQL; $conditions = ['t.TABLE_SCHEMA = ?']; if ($includeTableName) { $conditions[] = 't.TABLE_NAME = ?'; } $conditions[] = "t.TABLE_TYPE = 'BASE TABLE'"; return $sql . ' WHERE ' . implode(' AND ', $conditions); } } Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php 0000755 00000001613 00000000000 0024356 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; /** @internal */ final class ConnectionCollationMetadataProvider implements CollationMetadataProvider { /** @var Connection */ private $connection; public function __construct(Connection $connection) { $this->connection = $connection; } /** @throws Exception */ public function getCollationCharset(string $collation): ?string { $charset = $this->connection->fetchOne( <<<'SQL' SELECT CHARACTER_SET_NAME FROM information_schema.COLLATIONS WHERE COLLATION_NAME = ?; SQL , [$collation], ); if ($charset !== false) { return $charset; } return null; } } Platforms/MySQL/CollationMetadataProvider/CachingCollationMetadataProvider.php 0000755 00000001626 00000000000 0023617 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; use function array_key_exists; /** @internal */ final class CachingCollationMetadataProvider implements CollationMetadataProvider { /** @var CollationMetadataProvider */ private $collationMetadataProvider; /** @var array<string,?string> */ private $cache = []; public function __construct(CollationMetadataProvider $collationMetadataProvider) { $this->collationMetadataProvider = $collationMetadataProvider; } public function getCollationCharset(string $collation): ?string { if (array_key_exists($collation, $this->cache)) { return $this->cache[$collation]; } return $this->cache[$collation] = $this->collationMetadataProvider->getCollationCharset($collation); } } Platforms/MySQL/Comparator.php 0000755 00000005334 00000000000 0012251 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\MySQL; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Schema\Comparator as BaseComparator; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use function array_diff_assoc; use function array_intersect_key; /** * Compares schemas in the context of MySQL platform. * * In MySQL, unless specified explicitly, the column's character set and collation are inherited from its containing * table. So during comparison, an omitted value and the value that matches the default value of table in the * desired schema must be considered equal. */ class Comparator extends BaseComparator { /** @var CollationMetadataProvider */ private $collationMetadataProvider; /** @internal The comparator can be only instantiated by a schema manager. */ public function __construct(AbstractMySQLPlatform $platform, CollationMetadataProvider $collationMetadataProvider) { parent::__construct($platform); $this->collationMetadataProvider = $collationMetadataProvider; } public function compareTables(Table $fromTable, Table $toTable): TableDiff { return parent::compareTables( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } /** * {@inheritDoc} */ public function diffTable(Table $fromTable, Table $toTable) { return parent::diffTable( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } private function normalizeColumns(Table $table): Table { $tableOptions = array_intersect_key($table->getOptions(), [ 'charset' => null, 'collation' => null, ]); $table = clone $table; foreach ($table->getColumns() as $column) { $originalOptions = $column->getPlatformOptions(); $normalizedOptions = $this->normalizeOptions($originalOptions); $overrideOptions = array_diff_assoc($normalizedOptions, $tableOptions); if ($overrideOptions === $originalOptions) { continue; } $column->setPlatformOptions($overrideOptions); } return $table; } /** * @param array<string,string> $options * * @return array<string,string> */ private function normalizeOptions(array $options): array { if (isset($options['collation']) && ! isset($options['charset'])) { $charset = $this->collationMetadataProvider->getCollationCharset($options['collation']); if ($charset !== null) { $options['charset'] = $charset; } } return $options; } } Platforms/MySQL/CollationMetadataProvider.php 0000755 00000000311 00000000000 0015230 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\MySQL; /** @internal */ interface CollationMetadataProvider { public function getCollationCharset(string $collation): ?string; } Platforms/MariaDb1060Platform.php 0000755 00000000576 00000000000 0012513 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; /** * Provides the behavior, features and SQL dialect of the MariaDB 10.6 database platform. */ class MariaDb1060Platform extends MariaDb1052Platform { public function createSelectSQLBuilder(): SelectSQLBuilder { return AbstractPlatform::createSelectSQLBuilder(); } } Platforms/SQLite/Comparator.php 0000755 00000003111 00000000000 0012434 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\SQLite; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\Comparator as BaseComparator; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use function strcasecmp; /** * Compares schemas in the context of SQLite platform. * * BINARY is the default column collation and should be ignored if specified explicitly. */ class Comparator extends BaseComparator { /** @internal The comparator can be only instantiated by a schema manager. */ public function __construct(SqlitePlatform $platform) { parent::__construct($platform); } public function compareTables(Table $fromTable, Table $toTable): TableDiff { return parent::compareTables( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } /** * {@inheritDoc} */ public function diffTable(Table $fromTable, Table $toTable) { return parent::diffTable( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } private function normalizeColumns(Table $table): Table { $table = clone $table; foreach ($table->getColumns() as $column) { $options = $column->getPlatformOptions(); if (! isset($options['collation']) || strcasecmp($options['collation'], 'binary') !== 0) { continue; } unset($options['collation']); $column->setPlatformOptions($options); } return $table; } } Platforms/Keywords/PostgreSQL94Keywords.php 0000755 00000000331 00000000000 0014704 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * PostgreSQL 9.4 reserved keywords list. * * @deprecated Use {@see PostgreSQLKeywords} instead. */ class PostgreSQL94Keywords extends PostgreSQLKeywords { } Platforms/Keywords/ReservedKeywordsValidator.php 0000755 00000005643 00000000000 0016164 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\Deprecations\Deprecation; use function count; use function implode; use function str_replace; /** @deprecated Use database documentation instead. */ class ReservedKeywordsValidator implements Visitor { /** @var KeywordList[] */ private array $keywordLists; /** @var string[] */ private array $violations = []; /** @param KeywordList[] $keywordLists */ public function __construct(array $keywordLists) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5431', 'ReservedKeywordsValidator is deprecated. Use database documentation instead.', ); $this->keywordLists = $keywordLists; } /** @return string[] */ public function getViolations() { return $this->violations; } /** * @param string $word * * @return string[] */ private function isReservedWord($word): array { if ($word[0] === '`') { $word = str_replace('`', '', $word); } $keywordLists = []; foreach ($this->keywordLists as $keywordList) { if (! $keywordList->isKeyword($word)) { continue; } $keywordLists[] = $keywordList->getName(); } return $keywordLists; } /** * @param string $asset * @param string[] $violatedPlatforms */ private function addViolation($asset, $violatedPlatforms): void { if (count($violatedPlatforms) === 0) { return; } $this->violations[] = $asset . ' keyword violations: ' . implode(', ', $violatedPlatforms); } /** * {@inheritDoc} */ public function acceptColumn(Table $table, Column $column) { $this->addViolation( 'Table ' . $table->getName() . ' column ' . $column->getName(), $this->isReservedWord($column->getName()), ); } /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { } /** * {@inheritDoc} */ public function acceptIndex(Table $table, Index $index) { } /** * {@inheritDoc} */ public function acceptSchema(Schema $schema) { } /** * {@inheritDoc} */ public function acceptSequence(Sequence $sequence) { } /** * {@inheritDoc} */ public function acceptTable(Table $table) { $this->addViolation( 'Table ' . $table->getName(), $this->isReservedWord($table->getName()), ); } } Platforms/Keywords/MySQL84Keywords.php 0000755 00000002466 00000000000 0013660 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; use function array_diff; use function array_merge; /** * MySQL 8.4 reserved keywords list. */ class MySQL84Keywords extends MySQL80Keywords { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MySQL84Keywords::getName() is deprecated.', ); return 'MySQL84'; } /** * {@inheritDoc} * * @link https://dev.mysql.com/doc/refman/8.4/en/keywords.html */ protected function getKeywords() { $keywords = parent::getKeywords(); // Removed Keywords and Reserved Words $keywords = array_diff($keywords, [ 'MASTER_BIND', 'MASTER_SSL_VERIFY_SERVER_CERT', ]); // New Keywords and Reserved Words $keywords = array_merge($keywords, [ 'AUTO', 'BERNOULLI', 'GTIDS', 'LOG', 'MANUAL', 'PARALLEL', 'PARSE_TREE', 'QUALIFY', 'S3', 'TABLESAMPLE', ]); return $keywords; } } Platforms/Keywords/MySQL57Keywords.php 0000755 00000014106 00000000000 0013652 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * MySQL 5.7 reserved keywords list. * * @deprecated Use {@link MySQLKeywords} instead. */ class MySQL57Keywords extends MySQLKeywords { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MySQL57Keywords::getName() is deprecated.', ); return 'MySQL57'; } /** * {@inheritDoc} * * @link http://dev.mysql.com/doc/mysqld-version-reference/en/mysqld-version-reference-reservedwords-5-7.html */ protected function getKeywords() { return [ 'ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'CONDITION', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'ESCAPED', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'GENERATED', 'GET', 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERVAL', 'INTO', 'IO_AFTER_GTIDS', 'IO_BEFORE_GTIDS', 'IS', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_BIND', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NO_WRITE_TO_BINLOG', 'NOT', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE', 'OPTIMIZER_COSTS', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'PARTITION', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'RANGE', 'READ', 'READ_WRITE', 'READS', 'REAL', 'REFERENCES', 'REGEXP', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'RLIKE', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SMALLINT', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SSL', 'STARTING', 'STORED', 'STRAIGHT_JOIN', 'TABLE', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'VIRTUAL', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WRITE', 'XOR', 'YEAR_MONTH', 'ZEROFILL', ]; } } Platforms/Keywords/DB2Keywords.php 0000755 00000022633 00000000000 0013104 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * DB2 Keywords. */ class DB2Keywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'DB2Keywords::getName() is deprecated.', ); return 'DB2'; } /** * {@inheritDoc} */ protected function getKeywords() { return [ 'ACTIVATE', 'ADD', 'AFTER', 'ALIAS', 'ALL', 'ALLOCATE', 'ALLOW', 'ALTER', 'AND', 'ANY', 'AS', 'ASENSITIVE', 'ASSOCIATE', 'ASUTIME', 'AT', 'ATTRIBUTES', 'AUDIT', 'AUTHORIZATION', 'AUX', 'AUXILIARY', 'BEFORE', 'BEGIN', 'BETWEEN', 'BINARY', 'BUFFERPOOL', 'BY', 'CACHE', 'CALL', 'CALLED', 'CAPTURE', 'CARDINALITY', 'CASCADED', 'CASE', 'CAST', 'CCSID', 'CHAR', 'CHARACTER', 'CHECK', 'CLONE', 'CLOSE', 'CLUSTER', 'COLLECTION', 'COLLID', 'COLUMN', 'COMMENT', 'COMMIT', 'CONCAT', 'CONDITION', 'CONNECT', 'CONNECTION', 'CONSTRAINT', 'CONTAINS', 'CONTINUE', 'COUNT', 'COUNT_BIG', 'CREATE', 'CROSS', 'CURRENT', 'CURRENT_DATE', 'CURRENT_LC_CTYPE', 'CURRENT_PATH', 'CURRENT_SCHEMA', 'CURRENT_SERVER', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_TIMEZONE', 'CURRENT_USER', 'CURSOR', 'CYCLE', 'DATA', 'DATABASE', 'DATAPARTITIONNAME', 'DATAPARTITIONNUM', 'DATE', 'DAY', 'DAYS', 'DB2GENERAL', 'DB2GENRL', 'DB2SQL', 'DBINFO', 'DBPARTITIONNAME', 'DBPARTITIONNUM', 'DEALLOCATE', 'DECLARE', 'DEFAULT', 'DEFAULTS', 'DEFINITION', 'DELETE', 'DENSE_RANK', 'DENSERANK', 'DESCRIBE', 'DESCRIPTOR', 'DETERMINISTIC', 'DIAGNOSTICS', 'DISABLE', 'DISALLOW', 'DISCONNECT', 'DISTINCT', 'DO', 'DOCUMENT', 'DOUBLE', 'DROP', 'DSSIZE', 'DYNAMIC', 'EACH', 'EDITPROC', 'ELSE', 'ELSEIF', 'ENABLE', 'ENCODING', 'ENCRYPTION', 'END', 'END-EXEC', 'ENDING', 'ERASE', 'ESCAPE', 'EVERY', 'EXCEPT', 'EXCEPTION', 'EXCLUDING', 'EXCLUSIVE', 'EXECUTE', 'EXISTS', 'EXIT', 'EXPLAIN', 'EXTERNAL', 'EXTRACT', 'FENCED', 'FETCH', 'FIELDPROC', 'FILE', 'FINAL', 'FOR', 'FOREIGN', 'FREE', 'FROM', 'FULL', 'FUNCTION', 'GENERAL', 'GENERATED', 'GET', 'GLOBAL', 'GO', 'GOTO', 'GRANT', 'GRAPHIC', 'GROUP', 'HANDLER', 'HASH', 'HASHED_VALUE', 'HAVING', 'HINT', 'HOLD', 'HOUR', 'HOURS', 'IDENTITY', 'IF', 'IMMEDIATE', 'IN', 'INCLUDING', 'INCLUSIVE', 'INCREMENT', 'INDEX', 'INDICATOR', 'INF', 'INFINITY', 'INHERIT', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INTEGRITY', 'INTERSECT', 'INTO', 'IS', 'ISOBID', 'ISOLATION', 'ITERATE', 'JAR', 'JAVA', 'JOIN', 'KEEP', 'KEY', 'LABEL', 'LANGUAGE', 'LATERAL', 'LC_CTYPE', 'LEAVE', 'LEFT', 'LIKE', 'LINKTYPE', 'LOCAL', 'LOCALDATE', 'LOCALE', 'LOCALTIME', 'LOCALTIMESTAMP RIGHT', 'LOCATOR', 'LOCATORS', 'LOCK', 'LOCKMAX', 'LOCKSIZE', 'LONG', 'LOOP', 'MAINTAINED', 'MATERIALIZED', 'MAXVALUE', 'MICROSECOND', 'MICROSECONDS', 'MINUTE', 'MINUTES', 'MINVALUE', 'MODE', 'MODIFIES', 'MONTH', 'MONTHS', 'NAN', 'NEW', 'NEW_TABLE', 'NEXTVAL', 'NO', 'NOCACHE', 'NOCYCLE', 'NODENAME', 'NODENUMBER', 'NOMAXVALUE', 'NOMINVALUE', 'NONE', 'NOORDER', 'NORMALIZED', 'NOT', 'NULL', 'NULLS', 'NUMPARTS', 'OBID', 'OF', 'OLD', 'OLD_TABLE', 'ON', 'OPEN', 'OPTIMIZATION', 'OPTIMIZE', 'OPTION', 'OR', 'ORDER', 'OUT', 'OUTER', 'OVER', 'OVERRIDING', 'PACKAGE', 'PADDED', 'PAGESIZE', 'PARAMETER', 'PART', 'PARTITION', 'PARTITIONED', 'PARTITIONING', 'PARTITIONS', 'PASSWORD', 'PATH', 'PIECESIZE', 'PLAN', 'POSITION', 'PRECISION', 'PREPARE', 'PREVVAL', 'PRIMARY', 'PRIQTY', 'PRIVILEGES', 'PROCEDURE', 'PROGRAM', 'PSID', 'PUBLIC', 'QUERY', 'QUERYNO', 'RANGE', 'RANK', 'READ', 'READS', 'RECOVERY', 'REFERENCES', 'REFERENCING', 'REFRESH', 'RELEASE', 'RENAME', 'REPEAT', 'RESET', 'RESIGNAL', 'RESTART', 'RESTRICT', 'RESULT', 'RESULT_SET_LOCATOR WLM', 'RETURN', 'RETURNS', 'REVOKE', 'ROLE', 'ROLLBACK', 'ROUND_CEILING', 'ROUND_DOWN', 'ROUND_FLOOR', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN', 'ROUND_HALF_UP', 'ROUND_UP', 'ROUTINE', 'ROW', 'ROW_NUMBER', 'ROWNUMBER', 'ROWS', 'ROWSET', 'RRN', 'RUN', 'SAVEPOINT', 'SCHEMA', 'SCRATCHPAD', 'SCROLL', 'SEARCH', 'SECOND', 'SECONDS', 'SECQTY', 'SECURITY', 'SELECT', 'SENSITIVE', 'SEQUENCE', 'SESSION', 'SESSION_USER', 'SET', 'SIGNAL', 'SIMPLE', 'SNAN', 'SOME', 'SOURCE', 'SPECIFIC', 'SQL', 'SQLID', 'STACKED', 'STANDARD', 'START', 'STARTING', 'STATEMENT', 'STATIC', 'STATMENT', 'STAY', 'STOGROUP', 'STORES', 'STYLE', 'SUBSTRING', 'SUMMARY', 'SYNONYM', 'SYSFUN', 'SYSIBM', 'SYSPROC', 'SYSTEM', 'SYSTEM_USER', 'TABLE', 'TABLESPACE', 'THEN', 'TIME', 'TIMESTAMP', 'TO', 'TRANSACTION', 'TRIGGER', 'TRIM', 'TRUNCATE', 'TYPE', 'UNDO', 'UNION', 'UNIQUE', 'UNTIL', 'UPDATE', 'USAGE', 'USER', 'USING', 'VALIDPROC', 'VALUE', 'VALUES', 'VARIABLE', 'VARIANT', 'VCAT', 'VERSION', 'VIEW', 'VOLATILE', 'VOLUMES', 'WHEN', 'WHENEVER', 'WHERE', 'WHILE', 'WITH', 'WITHOUT', 'WRITE', 'XMLELEMENT', 'XMLEXISTS', 'XMLNAMESPACES', 'YEAR', 'YEARS', ]; } } Platforms/Keywords/SQLServer2012Keywords.php 0000755 00000000350 00000000000 0014660 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * Microsoft SQL Server 2012 reserved keyword dictionary. * * @deprecated Use {@see SQLServerKeywords} instead. */ class SQLServer2012Keywords extends SQLServerKeywords { } Platforms/Keywords/PostgreSQL100Keywords.php 0000755 00000001134 00000000000 0014752 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * PostgreSQL 10.0 reserved keywords list. * * @deprecated Use {@link PostgreSQLKeywords} instead. */ class PostgreSQL100Keywords extends PostgreSQL94Keywords { /** @deprecated */ public function getName(): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'PostgreSQL100Keywords::getName() is deprecated.', ); return 'PostgreSQL100'; } } Platforms/Keywords/SQLServerKeywords.php 0000755 00000011723 00000000000 0014361 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * Microsoft SQL Server 2012 reserved keyword dictionary. * Reserved keywords list corresponding to the Microsoft SQL Server database platform of the oldest supported version. */ class SQLServerKeywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'SQLServerKeywords::getName() is deprecated.', ); return 'SQLServer'; } /** * {@inheritDoc} * * @link http://msdn.microsoft.com/en-us/library/aa238507%28v=sql.80%29.aspx */ protected function getKeywords() { return [ 'ADD', 'ALL', 'ALTER', 'AND', 'ANY', 'AS', 'ASC', 'AUTHORIZATION', 'BACKUP', 'BEGIN', 'BETWEEN', 'BREAK', 'BROWSE', 'BULK', 'BY', 'CASCADE', 'CASE', 'CHECK', 'CHECKPOINT', 'CLOSE', 'CLUSTERED', 'COALESCE', 'COLLATE', 'COLUMN', 'COMMIT', 'COMPUTE', 'CONSTRAINT', 'CONTAINS', 'CONTAINSTABLE', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DBCC', 'DEALLOCATE', 'DECLARE', 'DEFAULT', 'DELETE', 'DENY', 'DESC', 'DISK', 'DISTINCT', 'DISTRIBUTED', 'DOUBLE', 'DROP', 'DUMP', 'ELSE', 'END', 'ERRLVL', 'ESCAPE', 'EXCEPT', 'EXEC', 'EXECUTE', 'EXISTS', 'EXIT', 'EXTERNAL', 'FETCH', 'FILE', 'FILLFACTOR', 'FOR', 'FOREIGN', 'FREETEXT', 'FREETEXTTABLE', 'FROM', 'FULL', 'FUNCTION', 'GOTO', 'GRANT', 'GROUP', 'HAVING', 'HOLDLOCK', 'IDENTITY', 'IDENTITY_INSERT', 'IDENTITYCOL', 'IF', 'IN', 'INDEX', 'INNER', 'INSERT', 'INTERSECT', 'INTO', 'IS', 'JOIN', 'KEY', 'KILL', 'LEFT', 'LIKE', 'LINENO', 'LOAD', 'MERGE', 'NATIONAL', 'NOCHECK ', 'NONCLUSTERED', 'NOT', 'NULL', 'NULLIF', 'OF', 'OFF', 'OFFSETS', 'ON', 'OPEN', 'OPENDATASOURCE', 'OPENQUERY', 'OPENROWSET', 'OPENXML', 'OPTION', 'OR', 'ORDER', 'OUTER', 'OVER', 'PERCENT', 'PIVOT', 'PLAN', 'PRECISION', 'PRIMARY', 'PRINT', 'PROC', 'PROCEDURE', 'PUBLIC', 'RAISERROR', 'READ', 'READTEXT', 'RECONFIGURE', 'REFERENCES', 'REPLICATION', 'RESTORE', 'RESTRICT', 'RETURN', 'REVERT', 'REVOKE', 'RIGHT', 'ROLLBACK', 'ROWCOUNT', 'ROWGUIDCOL', 'RULE', 'SAVE', 'SCHEMA', 'SECURITYAUDIT', 'SELECT', 'SEMANTICKEYPHRASETABLE', 'SEMANTICSIMILARITYDETAILSTABLE', 'SEMANTICSIMILARITYTABLE', 'SESSION_USER', 'SET', 'SETUSER', 'SHUTDOWN', 'SOME', 'STATISTICS', 'SYSTEM_USER', 'TABLE', 'TABLESAMPLE', 'TEXTSIZE', 'THEN', 'TO', 'TOP', 'TRAN', 'TRANSACTION', 'TRIGGER', 'TRUNCATE', 'TRY_CONVERT', 'TSEQUAL', 'UNION', 'UNIQUE', 'UNPIVOT', 'UPDATE', 'UPDATETEXT', 'USE', 'USER', 'VALUES', 'VARYING', 'VIEW', 'WAITFOR', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WITHIN GROUP', 'WRITETEXT', ]; } } Platforms/Keywords/KeywordList.php 0000755 00000002165 00000000000 0013263 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_flip; use function array_map; use function strtoupper; /** * Abstract interface for a SQL reserved keyword dictionary. * * @psalm-consistent-constructor */ abstract class KeywordList { /** @var string[]|null */ private ?array $keywords = null; /** * Checks if the given word is a keyword of this dialect/vendor platform. * * @param string $word * * @return bool */ public function isKeyword($word) { if ($this->keywords === null) { $this->initializeKeywords(); } return isset($this->keywords[strtoupper($word)]); } /** @return void */ protected function initializeKeywords() { $this->keywords = array_flip(array_map('strtoupper', $this->getKeywords())); } /** * Returns the list of keywords. * * @return string[] */ abstract protected function getKeywords(); /** * Returns the name of this keyword list. * * @deprecated * * @return string */ abstract public function getName(); } Platforms/Keywords/PostgreSQLKeywords.php 0000755 00000005553 00000000000 0014542 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * Reserved keywords list corresponding to the PostgreSQL database platform of the oldest supported version. */ class PostgreSQLKeywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'PostgreSQLKeywords::getName() is deprecated.', ); return 'PostgreSQL'; } /** * {@inheritDoc} */ protected function getKeywords() { return [ 'ALL', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'ARRAY', 'AS', 'ASC', 'ASYMMETRIC', 'AUTHORIZATION', 'BINARY', 'BOTH', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLLATION', 'COLUMN', 'CONCURRENTLY', 'CONSTRAINT', 'CREATE', 'CROSS', 'CURRENT_CATALOG', 'CURRENT_DATE', 'CURRENT_ROLE', 'CURRENT_SCHEMA', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'DEFAULT', 'DEFERRABLE', 'DESC', 'DISTINCT', 'DO', 'ELSE', 'END', 'EXCEPT', 'FALSE', 'FETCH', 'FOR', 'FOREIGN', 'FREEZE', 'FROM', 'FULL', 'GRANT', 'GROUP', 'HAVING', 'ILIKE', 'IN', 'INITIALLY', 'INNER', 'INTERSECT', 'INTO', 'IS', 'ISNULL', 'JOIN', 'LATERAL', 'LEADING', 'LEFT', 'LIKE', 'LIMIT', 'LOCALTIME', 'LOCALTIMESTAMP', 'NATURAL', 'NOT', 'NOTNULL', 'NULL', 'OFFSET', 'ON', 'ONLY', 'OR', 'ORDER', 'OUTER', 'OVERLAPS', 'PLACING', 'PRIMARY', 'REFERENCES', 'RETURNING', 'RIGHT', 'SELECT', 'SESSION_USER', 'SIMILAR', 'SOME', 'SYMMETRIC', 'TABLE', 'THEN', 'TO', 'TRAILING', 'TRUE', 'UNION', 'UNIQUE', 'USER', 'USING', 'VARIADIC', 'VERBOSE', 'WHEN', 'WHERE', 'WINDOW', 'WITH', ]; } } Platforms/Keywords/SQLiteKeywords.php 0000755 00000006253 00000000000 0013676 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * SQLite Keywordlist. */ class SQLiteKeywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'SQLiteKeywords::getName() is deprecated.', ); return 'SQLite'; } /** * {@inheritDoc} */ protected function getKeywords() { return [ 'ABORT', 'ACTION', 'ADD', 'AFTER', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ATTACH', 'AUTOINCREMENT', 'BEFORE', 'BEGIN', 'BETWEEN', 'BY', 'CASCADE', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLUMN', 'COMMIT', 'CONFLICT', 'CONSTRAINT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'DATABASE', 'DEFAULT', 'DEFERRABLE', 'DEFERRED', 'DELETE', 'DESC', 'DETACH', 'DISTINCT', 'DROP', 'EACH', 'ELSE', 'END', 'ESCAPE', 'EXCEPT', 'EXCLUSIVE', 'EXISTS', 'EXPLAIN', 'FAIL', 'FOR', 'FOREIGN', 'FROM', 'FULL', 'GLOB', 'GROUP', 'HAVING', 'IF', 'IGNORE', 'IMMEDIATE', 'IN', 'INDEX', 'INDEXED', 'INITIALLY', 'INNER', 'INSERT', 'INSTEAD', 'INTERSECT', 'INTO', 'IS', 'ISNULL', 'JOIN', 'KEY', 'LEFT', 'LIKE', 'LIMIT', 'MATCH', 'NATURAL', 'NO', 'NOT', 'NOTNULL', 'NULL', 'OF', 'OFFSET', 'ON', 'OR', 'ORDER', 'OUTER', 'PLAN', 'PRAGMA', 'PRIMARY', 'QUERY', 'RAISE', 'REFERENCES', 'REGEXP', 'REINDEX', 'RELEASE', 'RENAME', 'REPLACE', 'RESTRICT', 'RIGHT', 'ROLLBACK', 'ROW', 'SAVEPOINT', 'SELECT', 'SET', 'TABLE', 'TEMP', 'TEMPORARY', 'THEN', 'TO', 'TRANSACTION', 'TRIGGER', 'UNION', 'UNIQUE', 'UPDATE', 'USING', 'VACUUM', 'VALUES', 'VIEW', 'VIRTUAL', 'WHEN', 'WHERE', ]; } } Platforms/Keywords/MariaDb102Keywords.php 0000755 00000001171 00000000000 0014251 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * MariaDb reserved keywords list. * * @deprecated Use {@link MariaDBKeywords} instead. * * @link https://mariadb.com/kb/en/the-mariadb-library/reserved-words/ */ final class MariaDb102Keywords extends MariaDBKeywords { /** @deprecated */ public function getName(): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MariaDb102Keywords::getName() is deprecated.', ); return 'MariaDb102'; } } Platforms/Keywords/MySQLKeywords.php 0000755 00000013723 00000000000 0013502 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * MySQL Keywordlist. */ class MySQLKeywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MySQLKeywords::getName() is deprecated.', ); return 'MySQL'; } /** * {@inheritDoc} */ protected function getKeywords() { return [ 'ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'CONDITION', 'CONNECTION', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'ESCAPED', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'GENERAL', 'GOTO', 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IGNORE_SERVER_IDS', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERVAL', 'INTO', 'IS', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LABEL', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_HEARTBEAT_PERIOD', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NO_WRITE_TO_BINLOG', 'NOT', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'PARTITION', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'RAID0', 'RANGE', 'READ', 'READ_WRITE', 'READS', 'REAL', 'RECURSIVE', 'REFERENCES', 'REGEXP', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'RLIKE', 'ROWS', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SLOW', 'SMALLINT', 'SONAME', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SSL', 'STARTING', 'STRAIGHT_JOIN', 'TABLE', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WRITE', 'X509', 'XOR', 'YEAR_MONTH', 'ZEROFILL', ]; } } Platforms/Keywords/OracleKeywords.php 0000755 00000005753 00000000000 0013746 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * Oracle Keywordlist. */ class OracleKeywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'OracleKeywords::getName() is deprecated.', ); return 'Oracle'; } /** * {@inheritDoc} */ protected function getKeywords() { return [ 'ACCESS', 'ADD', 'ALL', 'ALTER', 'AND', 'ANY', 'ARRAYLEN', 'AS', 'ASC', 'AUDIT', 'BETWEEN', 'BY', 'CHAR', 'CHECK', 'CLUSTER', 'COLUMN', 'COMMENT', 'COMPRESS', 'CONNECT', 'CREATE', 'CURRENT', 'DATE', 'DECIMAL', 'DEFAULT', 'DELETE', 'DESC', 'DISTINCT', 'DROP', 'ELSE', 'EXCLUSIVE', 'EXISTS', 'FILE', 'FLOAT', 'FOR', 'FROM', 'GRANT', 'GROUP', 'HAVING', 'IDENTIFIED', 'IMMEDIATE', 'IN', 'INCREMENT', 'INDEX', 'INITIAL', 'INSERT', 'INTEGER', 'INTERSECT', 'INTO', 'IS', 'LEVEL', 'LIKE', 'LOCK', 'LONG', 'MAXEXTENTS', 'MINUS', 'MODE', 'MODIFY', 'NOAUDIT', 'NOCOMPRESS', 'NOT', 'NOTFOUND', 'NOWAIT', 'NULL', 'NUMBER', 'OF', 'OFFLINE', 'ON', 'ONLINE', 'OPTION', 'OR', 'ORDER', 'PCTFREE', 'PRIOR', 'PRIVILEGES', 'PUBLIC', 'RANGE', 'RAW', 'RENAME', 'RESOURCE', 'REVOKE', 'ROW', 'ROWID', 'ROWLABEL', 'ROWNUM', 'ROWS', 'SELECT', 'SESSION', 'SET', 'SHARE', 'SIZE', 'SMALLINT', 'SQLBUF', 'START', 'SUCCESSFUL', 'SYNONYM', 'SYSDATE', 'TABLE', 'THEN', 'TO', 'TRIGGER', 'UID', 'UNION', 'UNIQUE', 'UPDATE', 'USER', 'VALIDATE', 'VALUES', 'VARCHAR', 'VARCHAR2', 'VIEW', 'WHENEVER', 'WHERE', 'WITH', ]; } } Platforms/Keywords/MariaDBKeywords.php 0000755 00000014172 00000000000 0013773 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; class MariaDBKeywords extends MySQLKeywords { /** @deprecated */ public function getName(): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MariaDBKeywords::getName() is deprecated.', ); return 'MariaDB'; } /** * {@inheritDoc} */ protected function getKeywords(): array { return [ 'ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'CONDITION', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'ESCAPED', 'EXCEPT', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'GENERATED', 'GET', 'GENERAL', 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IGNORE_SERVER_IDS', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERSECT', 'INTERVAL', 'INTO', 'IO_AFTER_GTIDS', 'IO_BEFORE_GTIDS', 'IS', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_BIND', 'MASTER_HEARTBEAT_PERIOD', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NO_WRITE_TO_BINLOG', 'NOT', 'NULL', 'NUMERIC', 'OFFSET', 'ON', 'OPTIMIZE', 'OPTIMIZER_COSTS', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'OVER', 'PARTITION', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'RANGE', 'READ', 'READ_WRITE', 'READS', 'REAL', 'RECURSIVE', 'REFERENCES', 'REGEXP', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT', 'RETURN', 'RETURNING', 'REVOKE', 'RIGHT', 'RLIKE', 'ROWS', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SLOW', 'SMALLINT', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SSL', 'STARTING', 'STORED', 'STRAIGHT_JOIN', 'TABLE', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'VIRTUAL', 'WHEN', 'WHERE', 'WHILE', 'WINDOW', 'WITH', 'WRITE', 'XOR', 'YEAR_MONTH', 'ZEROFILL', ]; } } Platforms/Keywords/MySQL80Keywords.php 0000755 00000003011 00000000000 0013637 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; use function array_merge; /** * MySQL 8.0 reserved keywords list. */ class MySQL80Keywords extends MySQL57Keywords { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MySQL80Keywords::getName() is deprecated.', ); return 'MySQL80'; } /** * {@inheritDoc} * * @link https://dev.mysql.com/doc/refman/8.0/en/keywords.html */ protected function getKeywords() { $keywords = parent::getKeywords(); $keywords = array_merge($keywords, [ 'ADMIN', 'ARRAY', 'CUBE', 'CUME_DIST', 'DENSE_RANK', 'EMPTY', 'EXCEPT', 'FIRST_VALUE', 'FUNCTION', 'GROUPING', 'GROUPS', 'JSON_TABLE', 'LAG', 'LAST_VALUE', 'LATERAL', 'LEAD', 'MEMBER', 'NTH_VALUE', 'NTILE', 'OF', 'OVER', 'PERCENT_RANK', 'PERSIST', 'PERSIST_ONLY', 'RANK', 'RECURSIVE', 'ROW', 'ROWS', 'ROW_NUMBER', 'SYSTEM', 'WINDOW', ]); return $keywords; } } Platforms/DateIntervalUnit.php 0000755 00000000712 00000000000 0012412 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms; final class DateIntervalUnit { public const SECOND = 'SECOND'; public const MINUTE = 'MINUTE'; public const HOUR = 'HOUR'; public const DAY = 'DAY'; public const WEEK = 'WEEK'; public const MONTH = 'MONTH'; public const QUARTER = 'QUARTER'; public const YEAR = 'YEAR'; /** @codeCoverageIgnore */ private function __construct() { } } Platforms/PostgreSQL94Platform.php 0000755 00000000403 00000000000 0013052 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; /** * Provides the behavior, features and SQL dialect of the PostgreSQL 9.4+ database platform. * * @deprecated Use {@see PostgreSQLPlatform} instead. */ class PostgreSQL94Platform extends PostgreSQLPlatform { } Platforms/DB2111Platform.php 0000755 00000002132 00000000000 0011465 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Exception; use function sprintf; /** * Provides the behavior, features and SQL dialect of the IBM DB2 11.1 (11.1 GA) database platform. * * @deprecated This class will be merged with {@see DB2Platform} in 4.0 because support for IBM DB2 * releases prior to 11.1 will be dropped. * * @see https://www.ibm.com/docs/en/db2/11.1?topic=database-whats-new-db2-version-111-ga */ class DB2111Platform extends DB2Platform { /** * {@inheritDoc} * * @see https://www.ibm.com/docs/en/db2/11.1?topic=subselect-fetch-clause */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($offset > 0) { $query .= sprintf(' OFFSET %u ROWS', $offset); } if ($limit !== null) { if ($limit < 0) { throw new Exception(sprintf('Limit must be a positive integer or zero, %d given', $limit)); } $query .= sprintf(' FETCH %s %u ROWS ONLY', $offset === 0 ? 'FIRST' : 'NEXT', $limit); } return $query; } } Platforms/OraclePlatform.php 0000755 00000116053 00000000000 0012110 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\OracleSchemaManager; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types\BinaryType; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use function array_merge; use function count; use function explode; use function func_get_arg; use function func_num_args; use function implode; use function preg_match; use function sprintf; use function strlen; use function strpos; use function strtoupper; use function substr; /** * OraclePlatform. */ class OraclePlatform extends AbstractPlatform { /** * Assertion for Oracle identifiers. * * @deprecated * * @link http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm * * @param string $identifier * * @return void * * @throws Exception */ public static function assertValidIdentifier($identifier) { if (preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier) === 0) { throw new Exception('Invalid Oracle identifier'); } } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length !== null) { return sprintf('SUBSTR(%s, %d, %d)', $string, $start, $length); } return sprintf('SUBSTR(%s, %d)', $string, $start); } /** * @deprecated Generate dates within the application. * * @param string $type * * @return string */ public function getNowExpression($type = 'timestamp') { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4753', 'OraclePlatform::getNowExpression() is deprecated. Generate dates within the application.', ); switch ($type) { case 'date': case 'time': case 'timestamp': default: return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')'; } } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'INSTR(' . $str . ', ' . $substr . ')'; } return 'INSTR(' . $str . ', ' . $substr . ', ' . $startPos . ')'; } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { switch ($unit) { case DateIntervalUnit::MONTH: case DateIntervalUnit::QUARTER: case DateIntervalUnit::YEAR: switch ($unit) { case DateIntervalUnit::QUARTER: $interval = $this->multiplyInterval((string) $interval, 3); break; case DateIntervalUnit::YEAR: $interval = $this->multiplyInterval((string) $interval, 12); break; } return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')'; default: $calculationClause = ''; switch ($unit) { case DateIntervalUnit::SECOND: $calculationClause = '/24/60/60'; break; case DateIntervalUnit::MINUTE: $calculationClause = '/24/60'; break; case DateIntervalUnit::HOUR: $calculationClause = '/24'; break; case DateIntervalUnit::WEEK: $calculationClause = '*7'; break; } return '(' . $date . $operator . $interval . $calculationClause . ')'; } } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return sprintf('TRUNC(%s) - TRUNC(%s)', $date1, $date2); } /** * {@inheritDoc} */ public function getBitAndComparisonExpression($value1, $value2) { return 'BITAND(' . $value1 . ', ' . $value2 . ')'; } public function getCurrentDatabaseExpression(): string { return "SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')"; } /** * {@inheritDoc} */ public function getBitOrComparisonExpression($value1, $value2) { return '(' . $value1 . '-' . $this->getBitAndComparisonExpression($value1, $value2) . '+' . $value2 . ')'; } /** * {@inheritDoc} */ public function getCreatePrimaryKeySQL(Index $index, $table): string { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } return 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $index->getQuotedName($this) . ' PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; } /** * {@inheritDoc} * * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH. * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection * in {@see listSequences()} */ public function getCreateSequenceSQL(Sequence $sequence) { return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . ' START WITH ' . $sequence->getInitialValue() . ' MINVALUE ' . $sequence->getInitialValue() . ' INCREMENT BY ' . $sequence->getAllocationSize() . $this->getSequenceCacheSQL($sequence); } /** * {@inheritDoc} */ public function getAlterSequenceSQL(Sequence $sequence) { return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize() . $this->getSequenceCacheSQL($sequence); } /** * Cache definition for sequences */ private function getSequenceCacheSQL(Sequence $sequence): string { if ($sequence->getCache() === 0) { return ' NOCACHE'; } if ($sequence->getCache() === 1) { return ' NOCACHE'; } if ($sequence->getCache() > 1) { return ' CACHE ' . $sequence->getCache(); } return ''; } /** * {@inheritDoc} */ public function getSequenceNextValSQL($sequence) { return 'SELECT ' . $sequence . '.nextval FROM DUAL'; } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case TransactionIsolationLevel::READ_UNCOMMITTED: return 'READ UNCOMMITTED'; case TransactionIsolationLevel::READ_COMMITTED: return 'READ COMMITTED'; case TransactionIsolationLevel::REPEATABLE_READ: case TransactionIsolationLevel::SERIALIZABLE: return 'SERIALIZABLE'; default: return parent::_getTransactionIsolationLevelSQL($level); } } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'NUMBER(1)'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'NUMBER(10)'; } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'NUMBER(20)'; } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'NUMBER(5)'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0)'; } /** * {@inheritDoc} */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0) WITH TIME ZONE'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { return ''; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default string column length on Oracle is deprecated' . ', specify the length explicitly.', ); } return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(2000)') : ($length > 0 ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)'); } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length on Oracle is deprecated' . ', specify the length explicitly.', ); } return 'RAW(' . ($length > 0 ? $length : $this->getBinaryMaxLength()) . ')'; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'OraclePlatform::getBinaryMaxLength() is deprecated.', ); return 2000; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'CLOB'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListDatabasesSQL() { return 'SELECT username FROM all_users'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListSequencesSQL($database) { $database = $this->normalizeIdentifier($database); $database = $this->quoteStringLiteral($database->getName()); return 'SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ' . 'WHERE SEQUENCE_OWNER = ' . $database; } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $indexes = $options['indexes'] ?? []; $options['indexes'] = []; $sql = parent::_getCreateTableSQL($name, $columns, $options); foreach ($columns as $columnName => $column) { if (isset($column['sequence'])) { $sql[] = $this->getCreateSequenceSQL($column['sequence']); } if ( ! isset($column['autoincrement']) || ! $column['autoincrement'] && (! isset($column['autoinc']) || ! $column['autoinc']) ) { continue; } $sql = array_merge($sql, $this->getCreateAutoincrementSql($columnName, $name)); } foreach ($indexes as $index) { $sql[] = $this->getCreateIndexSQL($index, $name); } return $sql; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html */ public function getListTableIndexesSQL($table, $database = null) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); return "SELECT uind_col.index_name AS name, ( SELECT uind.index_type FROM user_indexes uind WHERE uind.index_name = uind_col.index_name ) AS type, decode( ( SELECT uind.uniqueness FROM user_indexes uind WHERE uind.index_name = uind_col.index_name ), 'NONUNIQUE', 0, 'UNIQUE', 1 ) AS is_unique, uind_col.column_name AS column_name, uind_col.column_position AS column_pos, ( SELECT ucon.constraint_type FROM user_constraints ucon WHERE ucon.index_name = uind_col.index_name AND ucon.table_name = uind_col.table_name ) AS is_primary FROM user_ind_columns uind_col WHERE uind_col.table_name = " . $table . ' ORDER BY uind_col.column_position ASC'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { return 'SELECT * FROM sys.user_tables'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return 'SELECT view_name, text FROM sys.user_views'; } /** * @internal The method should be only used from within the OraclePlatform class hierarchy. * * @param string $name * @param string $table * @param int $start * * @return string[] */ public function getCreateAutoincrementSql($name, $table, $start = 1) { $tableIdentifier = $this->normalizeIdentifier($table); $quotedTableName = $tableIdentifier->getQuotedName($this); $unquotedTableName = $tableIdentifier->getName(); $nameIdentifier = $this->normalizeIdentifier($name); $quotedName = $nameIdentifier->getQuotedName($this); $unquotedName = $nameIdentifier->getName(); $sql = []; $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier); $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true); $sql[] = "DECLARE constraints_Count NUMBER; BEGIN SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = '" . $unquotedTableName . "' AND CONSTRAINT_TYPE = 'P'; IF constraints_Count = 0 OR constraints_Count = '' THEN EXECUTE IMMEDIATE '" . $this->getCreateConstraintSQL($idx, $quotedTableName) . "'; END IF; END;"; $sequenceName = $this->getIdentitySequenceName( $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName, $nameIdentifier->isQuoted() ? $quotedName : $unquotedName, ); $sequence = new Sequence($sequenceName, $start); $sql[] = $this->getCreateSequenceSQL($sequence); $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . ' BEFORE INSERT ON ' . $quotedTableName . ' FOR EACH ROW DECLARE last_Sequence NUMBER; last_InsertID NUMBER; BEGIN IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.' . $quotedName . ' = 0) THEN SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL; ELSE SELECT NVL(Last_Number, 0) INTO last_Sequence FROM User_Sequences WHERE Sequence_Name = \'' . $sequence->getName() . '\'; SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL; WHILE (last_InsertID > last_Sequence) LOOP SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; END LOOP; SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; END IF; END;'; return $sql; } /** * @internal The method should be only used from within the OracleSchemaManager class hierarchy. * * Returns the SQL statements to drop the autoincrement for the given table name. * * @param string $table The table name to drop the autoincrement for. * * @return string[] */ public function getDropAutoincrementSql($table) { $table = $this->normalizeIdentifier($table); $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table); $identitySequenceName = $this->getIdentitySequenceName( $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(), '', ); return [ 'DROP TRIGGER ' . $autoincrementIdentifierName, $this->getDropSequenceSQL($identitySequenceName), $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)), ]; } /** * Normalizes the given identifier. * * Uppercases the given identifier if it is not quoted by intention * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers. * * @param string $name The identifier to normalize. */ private function normalizeIdentifier($name): Identifier { $identifier = new Identifier($name); return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name)); } /** * Adds suffix to identifier, * * if the new string exceeds max identifier length, * keeps $suffix, cuts from $identifier as much as the part exceeding. */ private function addSuffix(string $identifier, string $suffix): string { $maxPossibleLengthWithoutSuffix = $this->getMaxIdentifierLength() - strlen($suffix); if (strlen($identifier) > $maxPossibleLengthWithoutSuffix) { $identifier = substr($identifier, 0, $maxPossibleLengthWithoutSuffix); } return $identifier . $suffix; } /** * Returns the autoincrement primary key identifier name for the given table identifier. * * Quotes the autoincrement primary key identifier name * if the given table name is quoted by intention. */ private function getAutoincrementIdentifierName(Identifier $table): string { $identifierName = $this->addSuffix($table->getName(), '_AI_PK'); return $table->isQuoted() ? $this->quoteSingleIdentifier($identifierName) : $identifierName; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableForeignKeysSQL($table) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); return "SELECT alc.constraint_name, alc.DELETE_RULE, cols.column_name \"local_column\", cols.position, ( SELECT r_cols.table_name FROM user_cons_columns r_cols WHERE alc.r_constraint_name = r_cols.constraint_name AND r_cols.position = cols.position ) AS \"references_table\", ( SELECT r_cols.column_name FROM user_cons_columns r_cols WHERE alc.r_constraint_name = r_cols.constraint_name AND r_cols.position = cols.position ) AS \"foreign_column\" FROM user_cons_columns cols JOIN user_constraints alc ON alc.constraint_name = cols.constraint_name AND alc.constraint_type = 'R' AND alc.table_name = " . $table . ' ORDER BY cols.constraint_name ASC, cols.position ASC'; } /** * @deprecated * * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); return 'SELECT * FROM user_constraints WHERE table_name = ' . $table; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); $tabColumnsTableName = 'user_tab_columns'; $colCommentsTableName = 'user_col_comments'; $tabColumnsOwnerCondition = ''; $colCommentsOwnerCondition = ''; if ($database !== null && $database !== '/') { $database = $this->normalizeIdentifier($database); $database = $this->quoteStringLiteral($database->getName()); $tabColumnsTableName = 'all_tab_columns'; $colCommentsTableName = 'all_col_comments'; $tabColumnsOwnerCondition = ' AND c.owner = ' . $database; $colCommentsOwnerCondition = ' AND d.OWNER = c.OWNER'; } return sprintf( <<<'SQL' SELECT c.*, ( SELECT d.comments FROM %s d WHERE d.TABLE_NAME = c.TABLE_NAME%s AND d.COLUMN_NAME = c.COLUMN_NAME ) AS comments FROM %s c WHERE c.table_name = %s%s ORDER BY c.column_id SQL , $colCommentsTableName, $colCommentsOwnerCondition, $tabColumnsTableName, $table, $tabColumnsOwnerCondition, ); } /** * {@inheritDoc} */ public function getDropForeignKeySQL($foreignKey, $table) { if ($foreignKey instanceof ForeignKeyConstraint) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $foreignKey as a ForeignKeyConstraint object to %s is deprecated.' . ' Pass it as a quoted name instead.', __METHOD__, ); } else { $foreignKey = new Identifier($foreignKey); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); } else { $table = new Identifier($table); } $foreignKey = $foreignKey->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $referentialAction = ''; if ($foreignKey->hasOption('onDelete')) { $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); } if ($referentialAction !== '') { return ' ON DELETE ' . $referentialAction; } return ''; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getForeignKeyReferentialActionSQL($action) { $action = strtoupper($action); switch ($action) { case 'RESTRICT': // RESTRICT is not supported, therefore falling back to NO ACTION. case 'NO ACTION': // NO ACTION cannot be declared explicitly, // therefore returning empty string to indicate to OMIT the referential clause. return ''; case 'CASCADE': case 'SET NULL': return $action; default: // SET DEFAULT is not supported, throw exception instead. throw new InvalidArgumentException('Invalid foreign key action: ' . $action); } } /** * {@inheritDoc} */ public function getCreateDatabaseSQL($name) { return 'CREATE USER ' . $name; } /** * {@inheritDoc} */ public function getDropDatabaseSQL($name) { return 'DROP USER ' . $name . ' CASCADE'; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = []; $commentsSQL = []; $columnSql = []; $fields = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); $comment = $this->getColumnComment($column); if ($comment === null || $comment === '') { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $column->getQuotedName($this), $comment, ); } if (count($fields) > 0) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ADD (' . implode(', ', $fields) . ')'; } $fields = []; foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $newColumn = $columnDiff->getNewColumn(); // Do not generate column alteration clause if type is binary and only fixed property has changed. // Oracle only supports binary type columns with variable length. // Avoids unnecessary table alteration statements. if ( $newColumn->getType() instanceof BinaryType && $columnDiff->hasFixedChanged() && count($columnDiff->changedProperties) === 1 ) { continue; } $columnHasChangedComment = $columnDiff->hasCommentChanged(); /** * Do not add query part if only comment has changed */ if (! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) { $newColumnProperties = $newColumn->toArray(); if (! $columnDiff->hasNotNullChanged()) { unset($newColumnProperties['notnull']); } $fields[] = $newColumn->getQuotedName($this) . $this->getColumnDeclarationSQL('', $newColumnProperties); } if (! $columnHasChangedComment) { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $newColumn->getQuotedName($this), $this->getColumnComment($newColumn), ); } if (count($fields) > 0) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY (' . implode(', ', $fields) . ')'; } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); } $fields = []; foreach ($diff->getDroppedColumns() as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $fields[] = $column->getQuotedName($this); } if (count($fields) > 0) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP (' . implode(', ', $fields) . ')'; } $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { $sql = array_merge($sql, $commentsSQL); $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $sql[] = sprintf( 'ALTER TABLE %s RENAME TO %s', $tableNameSQL, $newName->getQuotedName($this), ); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff), ); } return array_merge($sql, $tableSql, $columnSql); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getColumnDeclarationSQL($name, array $column) { if (isset($column['columnDefinition'])) { $columnDef = $this->getCustomTypeDeclarationSQL($column); } else { $default = $this->getDefaultValueDeclarationSQL($column); $notnull = ''; if (isset($column['notnull'])) { $notnull = $column['notnull'] ? ' NOT NULL' : ' NULL'; } if (! empty($column['unique'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "unique" column property is deprecated. Use unique constraints instead.', ); $unique = ' ' . $this->getUniqueFieldDeclarationSQL(); } else { $unique = ''; } if (! empty($column['check'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "check" column property is deprecated.', ); $check = ' ' . $column['check']; } else { $check = ''; } $typeDecl = $column['type']->getSQLDeclaration($column, $this); $columnDef = $typeDecl . $default . $notnull . $unique . $check; } return $name . ' ' . $columnDef; } /** * {@inheritDoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { if (strpos($tableName, '.') !== false) { [$schema] = explode('.', $tableName); $oldIndexName = $schema . '.' . $oldIndexName; } return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)]; } /** * {@inheritDoc} * * @deprecated */ public function usesSequenceEmulatedIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the OraclePlatform class hierarchy. */ public function getIdentitySequenceName($tableName, $columnName) { $table = new Identifier($tableName); // No usage of column name to preserve BC compatibility with <2.5 $identitySequenceName = $this->addSuffix($table->getName(), '_SEQ'); if ($table->isQuoted()) { $identitySequenceName = '"' . $identitySequenceName . '"'; } $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName); return $identitySequenceIdentifier->getQuotedName($this); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsCommentOnStatement() { return true; } /** * {@inheritDoc} */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4749', 'OraclePlatform::getName() is deprecated. Identify platforms by their class.', ); return 'oracle'; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit === null && $offset <= 0) { return $query; } if (preg_match('/^\s*SELECT/i', $query) === 1) { if (preg_match('/\sFROM\s/i', $query) === 0) { $query .= ' FROM dual'; } $columns = ['a.*']; if ($offset > 0) { $columns[] = 'ROWNUM AS doctrine_rownum'; } $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query); if ($limit !== null) { $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit); } if ($offset > 0) { $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1); } } return $query; } /** * {@inheritDoc} */ public function getCreateTemporaryTableSnippetSQL() { return 'CREATE GLOBAL TEMPORARY TABLE'; } /** * {@inheritDoc} */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:sP'; } /** * {@inheritDoc} */ public function getDateFormatString() { return 'Y-m-d 00:00:00'; } /** * {@inheritDoc} */ public function getTimeFormatString() { return '1900-01-01 H:i:s'; } /** * {@inheritDoc} */ public function getMaxIdentifierLength() { return 30; } /** * {@inheritDoc} */ public function supportsSequences() { return true; } /** * {@inheritDoc} */ public function supportsReleaseSavepoints() { return false; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); } /** * {@inheritDoc} */ public function getDummySelectSQL() { $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; return sprintf('SELECT %s FROM DUAL', $expression); } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'binary_double' => Types::FLOAT, 'binary_float' => Types::FLOAT, 'binary_integer' => Types::BOOLEAN, 'blob' => Types::BLOB, 'char' => Types::STRING, 'clob' => Types::TEXT, 'date' => Types::DATE_MUTABLE, 'float' => Types::FLOAT, 'integer' => Types::INTEGER, 'long' => Types::STRING, 'long raw' => Types::BLOB, 'nchar' => Types::STRING, 'nclob' => Types::TEXT, 'number' => Types::INTEGER, 'nvarchar2' => Types::STRING, 'pls_integer' => Types::BOOLEAN, 'raw' => Types::BINARY, 'rowid' => Types::STRING, 'timestamp' => Types::DATETIME_MUTABLE, 'timestamptz' => Types::DATETIMETZ_MUTABLE, 'urowid' => Types::STRING, 'varchar' => Types::STRING, 'varchar2' => Types::STRING, ]; } /** * {@inheritDoc} */ public function releaseSavePoint($savepoint) { return ''; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'OraclePlatform::getReservedKeywordsClass() is deprecated,' . ' use OraclePlatform::createReservedKeywordsList() instead.', ); return Keywords\OracleKeywords::class; } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'BLOB'; } /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ public function getListTableCommentsSQL(string $table, ?string $database = null): string { $tableCommentsName = 'user_tab_comments'; $ownerCondition = ''; if ($database !== null && $database !== '/') { $tableCommentsName = 'all_tab_comments'; $ownerCondition = ' AND owner = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($database)->getName(), ); } return sprintf( <<<'SQL' SELECT comments FROM %s WHERE table_name = %s%s SQL , $tableCommentsName, $this->quoteStringLiteral($this->normalizeIdentifier($table)->getName()), $ownerCondition, ); } public function createSchemaManager(Connection $connection): OracleSchemaManager { return new OracleSchemaManager($connection, $this); } } Platforms/PostgreSQL120Platform.php 0000755 00000001752 00000000000 0013130 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms; /** * Provides the behavior, features and SQL dialect of the PostgreSQL 12.0 database platform. */ class PostgreSQL120Platform extends PostgreSQL100Platform { public function getDefaultColumnValueSQLSnippet(): string { // in case of GENERATED ALWAYS AS (foobar) STORED column (added in PostgreSQL 12.0) // PostgreSQL's pg_get_expr(adbin, adrelid) will return the 'foobar' part // which is not the 'default' value of the column but its 'definition' // so in that case we force it to NULL as DBAL will use that column only for the // 'default' value return <<<'SQL' SELECT CASE WHEN a.attgenerated = 's' THEN NULL ELSE pg_get_expr(adbin, adrelid) END FROM pg_attrdef WHERE c.oid = pg_attrdef.adrelid AND pg_attrdef.adnum=a.attnum SQL; } } Platforms/PostgreSQLPlatform.php 0000755 00000122271 00000000000 0012705 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\PostgreSQLSchemaManager; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\Types\BinaryType; use Doctrine\DBAL\Types\BlobType; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use UnexpectedValueException; use function array_diff; use function array_merge; use function array_unique; use function array_values; use function count; use function explode; use function implode; use function in_array; use function is_array; use function is_bool; use function is_numeric; use function is_string; use function sprintf; use function strpos; use function strtolower; use function trim; /** * Provides the behavior, features and SQL dialect of the PostgreSQL database platform of the oldest supported version. */ class PostgreSQLPlatform extends AbstractPlatform { private bool $useBooleanTrueFalseStrings = true; /** @var string[][] PostgreSQL booleans literals */ private array $booleanLiterals = [ 'true' => [ 't', 'true', 'y', 'yes', 'on', '1', ], 'false' => [ 'f', 'false', 'n', 'no', 'off', '0', ], ]; /** * PostgreSQL has different behavior with some drivers * with regard to how booleans have to be handled. * * Enables use of 'true'/'false' or otherwise 1 and 0 instead. * * @param bool $flag * * @return void */ public function setUseBooleanTrueFalseStrings($flag) { $this->useBooleanTrueFalseStrings = (bool) $flag; } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length === null) { return 'SUBSTRING(' . $string . ' FROM ' . $start . ')'; } return 'SUBSTRING(' . $string . ' FROM ' . $start . ' FOR ' . $length . ')'; } /** * {@inheritDoc} * * @deprecated Generate dates within the application. */ public function getNowExpression() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4753', 'PostgreSQLPlatform::getNowExpression() is deprecated. Generate dates within the application.', ); return 'LOCALTIMESTAMP(0)'; } /** * {@inheritDoc} */ public function getRegexpExpression() { return 'SIMILAR TO'; } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos !== false) { $str = $this->getSubstringExpression($str, $startPos); return 'CASE WHEN (POSITION(' . $substr . ' IN ' . $str . ') = 0) THEN 0' . ' ELSE (POSITION(' . $substr . ' IN ' . $str . ') + ' . $startPos . ' - 1) END'; } return 'POSITION(' . $substr . ' IN ' . $str . ')'; } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { if ($unit === DateIntervalUnit::QUARTER) { $interval = $this->multiplyInterval((string) $interval, 3); $unit = DateIntervalUnit::MONTH; } return '(' . $date . ' ' . $operator . ' (' . $interval . " || ' " . $unit . "')::interval)"; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))'; } public function getCurrentDatabaseExpression(): string { return 'CURRENT_DATABASE()'; } /** * {@inheritDoc} */ public function supportsSequences() { return true; } /** * {@inheritDoc} */ public function supportsSchemas() { return true; } /** * {@inheritDoc} * * @deprecated */ public function getDefaultSchemaName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return 'public'; } /** * {@inheritDoc} */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsPartialIndexes() { return true; } /** * {@inheritDoc} * * @deprecated */ public function usesSequenceEmulatedIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return true; } /** * {@inheritDoc} * * @deprecated */ public function getIdentitySequenceName($tableName, $columnName) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return $tableName . '_' . $columnName . '_seq'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsCommentOnStatement() { return true; } /** * {@inheritDoc} * * @deprecated */ public function hasNativeGuidType() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } public function createSelectSQLBuilder(): SelectSQLBuilder { return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', null); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListDatabasesSQL() { return 'SELECT datname FROM pg_database'; } /** * {@inheritDoc} * * @deprecated Use {@see PostgreSQLSchemaManager::listSchemaNames()} instead. */ public function getListNamespacesSQL() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'PostgreSQLPlatform::getListNamespacesSQL() is deprecated,' . ' use PostgreSQLSchemaManager::listSchemaNames() instead.', ); return "SELECT schema_name AS nspname FROM information_schema.schemata WHERE schema_name NOT LIKE 'pg\_%' AND schema_name != 'information_schema'"; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListSequencesSQL($database) { return 'SELECT sequence_name AS relname, sequence_schema AS schemaname, minimum_value AS min_value, increment AS increment_by FROM information_schema.sequences WHERE sequence_catalog = ' . $this->quoteStringLiteral($database) . " AND sequence_schema NOT LIKE 'pg\_%' AND sequence_schema != 'information_schema'"; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { return "SELECT quote_ident(table_name) AS table_name, table_schema AS schema_name FROM information_schema.tables WHERE table_schema NOT LIKE 'pg\_%' AND table_schema != 'information_schema' AND table_name != 'geometry_columns' AND table_name != 'spatial_ref_sys' AND table_type != 'VIEW'"; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return 'SELECT quote_ident(table_name) AS viewname, table_schema AS schemaname, view_definition AS definition FROM information_schema.views WHERE view_definition IS NOT NULL'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { return 'SELECT quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r WHERE r.conrelid = ( SELECT c.oid FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n WHERE ' . $this->getTableWhereClause($table) . " AND n.oid = c.relnamespace ) AND r.contype = 'f'"; } /** * @deprecated * * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { $table = new Identifier($table); $table = $this->quoteStringLiteral($table->getName()); return sprintf( <<<'SQL' SELECT quote_ident(relname) as relname FROM pg_class WHERE oid IN ( SELECT indexrelid FROM pg_index, pg_class WHERE pg_class.relname = %s AND pg_class.oid = pg_index.indrelid AND (indisunique = 't' OR indisprimary = 't') ) SQL , $table, ); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ public function getListTableIndexesSQL($table, $database = null) { return 'SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary, pg_index.indkey, pg_index.indrelid, pg_get_expr(indpred, indrelid) AS where FROM pg_class, pg_index WHERE oid IN ( SELECT indexrelid FROM pg_index si, pg_class sc, pg_namespace sn WHERE ' . $this->getTableWhereClause($table, 'sc', 'sn') . ' AND sc.oid=si.indrelid AND sc.relnamespace = sn.oid ) AND pg_index.indexrelid = oid'; } /** * @param string $table * @param string $classAlias * @param string $namespaceAlias */ private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n'): string { $whereClause = $namespaceAlias . ".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND "; if (strpos($table, '.') !== false) { [$schema, $table] = explode('.', $table); $schema = $this->quoteStringLiteral($schema); } else { $schema = 'ANY(current_schemas(false))'; } $table = new Identifier($table); $table = $this->quoteStringLiteral($table->getName()); return $whereClause . sprintf( '%s.relname = %s AND %s.nspname = %s', $classAlias, $table, $namespaceAlias, $schema, ); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { return "SELECT a.attnum, quote_ident(a.attname) AS field, t.typname AS type, format_type(a.atttypid, a.atttypmod) AS complete_type, (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation, (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type, (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type, a.attnotnull AS isnotnull, (SELECT 't' FROM pg_index WHERE c.oid = pg_index.indrelid AND pg_index.indkey[0] = a.attnum AND pg_index.indisprimary = 't' ) AS pri, (SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE c.oid = pg_attrdef.adrelid AND pg_attrdef.adnum=a.attnum ) AS default, (SELECT pg_description.description FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid ) AS comment FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n WHERE " . $this->getTableWhereClause($table, 'c', 'n') . ' AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid AND n.oid = c.relnamespace ORDER BY a.attnum'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = ''; if ($foreignKey->hasOption('match')) { $query .= ' MATCH ' . $foreignKey->getOption('match'); } $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) { $query .= ' DEFERRABLE'; } else { $query .= ' NOT DEFERRABLE'; } if ( ($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false) || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) ) { $query .= ' INITIALLY DEFERRED'; } else { $query .= ' INITIALLY IMMEDIATE'; } return $query; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = []; $commentsSQL = []; $columnSql = []; $table = $diff->getOldTable() ?? $diff->getName($this); $tableNameSQL = $table->getQuotedName($this); foreach ($diff->getAddedColumns() as $addedColumn) { if ($this->onSchemaAlterTableAddColumn($addedColumn, $diff, $columnSql)) { continue; } $query = 'ADD ' . $this->getColumnDeclarationSQL( $addedColumn->getQuotedName($this), $addedColumn->toArray(), ); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; $comment = $this->getColumnComment($addedColumn); if ($comment === null || $comment === '') { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $addedColumn->getQuotedName($this), $comment, ); } foreach ($diff->getDroppedColumns() as $droppedColumn) { if ($this->onSchemaAlterTableRemoveColumn($droppedColumn, $diff, $columnSql)) { continue; } $query = 'DROP ' . $droppedColumn->getQuotedName($this); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } if ($this->isUnchangedBinaryColumn($columnDiff)) { continue; } $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); $newColumn = $columnDiff->getNewColumn(); $oldColumnName = $oldColumn->getQuotedName($this); if ( $columnDiff->hasTypeChanged() || $columnDiff->hasPrecisionChanged() || $columnDiff->hasScaleChanged() || $columnDiff->hasFixedChanged() ) { $type = $newColumn->getType(); // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type $columnDefinition = $newColumn->toArray(); $columnDefinition['autoincrement'] = false; // here was a server version check before, but DBAL API does not support this anymore. $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSQLDeclaration($columnDefinition, $this); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } if ($columnDiff->hasDefaultChanged()) { $defaultClause = $newColumn->getDefault() === null ? ' DROP DEFAULT' : ' SET' . $this->getDefaultValueDeclarationSQL($newColumn->toArray()); $query = 'ALTER ' . $oldColumnName . $defaultClause; $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } if ($columnDiff->hasNotNullChanged()) { $query = 'ALTER ' . $oldColumnName . ' ' . ($newColumn->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL'; $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } if ($columnDiff->hasAutoIncrementChanged()) { if ($newColumn->getAutoincrement()) { // add autoincrement $seqName = $this->getIdentitySequenceName( $table->getName(), $oldColumnName, ); $sql[] = 'CREATE SEQUENCE ' . $seqName; $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ') FROM ' . $tableNameSQL . '))'; $query = 'ALTER ' . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')"; } else { // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have $query = 'ALTER ' . $oldColumnName . ' DROP DEFAULT'; } $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } $oldComment = $this->getOldColumnComment($columnDiff); $newComment = $this->getColumnComment($newColumn); if ( $columnDiff->hasCommentChanged() || ($columnDiff->getOldColumn() !== null && $oldComment !== $newComment) ) { $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $newColumn->getQuotedName($this), $newComment, ); } if (! $columnDiff->hasLengthChanged()) { continue; } $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $newColumn->getType()->getSQLDeclaration($newColumn->toArray(), $this); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); } $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { $sql = array_merge($sql, $commentsSQL); $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $sql[] = sprintf( 'ALTER TABLE %s RENAME TO %s', $tableNameSQL, $newName->getQuotedName($this), ); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff), ); } return array_merge($sql, $tableSql, $columnSql); } /** * Checks whether a given column diff is a logically unchanged binary type column. * * Used to determine whether a column alteration for a binary type column can be skipped. * Doctrine's {@see BinaryType} and {@see BlobType} are mapped to the same database column type on this platform * as this platform does not have a native VARBINARY/BINARY column type. Therefore the comparator * might detect differences for binary type columns which do not have to be propagated * to database as there actually is no difference at database level. */ private function isUnchangedBinaryColumn(ColumnDiff $columnDiff): bool { $newColumnType = $columnDiff->getNewColumn()->getType(); if (! $newColumnType instanceof BinaryType && ! $newColumnType instanceof BlobType) { return false; } $oldColumn = $columnDiff->getOldColumn() instanceof Column ? $columnDiff->getOldColumn() : null; if ($oldColumn !== null) { $oldColumnType = $oldColumn->getType(); if (! $oldColumnType instanceof BinaryType && ! $oldColumnType instanceof BlobType) { return false; } return count(array_diff($columnDiff->changedProperties, ['type', 'length', 'fixed'])) === 0; } if ($columnDiff->hasTypeChanged()) { return false; } return count(array_diff($columnDiff->changedProperties, ['length', 'fixed'])) === 0; } /** * {@inheritDoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { if (strpos($tableName, '.') !== false) { [$schema] = explode('.', $tableName); $oldIndexName = $schema . '.' . $oldIndexName; } return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)]; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getCommentOnColumnSQL($tableName, $columnName, $comment) { $tableName = new Identifier($tableName); $columnName = new Identifier($columnName); $comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment); return sprintf( 'COMMENT ON COLUMN %s.%s IS %s', $tableName->getQuotedName($this), $columnName->getQuotedName($this), $comment, ); } /** * {@inheritDoc} */ public function getCreateSequenceSQL(Sequence $sequence) { return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize() . ' MINVALUE ' . $sequence->getInitialValue() . ' START ' . $sequence->getInitialValue() . $this->getSequenceCacheSQL($sequence); } /** * {@inheritDoc} */ public function getAlterSequenceSQL(Sequence $sequence) { return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize() . $this->getSequenceCacheSQL($sequence); } /** * Cache definition for sequences */ private function getSequenceCacheSQL(Sequence $sequence): string { if ($sequence->getCache() > 1) { return ' CACHE ' . $sequence->getCache(); } return ''; } /** * {@inheritDoc} */ public function getDropSequenceSQL($sequence) { return parent::getDropSequenceSQL($sequence) . ' CASCADE'; } /** * {@inheritDoc} */ public function getDropForeignKeySQL($foreignKey, $table) { return $this->getDropConstraintSQL($foreignKey, $table); } /** * {@inheritDoc} */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index && $index->isPrimary() && $table !== null) { $constraintName = $index->getName() === 'primary' ? $this->tableName($table) . '_pkey' : $index->getName(); return $this->getDropConstraintSQL($constraintName, $table); } if ($index === '"primary"' && $table !== null) { $constraintName = $this->tableName($table) . '_pkey'; return $this->getDropConstraintSQL($constraintName, $table); } return parent::getDropIndexSQL($index, $table); } /** * @param Table|string|null $table * * @return string */ private function tableName($table) { return $table instanceof Table ? $table->getName() : (string) $table; } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['primary']) && ! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } $unlogged = isset($options['unlogged']) && $options['unlogged'] === true ? ' UNLOGGED' : ''; $query = 'CREATE' . $unlogged . ' TABLE ' . $name . ' (' . $queryFields . ')'; $sql = [$query]; if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index) { $sql[] = $this->getCreateIndexSQL($index, $name); } } if (isset($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $uniqueConstraint) { $sql[] = $this->getCreateConstraintSQL($uniqueConstraint, $name); } } if (isset($options['foreignKeys'])) { foreach ($options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } return $sql; } /** * Converts a single boolean value. * * First converts the value to its native PHP boolean type * and passes it to the given callback function to be reconverted * into any custom representation. * * @param mixed $value The value to convert. * @param callable $callback The callback function to use for converting the real boolean value. * * @return mixed * * @throws UnexpectedValueException */ private function convertSingleBooleanValue($value, $callback) { if ($value === null) { return $callback(null); } if (is_bool($value) || is_numeric($value)) { return $callback((bool) $value); } if (! is_string($value)) { return $callback(true); } /** * Better safe than sorry: http://php.net/in_array#106319 */ if (in_array(strtolower(trim($value)), $this->booleanLiterals['false'], true)) { return $callback(false); } if (in_array(strtolower(trim($value)), $this->booleanLiterals['true'], true)) { return $callback(true); } throw new UnexpectedValueException(sprintf("Unrecognized boolean literal '%s'", $value)); } /** * Converts one or multiple boolean values. * * First converts the value(s) to their native PHP boolean type * and passes them to the given callback function to be reconverted * into any custom representation. * * @param mixed $item The value(s) to convert. * @param callable $callback The callback function to use for converting the real boolean value(s). * * @return mixed */ private function doConvertBooleans($item, $callback) { if (is_array($item)) { foreach ($item as $key => $value) { $item[$key] = $this->convertSingleBooleanValue($value, $callback); } return $item; } return $this->convertSingleBooleanValue($item, $callback); } /** * {@inheritDoc} * * Postgres wants boolean values converted to the strings 'true'/'false'. */ public function convertBooleans($item) { if (! $this->useBooleanTrueFalseStrings) { return parent::convertBooleans($item); } return $this->doConvertBooleans( $item, /** @param mixed $value */ static function ($value) { if ($value === null) { return 'NULL'; } return $value === true ? 'true' : 'false'; }, ); } /** * {@inheritDoc} */ public function convertBooleansToDatabaseValue($item) { if (! $this->useBooleanTrueFalseStrings) { return parent::convertBooleansToDatabaseValue($item); } return $this->doConvertBooleans( $item, /** @param mixed $value */ static function ($value): ?int { return $value === null ? null : (int) $value; }, ); } /** * {@inheritDoc} * * @param T $item * * @return (T is null ? null : bool) * * @template T */ public function convertFromBoolean($item) { if ($item !== null && in_array(strtolower($item), $this->booleanLiterals['false'], true)) { return false; } return parent::convertFromBoolean($item); } /** * {@inheritDoc} */ public function getSequenceNextValSQL($sequence) { return "SELECT NEXTVAL('" . $sequence . "')"; } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'BOOLEAN'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { if (! empty($column['autoincrement'])) { return 'SERIAL'; } return 'INT'; } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { if (! empty($column['autoincrement'])) { return 'BIGSERIAL'; } return 'BIGINT'; } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { if (! empty($column['autoincrement'])) { return 'SMALLSERIAL'; } return 'SMALLINT'; } /** * {@inheritDoc} */ public function getGuidTypeDeclarationSQL(array $column) { return 'UUID'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0) WITHOUT TIME ZONE'; } /** * {@inheritDoc} */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0) WITH TIME ZONE'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME(0) WITHOUT TIME ZONE'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { return ''; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return 'BYTEA'; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'TEXT'; } /** * {@inheritDoc} */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4749', 'PostgreSQLPlatform::getName() is deprecated. Identify platforms by their class.', ); return 'postgresql'; } /** * {@inheritDoc} */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:sO'; } /** * {@inheritDoc} */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); $sql = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); if ($cascade) { $sql .= ' CASCADE'; } return $sql; } /** * Get the snippet used to retrieve the default value for a given column */ public function getDefaultColumnValueSQLSnippet(): string { return <<<'SQL' SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE c.oid = pg_attrdef.adrelid AND pg_attrdef.adnum=a.attnum SQL; } /** * {@inheritDoc} */ public function getReadLockSQL() { return 'FOR SHARE'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'bigint' => Types::BIGINT, 'bigserial' => Types::BIGINT, 'bool' => Types::BOOLEAN, 'boolean' => Types::BOOLEAN, 'bpchar' => Types::STRING, 'bytea' => Types::BLOB, 'char' => Types::STRING, 'date' => Types::DATE_MUTABLE, 'datetime' => Types::DATETIME_MUTABLE, 'decimal' => Types::DECIMAL, 'double' => Types::FLOAT, 'double precision' => Types::FLOAT, 'float' => Types::FLOAT, 'float4' => Types::FLOAT, 'float8' => Types::FLOAT, 'inet' => Types::STRING, 'int' => Types::INTEGER, 'int2' => Types::SMALLINT, 'int4' => Types::INTEGER, 'int8' => Types::BIGINT, 'integer' => Types::INTEGER, 'interval' => Types::STRING, 'json' => Types::JSON, 'jsonb' => Types::JSON, 'money' => Types::DECIMAL, 'numeric' => Types::DECIMAL, 'serial' => Types::INTEGER, 'serial4' => Types::INTEGER, 'serial8' => Types::BIGINT, 'real' => Types::FLOAT, 'smallint' => Types::SMALLINT, 'text' => Types::TEXT, 'time' => Types::TIME_MUTABLE, 'timestamp' => Types::DATETIME_MUTABLE, 'timestamptz' => Types::DATETIMETZ_MUTABLE, 'timetz' => Types::TIME_MUTABLE, 'tsvector' => Types::TEXT, 'uuid' => Types::GUID, 'varchar' => Types::STRING, 'year' => Types::DATE_MUTABLE, '_varchar' => Types::STRING, ]; } /** * {@inheritDoc} * * @deprecated */ public function getVarcharMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'PostgreSQLPlatform::getVarcharMaxLength() is deprecated.', ); return 65535; } /** * {@inheritDoc} */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'PostgreSQLPlatform::getBinaryMaxLength() is deprecated.', ); return 0; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryDefaultLength() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length is deprecated, specify the length explicitly.', ); return 0; } /** * {@inheritDoc} * * @deprecated */ public function hasNativeJsonType() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'PostgreSQLPlatform::getReservedKeywordsClass() is deprecated,' . ' use PostgreSQLPlatform::createReservedKeywordsList() instead.', ); return Keywords\PostgreSQL94Keywords::class; } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'BYTEA'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getDefaultValueDeclarationSQL($column) { if (isset($column['autoincrement']) && $column['autoincrement'] === true) { return ''; } return parent::getDefaultValueDeclarationSQL($column); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsColumnCollation() { return true; } /** * {@inheritDoc} */ public function getJsonTypeDeclarationSQL(array $column) { if (! empty($column['jsonb'])) { return 'JSONB'; } return 'JSON'; } private function getOldColumnComment(ColumnDiff $columnDiff): ?string { $oldColumn = $columnDiff->getOldColumn(); if ($oldColumn !== null) { return $this->getColumnComment($oldColumn); } return null; } /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ public function getListTableMetadataSQL(string $table, ?string $schema = null): string { if ($schema !== null) { $table = $schema . '.' . $table; } return sprintf( <<<'SQL' SELECT obj_description(%s::regclass) AS table_comment; SQL , $this->quoteStringLiteral($table), ); } public function createSchemaManager(Connection $connection): PostgreSQLSchemaManager { return new PostgreSQLSchemaManager($connection, $this); } } Platforms/MySQL80Platform.php 0000755 00000001657 00000000000 0012023 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\Deprecations\Deprecation; /** * Provides the behavior, features and SQL dialect of the MySQL 8.0 database platform. */ class MySQL80Platform extends MySQL57Platform { /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'MySQL80Platform::getReservedKeywordsClass() is deprecated,' . ' use MySQL80Platform::createReservedKeywordsList() instead.', ); return Keywords\MySQL80Keywords::class; } public function createSelectSQLBuilder(): SelectSQLBuilder { return AbstractPlatform::createSelectSQLBuilder(); } } Platforms/MariaDb1052Platform.php 0000755 00000001465 00000000000 0012512 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\TableDiff; /** * Provides the behavior, features and SQL dialect of the MariaDB 10.5 database platform. */ class MariaDb1052Platform extends MariaDb1043Platform { /** * {@inheritDoc} */ protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { return []; } /** * {@inheritDoc} */ protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { return []; } /** * {@inheritDoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { return ['ALTER TABLE ' . $tableName . ' RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)]; } } Platforms/PostgreSQL100Platform.php 0000755 00000002245 00000000000 0013124 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Platforms\Keywords\PostgreSQL100Keywords; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\Deprecations\Deprecation; /** * Provides the behavior, features and SQL dialect of the PostgreSQL 10.0 database platform. * * @deprecated This class will be merged with {@see PostgreSQLPlatform} in 4.0 because support for Postgres * releases prior to 10.0 will be dropped. */ class PostgreSQL100Platform extends PostgreSQL94Platform { /** @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass(): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'PostgreSQL100Platform::getReservedKeywordsClass() is deprecated,' . ' use PostgreSQL100Platform::createReservedKeywordsList() instead.', ); return PostgreSQL100Keywords::class; } public function createSelectSQLBuilder(): SelectSQLBuilder { return AbstractPlatform::createSelectSQLBuilder(); } } ColumnCase.php 0000755 00000000655 00000000000 0007260 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Contains portable column case conversions. */ final class ColumnCase { /** * Convert column names to upper case. */ public const UPPER = 1; /** * Convert column names to lower case. */ public const LOWER = 2; /** * This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } } ExpandArrayParameters.php 0000755 00000007373 00000000000 0011475 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\ArrayParameters\Exception\MissingNamedParameter; use Doctrine\DBAL\ArrayParameters\Exception\MissingPositionalParameter; use Doctrine\DBAL\SQL\Parser\Visitor; use Doctrine\DBAL\Types\Type; use function array_fill; use function array_key_exists; use function count; use function implode; use function substr; final class ExpandArrayParameters implements Visitor { /** @var array<int,mixed>|array<string,mixed> */ private array $originalParameters; /** @var array<int,Type|int|string|null>|array<string,Type|int|string|null> */ private array $originalTypes; private int $originalParameterIndex = 0; /** @var list<string> */ private array $convertedSQL = []; /** @var list<mixed> */ private array $convertedParameters = []; /** @var array<int,Type|int|string|null> */ private array $convertedTypes = []; /** * @param array<int, mixed>|array<string, mixed> $parameters * @param array<int,Type|int|string|null>|array<string,Type|int|string|null> $types */ public function __construct(array $parameters, array $types) { $this->originalParameters = $parameters; $this->originalTypes = $types; } public function acceptPositionalParameter(string $sql): void { $index = $this->originalParameterIndex; if (! array_key_exists($index, $this->originalParameters)) { throw MissingPositionalParameter::new($index); } $this->acceptParameter($index, $this->originalParameters[$index]); $this->originalParameterIndex++; } public function acceptNamedParameter(string $sql): void { $name = substr($sql, 1); if (! array_key_exists($name, $this->originalParameters)) { throw MissingNamedParameter::new($name); } $this->acceptParameter($name, $this->originalParameters[$name]); } public function acceptOther(string $sql): void { $this->convertedSQL[] = $sql; } public function getSQL(): string { return implode('', $this->convertedSQL); } /** @return list<mixed> */ public function getParameters(): array { return $this->convertedParameters; } /** * @param int|string $key * @param mixed $value */ private function acceptParameter($key, $value): void { if (! isset($this->originalTypes[$key])) { $this->convertedSQL[] = '?'; $this->convertedParameters[] = $value; return; } $type = $this->originalTypes[$key]; if ( $type !== ArrayParameterType::INTEGER && $type !== ArrayParameterType::STRING && $type !== ArrayParameterType::ASCII && $type !== ArrayParameterType::BINARY ) { $this->appendTypedParameter([$value], $type); return; } if (count($value) === 0) { $this->convertedSQL[] = 'NULL'; return; } $this->appendTypedParameter($value, ArrayParameterType::toElementParameterType($type)); } /** @return array<int,Type|int|string|null> */ public function getTypes(): array { return $this->convertedTypes; } /** * @param list<mixed> $values * @param Type|int|string|null $type */ private function appendTypedParameter(array $values, $type): void { $this->convertedSQL[] = implode(', ', array_fill(0, count($values), '?')); $index = count($this->convertedParameters); foreach ($values as $value) { $this->convertedParameters[] = $value; $this->convertedTypes[$index] = $type; $index++; } } } FetchMode.php 0000755 00000000646 00000000000 0007065 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Legacy Class that keeps BC for using the legacy APIs fetch()/fetchAll(). * * @deprecated Use the dedicated fetch*() methods for the desired fetch mode instead. */ class FetchMode { /** @link PDO::FETCH_ASSOC */ public const ASSOCIATIVE = 2; /** @link PDO::FETCH_NUM */ public const NUMERIC = 3; /** @link PDO::FETCH_COLUMN */ public const COLUMN = 7; } ParameterType.php 0000755 00000001726 00000000000 0010011 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Contains statement parameter types. */ final class ParameterType { /** * Represents the SQL NULL data type. */ public const NULL = 0; /** * Represents the SQL INTEGER data type. */ public const INTEGER = 1; /** * Represents the SQL CHAR, VARCHAR, or other string data type. * * @see \PDO::PARAM_STR */ public const STRING = 2; /** * Represents the SQL large object data type. */ public const LARGE_OBJECT = 3; /** * Represents a boolean data type. * * @see \PDO::PARAM_BOOL */ public const BOOLEAN = 5; /** * Represents a binary string data type. */ public const BINARY = 16; /** * Represents an ASCII string data type */ public const ASCII = 17; /** * This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } } TransactionIsolationLevel.php 0000755 00000001131 00000000000 0012354 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL; final class TransactionIsolationLevel { /** * Transaction isolation level READ UNCOMMITTED. */ public const READ_UNCOMMITTED = 1; /** * Transaction isolation level READ COMMITTED. */ public const READ_COMMITTED = 2; /** * Transaction isolation level REPEATABLE READ. */ public const REPEATABLE_READ = 3; /** * Transaction isolation level SERIALIZABLE. */ public const SERIALIZABLE = 4; /** @codeCoverageIgnore */ private function __construct() { } } VersionAwarePlatformDriver.php 0000755 00000002027 00000000000 0012510 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Contract for a driver that is able to create platform instances by version. * * Doctrine uses different platform classes for different vendor versions to * support the correct features and SQL syntax of each version. * This interface should be implemented by drivers that are capable to do this * distinction. * * @deprecated All drivers will have to be aware of the server version in the next major release. */ interface VersionAwarePlatformDriver extends Driver { /** * Factory method for creating the appropriate platform instance for the given version. * * @param string $version The platform/server version string to evaluate. This should be given in the notation * the underlying database vendor uses. * * @return AbstractPlatform * * @throws Exception If the given version string could not be evaluated. */ public function createDatabasePlatformForVersion($version); } Driver.php 0000755 00000003174 00000000000 0006461 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Driver\Connection as DriverConnection; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; use SensitiveParameter; /** * Driver interface. * Interface that all DBAL drivers must implement. * * @psalm-import-type Params from DriverManager */ interface Driver { /** * Attempts to create a connection with the database. * * @param array<string, mixed> $params All connection parameters. * @psalm-param Params $params All connection parameters. * * @return DriverConnection The database connection. * * @throws Exception */ public function connect( #[SensitiveParameter] array $params ); /** * Gets the DatabasePlatform instance that provides all the metadata about * the platform this driver connects to. * * @return AbstractPlatform The database platform. */ public function getDatabasePlatform(); /** * Gets the SchemaManager that can be used to inspect and change the underlying * database schema of the platform this driver connects to. * * @deprecated Use {@link AbstractPlatform::createSchemaManager()} instead. * * @return AbstractSchemaManager */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform); /** * Gets the ExceptionConverter that can be used to convert driver-level exceptions into DBAL exceptions. */ public function getExceptionConverter(): ExceptionConverter; } Tools/DsnParser.php 0000755 00000015115 00000000000 0010225 0 ustar 00 <?php namespace Doctrine\DBAL\Tools; use Doctrine\DBAL\Driver; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception\MalformedDsnException; use SensitiveParameter; use function array_merge; use function assert; use function is_a; use function is_string; use function parse_str; use function parse_url; use function preg_replace; use function rawurldecode; use function str_replace; use function strpos; use function substr; /** @psalm-import-type Params from DriverManager */ final class DsnParser { /** @var array<string, string|class-string<Driver>> */ private array $schemeMapping; /** @param array<string, string|class-string<Driver>> $schemeMapping An array used to map DSN schemes to DBAL drivers */ public function __construct(array $schemeMapping = []) { $this->schemeMapping = $schemeMapping; } /** * @psalm-return Params * * @throws MalformedDsnException */ public function parse( #[SensitiveParameter] string $dsn ): array { // (pdo-)?sqlite3?:///... => (pdo-)?sqlite3?://localhost/... or else the URL will be invalid $url = preg_replace('#^((?:pdo-)?sqlite3?):///#', '$1://localhost/', $dsn); assert($url !== null); $url = parse_url($url); if ($url === false) { throw MalformedDsnException::new(); } foreach ($url as $param => $value) { if (! is_string($value)) { continue; } $url[$param] = rawurldecode($value); } $params = []; if (isset($url['scheme'])) { $params['driver'] = $this->parseDatabaseUrlScheme($url['scheme']); } if (isset($url['host'])) { $params['host'] = $url['host']; } if (isset($url['port'])) { $params['port'] = $url['port']; } if (isset($url['user'])) { $params['user'] = $url['user']; } if (isset($url['pass'])) { $params['password'] = $url['pass']; } if (isset($params['driver']) && is_a($params['driver'], Driver::class, true)) { $params['driverClass'] = $params['driver']; unset($params['driver']); } $params = $this->parseDatabaseUrlPath($url, $params); $params = $this->parseDatabaseUrlQuery($url, $params); return $params; } /** * Parses the given connection URL and resolves the given connection parameters. * * Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters * via {@see parseDatabaseUrlScheme}. * * @see parseDatabaseUrlScheme * * @param mixed[] $url The URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private function parseDatabaseUrlPath(array $url, array $params): array { if (! isset($url['path'])) { return $params; } $url['path'] = $this->normalizeDatabaseUrlPath($url['path']); // If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate // and therefore treat the path as a regular DBAL connection URL path. if (! isset($params['driver'])) { return $this->parseRegularDatabaseUrlPath($url, $params); } if (strpos($params['driver'], 'sqlite') !== false) { return $this->parseSqliteDatabaseUrlPath($url, $params); } return $this->parseRegularDatabaseUrlPath($url, $params); } /** * Normalizes the given connection URL path. * * @return string The normalized connection URL path */ private function normalizeDatabaseUrlPath(string $urlPath): string { // Trim leading slash from URL path. return substr($urlPath, 1); } /** * Parses the query part of the given connection URL and resolves the given connection parameters. * * @param mixed[] $url The connection URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private function parseDatabaseUrlQuery(array $url, array $params): array { if (! isset($url['query'])) { return $params; } $query = []; parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode return array_merge($params, $query); // parse_str wipes existing array elements } /** * Parses the given regular connection URL and resolves the given connection parameters. * * Assumes that the "path" URL part is already normalized via {@see normalizeDatabaseUrlPath}. * * @see normalizeDatabaseUrlPath * * @param mixed[] $url The regular connection URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private function parseRegularDatabaseUrlPath(array $url, array $params): array { $params['dbname'] = $url['path']; return $params; } /** * Parses the given SQLite connection URL and resolves the given connection parameters. * * Assumes that the "path" URL part is already normalized via {@see normalizeDatabaseUrlPath}. * * @see normalizeDatabaseUrlPath * * @param mixed[] $url The SQLite connection URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private function parseSqliteDatabaseUrlPath(array $url, array $params): array { if ($url['path'] === ':memory:') { $params['memory'] = true; return $params; } $params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key return $params; } /** * Parses the scheme part from given connection URL and resolves the given connection parameters. * * @return string The resolved driver. */ private function parseDatabaseUrlScheme(string $scheme): string { // URL schemes must not contain underscores, but dashes are ok $driver = str_replace('-', '_', $scheme); // If the driver is an alias (e.g. "postgres"), map it to the actual name ("pdo-pgsql"). // Otherwise, let checkParams decide later if the driver exists. return $this->schemeMapping[$driver] ?? $driver; } } Tools/Console/ConnectionNotFound.php 0000755 00000000212 00000000000 0013472 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console; use OutOfBoundsException; final class ConnectionNotFound extends OutOfBoundsException { } Tools/Console/ConnectionProvider.php 0000755 00000000504 00000000000 0013534 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console; use Doctrine\DBAL\Connection; interface ConnectionProvider { public function getDefaultConnection(): Connection; /** @throws ConnectionNotFound in case a connection with the given name does not exist. */ public function getConnection(string $name): Connection; } Tools/Console/ConnectionProvider/SingleConnectionProvider.php 0000755 00000001715 00000000000 0020515 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console\ConnectionProvider; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Tools\Console\ConnectionNotFound; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use function sprintf; class SingleConnectionProvider implements ConnectionProvider { private Connection $connection; private string $defaultConnectionName; public function __construct(Connection $connection, string $defaultConnectionName = 'default') { $this->connection = $connection; $this->defaultConnectionName = $defaultConnectionName; } public function getDefaultConnection(): Connection { return $this->connection; } public function getConnection(string $name): Connection { if ($name !== $this->defaultConnectionName) { throw new ConnectionNotFound(sprintf('Connection with name "%s" does not exist.', $name)); } return $this->connection; } } Tools/Console/ConsoleRunner.php 0000755 00000004622 00000000000 0012523 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console; use Composer\InstalledVersions; use Doctrine\DBAL\Tools\Console\Command\ReservedWordsCommand; use Doctrine\DBAL\Tools\Console\Command\RunSqlCommand; use Exception; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use function assert; /** * Handles running the Console Tools inside Symfony Console context. * * @deprecated Use Symfony Console documentation to bootstrap a command-line application. */ class ConsoleRunner { /** * Runs console with the given connection provider. * * @param Command[] $commands * * @return void * * @throws Exception */ public static function run(ConnectionProvider $connectionProvider, $commands = []) { $version = InstalledVersions::getVersion('doctrine/dbal'); assert($version !== null); $cli = new Application('Doctrine Command Line Interface', $version); $cli->setCatchExceptions(true); self::addCommands($cli, $connectionProvider); $cli->addCommands($commands); $cli->run(); } /** @return void */ public static function addCommands(Application $cli, ConnectionProvider $connectionProvider) { $cli->addCommands([ new RunSqlCommand($connectionProvider), new ReservedWordsCommand($connectionProvider), ]); } /** * Prints the instructions to create a configuration file * * @deprecated This method will be removed without replacement. * * @return void */ public static function printCliConfigTemplate() { echo <<<'HELP' You are missing a "cli-config.php" or "config/cli-config.php" file in your project, which is required to get the Doctrine-DBAL Console working. You can use the following sample as a template: <?php use Doctrine\DBAL\Tools\Console\ConnectionProvider\SingleConnectionProvider; // You can append new commands to $commands array, if needed // replace with the mechanism to retrieve DBAL connection(s) in your app // and return a Doctrine\DBAL\Tools\Console\ConnectionProvider instance. $connection = getDBALConnection(); // in case you have a single connection you can use SingleConnectionProvider // otherwise you need to implement the Doctrine\DBAL\Tools\Console\ConnectionProvider interface with your custom logic return new SingleConnectionProvider($connection); HELP; } } Tools/Console/Command/CommandCompatibility.php 0000755 00000001550 00000000000 0015412 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Tools\Console\Command; use ReflectionMethod; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; if ((new ReflectionMethod(Command::class, 'execute'))->hasReturnType()) { /** @internal */ trait CommandCompatibility { protected function execute(InputInterface $input, OutputInterface $output): int { return $this->doExecute($input, $output); } } } else { /** @internal */ trait CommandCompatibility { /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { return $this->doExecute($input, $output); } } } Tools/Console/Command/RunSqlCommand.php 0000755 00000007323 00000000000 0014031 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console\Command; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use RuntimeException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function array_keys; use function assert; use function is_bool; use function is_string; use function sprintf; use function stripos; /** * Task for executing arbitrary SQL that can come from a file or directly from * the command line. */ class RunSqlCommand extends Command { use CommandCompatibility; private ConnectionProvider $connectionProvider; public function __construct(ConnectionProvider $connectionProvider) { parent::__construct(); $this->connectionProvider = $connectionProvider; } /** @return void */ protected function configure() { $this ->setName('dbal:run-sql') ->setDescription('Executes arbitrary SQL directly from the command line.') ->setDefinition([ new InputOption('connection', null, InputOption::VALUE_REQUIRED, 'The named database connection'), new InputArgument('sql', InputArgument::REQUIRED, 'The SQL statement to execute.'), new InputOption('depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of result set (deprecated).'), new InputOption('force-fetch', null, InputOption::VALUE_NONE, 'Forces fetching the result.'), ]) ->setHelp(<<<'EOT' The <info>%command.name%</info> command executes the given SQL query and outputs the results: <info>php %command.full_name% "SELECT * FROM users"</info> EOT); } /** @throws Exception */ private function doExecute(InputInterface $input, OutputInterface $output): int { $conn = $this->getConnection($input); $io = new SymfonyStyle($input, $output); $sql = $input->getArgument('sql'); if ($sql === null) { throw new RuntimeException("Argument 'SQL' is required in order to execute this command correctly."); } assert(is_string($sql)); if ($input->getOption('depth') !== null) { $io->warning('Parameter "depth" is deprecated and has no effect anymore.'); } $forceFetch = $input->getOption('force-fetch'); assert(is_bool($forceFetch)); if (stripos($sql, 'select') === 0 || $forceFetch) { $this->runQuery($io, $conn, $sql); } else { $this->runStatement($io, $conn, $sql); } return 0; } private function getConnection(InputInterface $input): Connection { $connectionName = $input->getOption('connection'); assert(is_string($connectionName) || $connectionName === null); if ($connectionName !== null) { return $this->connectionProvider->getConnection($connectionName); } return $this->connectionProvider->getDefaultConnection(); } /** @throws Exception */ private function runQuery(SymfonyStyle $io, Connection $conn, string $sql): void { $resultSet = $conn->fetchAllAssociative($sql); if ($resultSet === []) { $io->success('The query yielded an empty result set.'); return; } $io->table(array_keys($resultSet[0]), $resultSet); } /** @throws Exception */ private function runStatement(SymfonyStyle $io, Connection $conn, string $sql): void { $io->success(sprintf('%d rows affected.', $conn->executeStatement($sql))); } } Tools/Console/Command/ReservedWordsCommand.php 0000755 00000015676 00000000000 0015415 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console\Command; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\Keywords\DB2Keywords; use Doctrine\DBAL\Platforms\Keywords\KeywordList; use Doctrine\DBAL\Platforms\Keywords\MariaDb102Keywords; use Doctrine\DBAL\Platforms\Keywords\MySQL57Keywords; use Doctrine\DBAL\Platforms\Keywords\MySQL80Keywords; use Doctrine\DBAL\Platforms\Keywords\MySQL84Keywords; use Doctrine\DBAL\Platforms\Keywords\MySQLKeywords; use Doctrine\DBAL\Platforms\Keywords\OracleKeywords; use Doctrine\DBAL\Platforms\Keywords\PostgreSQL100Keywords; use Doctrine\DBAL\Platforms\Keywords\PostgreSQL94Keywords; use Doctrine\DBAL\Platforms\Keywords\ReservedKeywordsValidator; use Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords; use Doctrine\DBAL\Platforms\Keywords\SQLServer2012Keywords; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function array_keys; use function assert; use function count; use function implode; use function is_array; use function is_string; /** @deprecated Use database documentation instead. */ class ReservedWordsCommand extends Command { use CommandCompatibility; /** @var array<string,KeywordList> */ private array $keywordLists; private ConnectionProvider $connectionProvider; public function __construct(ConnectionProvider $connectionProvider) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5431', 'ReservedWordsCommand is deprecated. Use database documentation instead.', ); parent::__construct(); $this->connectionProvider = $connectionProvider; $this->keywordLists = [ 'db2' => new DB2Keywords(), 'mariadb102' => new MariaDb102Keywords(), 'mysql' => new MySQLKeywords(), 'mysql57' => new MySQL57Keywords(), 'mysql80' => new MySQL80Keywords(), 'mysql84' => new MySQL84Keywords(), 'oracle' => new OracleKeywords(), 'pgsql' => new PostgreSQL94Keywords(), 'pgsql100' => new PostgreSQL100Keywords(), 'sqlite' => new SQLiteKeywords(), 'sqlserver' => new SQLServer2012Keywords(), ]; } /** * Add or replace a keyword list. */ public function setKeywordList(string $name, KeywordList $keywordList): void { $this->keywordLists[$name] = $keywordList; } /** * If you want to add or replace a keywords list use this command. * * @param string $name * @param class-string<KeywordList> $class * * @return void */ public function setKeywordListClass($name, $class) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'ReservedWordsCommand::setKeywordListClass() is deprecated,' . ' use ReservedWordsCommand::setKeywordList() instead.', ); $this->keywordLists[$name] = new $class(); } /** @return void */ protected function configure() { $this ->setName('dbal:reserved-words') ->setDescription('Checks if the current database contains identifiers that are reserved.') ->setDefinition([ new InputOption('connection', null, InputOption::VALUE_REQUIRED, 'The named database connection'), new InputOption( 'list', 'l', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Keyword-List name.', ), ]) ->setHelp(<<<'EOT' Checks if the current database contains tables and columns with names that are identifiers in this dialect or in other SQL dialects. By default all supported platform keywords are checked: <info>%command.full_name%</info> If you want to check against specific dialects you can pass them to the command: <info>%command.full_name% -l mysql -l pgsql</info> The following keyword lists are currently shipped with Doctrine: * db2 * mariadb102 * mysql * mysql57 * mysql80 * mysql84 * oracle * pgsql * pgsql100 * sqlite * sqlserver EOT); } /** @throws Exception */ private function doExecute(InputInterface $input, OutputInterface $output): int { $output->writeln( '<comment>The <info>dbal:reserved-words</info> command is deprecated.</comment>' . ' Use the documentation on the used database platform(s) instead.', ); $output->writeln(''); $conn = $this->getConnection($input); $keywordLists = $input->getOption('list'); if (is_string($keywordLists)) { $keywordLists = [$keywordLists]; } elseif (! is_array($keywordLists)) { $keywordLists = []; } if (count($keywordLists) === 0) { $keywordLists = array_keys($this->keywordLists); } $keywords = []; foreach ($keywordLists as $keywordList) { if (! isset($this->keywordLists[$keywordList])) { throw new InvalidArgumentException( "There exists no keyword list with name '" . $keywordList . "'. " . 'Known lists: ' . implode(', ', array_keys($this->keywordLists)), ); } $keywords[] = $this->keywordLists[$keywordList]; } $output->write( 'Checking keyword violations for <comment>' . implode(', ', $keywordLists) . '</comment>...', true, ); $schema = $conn->getSchemaManager()->introspectSchema(); $visitor = new ReservedKeywordsValidator($keywords); $schema->visit($visitor); $violations = $visitor->getViolations(); if (count($violations) !== 0) { $output->write( 'There are <error>' . count($violations) . '</error> reserved keyword violations' . ' in your database schema:', true, ); foreach ($violations as $violation) { $output->write(' - ' . $violation, true); } return 1; } $output->write('No reserved keywords violations have been found!', true); return 0; } private function getConnection(InputInterface $input): Connection { $connectionName = $input->getOption('connection'); assert(is_string($connectionName) || $connectionName === null); if ($connectionName !== null) { return $this->connectionProvider->getConnection($connectionName); } return $this->connectionProvider->getDefaultConnection(); } } Exception.php 0000755 00000000274 00000000000 0007162 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk; /** * An exception originating from the Pheanstalk package. * @internal * @extensible */ class Exception extends \Exception { } Contract/ResponseInterface.php 0000755 00000000223 00000000000 0012412 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; /** * A response from the beanstalkd server. */ interface ResponseInterface { } Contract/PheanstalkSubscriberInterface.php 0000755 00000005543 00000000000 0014744 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; use Pheanstalk\Values\Job; use Pheanstalk\Values\TubeList; use Pheanstalk\Values\TubeName; interface PheanstalkSubscriberInterface { /** * Permanently deletes a job. */ public function delete(JobIdInterface $job): void; /** * Remove the specified tube from the watchlist. * @return int The number of watched tubes for the connection */ public function ignore(TubeName $tube): int; /** * The names of the tubes being watched, to reserve jobs from. * * Returns the watchlist, always queries the server */ public function listTubesWatched(): TubeList; /** * Puts a reserved job back into the ready queue. * * Marks the jobs state as "ready" to be run by any client. * It is normally used when the job fails because of a transitory error. * * @param JobIdInterface $job * @param int $priority From 0 (most urgent) to 0xFFFFFFFF (least urgent) * @param int $delay Seconds to wait before job becomes ready */ public function release( JobIdInterface $job, int $priority = PheanstalkPublisherInterface::DEFAULT_PRIORITY, int $delay = PheanstalkPublisherInterface::DEFAULT_DELAY ): void; /** * Reserves/locks a ready job in a watched tube. */ public function reserve(): Job; /** * Puts a job into a 'buried' state, revived only by 'kick' command. */ public function bury(JobIdInterface $job, int $priority = PheanstalkPublisherInterface::DEFAULT_PRIORITY): void; /** * Reserves/locks a specific job * @param JobIdInterface $job * @return Job */ public function reserveJob(JobIdInterface $job): Job; /** * Reserves/locks a ready job in a watched tube, uses the 'reserve-with-timeout' instead of 'reserve'. * * A timeout value of 0 will cause the server to immediately return either a * response or TIMED_OUT. A positive value of timeout will limit the amount of * time the client will block on the reserve request until a job becomes * available. * @param int<0, max> $timeout */ public function reserveWithTimeout(int $timeout): ?Job; /** * Allows a worker to request more time to work on a job. * * This is useful for jobs that potentially take a long time, but you still want * the benefits of a TTR pulling a job away from an unresponsive worker. A worker * may periodically tell the server that it's still alive and processing a job * (e.g. it may do this on DEADLINE_SOON). * */ public function touch(JobIdInterface $job): void; /** * Add the specified tube to the watchlist, to reserve jobs from. * @return int The number of watched tubes for the connection */ public function watch(TubeName $tube): int; } Contract/CommandInterface.php 0000755 00000002116 00000000000 0012175 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; use Pheanstalk\Values\Job; use Pheanstalk\Values\JobId; use Pheanstalk\Values\JobStats; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ServerStats; use Pheanstalk\Values\Success; use Pheanstalk\Values\TubeList; use Pheanstalk\Values\TubeName; use Pheanstalk\Values\TubeStats; /** * A command to be sent to the beanstalkd server, and response processing logic. * @internal */ interface CommandInterface { /** * The command line, without trailing CRLF. */ public function getCommandLine(): string; /** * Interprets a raw response object * MUST throw an exception if the parser determines the response to be invalid * MUST return the response type as defined in the PheanstalkInterface for the command type. * If the PheanstalkInterface has return type void, the implementation MUST return EmptySuccessResponse * @param RawResponse $response */ public function interpret(RawResponse $response): int|Job|JobId|TubeName|TubeList|Success|JobStats|TubeStats|ServerStats; } Contract/JobIdResponseInterface.php 0000755 00000000255 00000000000 0013327 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; interface JobIdResponseInterface extends ResponseInterface { public function getId(): JobIdInterface; } Contract/PheanstalkPublisherInterface.php 0000755 00000002214 00000000000 0014566 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; use Pheanstalk\Values\TubeName; interface PheanstalkPublisherInterface { public const DEFAULT_DELAY = 0; // no delay public const DEFAULT_PRIORITY = 1024; // most urgent: 0, least urgent: 4294967295 public const DEFAULT_TTR = 60; // 1 minute /** * The name of the current tube used for publishing jobs to. * Always queries the server */ public function listTubeUsed(): TubeName; /** * Puts a job on the queue. * * @param string $data The job data * @param int $priority From 0 (most urgent) to 0xFFFFFFFF (least urgent) * @param int $delay Seconds to wait before job becomes ready * @param int $timeToRelease Time To Run: seconds a job can be reserved for */ public function put( string $data, int $priority = self::DEFAULT_PRIORITY, int $delay = self::DEFAULT_DELAY, int $timeToRelease = self::DEFAULT_TTR ): JobIdInterface; /** * Change to the specified tube name for publishing jobs to. */ public function useTube(TubeName $tube): void; } Contract/DispatcherInterface.php 0000755 00000000315 00000000000 0012704 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; use Pheanstalk\Values\RawResponse; interface DispatcherInterface { public function dispatch(CommandInterface $command): RawResponse; } Contract/SocketInterface.php 0000755 00000001132 00000000000 0012044 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; interface SocketInterface { /** * Writes data to the socket. */ public function write(string $data): void; /** * Reads up to $length bytes from the socket. * @param int<0, max> $length */ public function read(int $length): string; /** * Reads up to the next new-line. * Trailing whitespace is trimmed. */ public function getLine(): string; /** * Disconnect the socket; subsequent usage of the socket will fail. */ public function disconnect(): void; } Contract/SocketFactoryInterface.php 0000755 00000000735 00000000000 0013404 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; use Pheanstalk\Exception\ConnectionException; interface SocketFactoryInterface { public const DEFAULT_PORT = 11300; /** * This function must return a connected socket that is ready for reading / writing. * @return SocketInterface * @throws ConnectionException when the underlying implementation is not able to create a connection */ public function create(): SocketInterface; } Contract/JobResponseInterface.php 0000755 00000000334 00000000000 0013050 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; /** * A response from the beanstalkd server. */ interface JobResponseInterface extends JobIdResponseInterface { public function getData(): string; } Contract/JobIdInterface.php 0000755 00000000423 00000000000 0011605 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; interface JobIdInterface { /** * This is a string to support 64bit numbers on 32bit systems. * @return string A numeric string representing the ID. */ public function getId(): string; } Contract/PheanstalkManagerInterface.php 0000755 00000004526 00000000000 0014213 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; use Pheanstalk\Values\Job; use Pheanstalk\Values\JobStats; use Pheanstalk\Values\ServerStats; use Pheanstalk\Values\TubeList; use Pheanstalk\Values\TubeName; use Pheanstalk\Values\TubeStats; interface PheanstalkManagerInterface { /** * Kicks buried or delayed jobs into a 'ready' state. * If there are buried jobs, it will kick up to $max of them. * Otherwise, it will kick up to $max delayed jobs. * * @param int $max The maximum jobs to kick * * @return int Number of jobs kicked */ public function kick(int $max): int; /** * A variant of kick that operates with a single job. If the given job * exists and is in a buried or delayed state, it will be moved to the * ready queue of the the same tube where it currently belongs. */ public function kickJob(JobIdInterface $job): void; /** * The names of all tubes on the server. */ public function listTubes(): TubeList; /** * Temporarily prevent jobs being reserved from the given tube. * * @param int $delay Seconds before jobs may be reserved from this queue. */ public function pauseTube(TubeName $tube, int $delay): void; /** * Resume jobs for a given paused tube. */ public function resumeTube(TubeName $tube): void; /** * Inspect a job in the system, regardless of what tube it is in. */ public function peek(JobIdInterface $job): Job; /** * Inspect the next ready job in the currently used tube. */ public function peekReady(): ?Job; /** * Inspect the shortest-remaining-delayed job in the currently used tube. * @return ?Job */ public function peekDelayed(): ?Job; /** * Inspect the next job in the list of buried jobs in the currently used tube. */ public function peekBuried(): ?Job; /** * Gives statistical information about the specified job if it exists. */ public function statsJob(JobIdInterface $job): JobStats; /** * Gives statistical information about the specified tube if it exists. */ public function statsTube(TubeName $tube): TubeStats; /** * Gives statistical information about the beanstalkd system as a whole. */ public function stats(): ServerStats; } Contract/CommandWithDataInterface.php 0000755 00000000643 00000000000 0013626 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Contract; /** * Some commands carry data besides their command line. Commands carrying data implement this interface so that it can * be detected and sent by the dispatching code in Connection */ interface CommandWithDataInterface extends CommandInterface { /** * The binary data to follow the command. */ public function getData(): string; } Socket/FileSocket.php 0000755 00000005706 00000000000 0010511 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Socket; use Pheanstalk\Contract\SocketInterface; use Pheanstalk\Exception\ConnectionException; use Pheanstalk\Values\Timeout; /** * A Socket implementation using the standard file functions. * @internal */ abstract class FileSocket implements SocketInterface { /** * @phpstan-var resource * @psalm-var resource|closed-resource */ private $socket; protected function __construct(mixed $socket, Timeout $receiveTimeout) { if (!is_resource($socket)) { throw new \InvalidArgumentException("A valid resource is required"); } stream_set_timeout($socket, $receiveTimeout->seconds, $receiveTimeout->microSeconds); $this->socket = $socket; } /** * @return resource */ final protected function getSocket() { if (!is_resource($this->socket)) { throw new ConnectionException(0, "The connection was closed"); } return $this->socket; } /** * Writes data to the socket. */ public function write(string $data): void { $socket = $this->getSocket(); $retries = 0; error_clear_last(); while ($data !== "" && $retries < 10) { $written = fwrite($socket, $data); if ($written === false) { $this->throwException(); } elseif ($written === 0) { $retries++; continue; } $data = substr($data, $written); } if ($data !== "") { throw new ConnectionException(0, 'Write failed for 10 attempts'); } } private function throwException(): never { if (null === $error = error_get_last()) { throw new ConnectionException(0, 'Unknown error'); } throw new ConnectionException($error['type'], $error['message']); } /** * Reads up to $length bytes from the socket. * @param int<0, max> $length */ public function read(int $length): string { $socket = $this->getSocket(); $buffer = ''; while (0 < $remaining = $length - mb_strlen($buffer, '8bit')) { $result = fread($socket, $remaining); if ($result === false) { $this->throwException(); } $buffer .= $result; } return $buffer; } /** * Reads up to the next new-line. * Trailing whitespace is trimmed. */ public function getLine(): string { $socket = $this->getSocket(); $result = fgets($socket, 8192); if ($result === false) { $this->throwException(); } return rtrim($result); } /** * Disconnect the socket; subsequent usage of the socket will fail. * @idempotent */ public function disconnect(): void { if (is_resource($this->socket)) { fclose($this->socket); } } } Socket/FsockopenSocket.php 0000755 00000001306 00000000000 0011551 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Socket; use Pheanstalk\Exception\ConnectionException; use Pheanstalk\Values\Timeout; /** * A Socket implementation using the fsockopen * @internal */ final class FsockopenSocket extends FileSocket { public function __construct( string $host, int $port, Timeout $connectTimeout, Timeout $readTimeout ) { $socket = @fsockopen($host, str_starts_with($host, 'unix://') ? -1 : $port, $error, $errorMessage, $connectTimeout->toFloat()); if ($socket === false) { throw new ConnectionException($error, $errorMessage); } parent::__construct($socket, $readTimeout); } } Socket/StreamSocket.php 0000755 00000002113 00000000000 0011052 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Socket; use Pheanstalk\Exception\ConnectionException; use Pheanstalk\Values\Timeout; /** * A Socket implementation using the Streams extension * @internal */ final class StreamSocket extends FileSocket { public function __construct( string $host, int $port, Timeout $connectTimeout, Timeout $receiveTimeout ) { $context = stream_context_create(); if (str_starts_with($host, 'unix://')) { $socket = @stream_socket_client($host, $error, $errorMessage, $connectTimeout->toFloat(), STREAM_CLIENT_CONNECT, $context); } else { $socket = @stream_socket_client( "tcp://$host:$port", $error, $errorMessage, $connectTimeout->toFloat(), STREAM_CLIENT_CONNECT, $context ); } if ($socket === false) { throw new ConnectionException($error, $errorMessage); } parent::__construct($socket, $receiveTimeout); } } Socket/SocketSocket.php 0000755 00000007552 00000000000 0011063 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Socket; use Pheanstalk\Contract\SocketInterface; use Pheanstalk\Exception\ConnectionException; use Pheanstalk\Values\Timeout; use Socket; /** * A Socket implementation using the Sockets extension * @internal */ final class SocketSocket implements SocketInterface { private null|\Socket $socket; public function __construct( string $host, int $port, Timeout $connectTimeout, Timeout $sendTimeout, Timeout $receiveTimeout ) { if (str_starts_with($host, 'unix://')) { $socket = @socket_create(AF_UNIX, SOCK_STREAM, SOL_SOCKET); } else { $socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP); } if ($socket === false) { throw new ConnectionException(0, "Failed to create socket"); } socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1); socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $connectTimeout->toArray()); socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $connectTimeout->toArray()); if (!str_starts_with($host, 'unix://')) { $addresses = gethostbynamel($host); if ($addresses === false) { throw new ConnectionException(0, "Could not resolve hostname $host"); } if (@socket_connect($socket, $addresses[0], $port) === false) { $this->throwException($socket); } } elseif (@socket_connect($socket, substr($host, 7)) === false) { $this->throwException($socket); } socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $sendTimeout->toArray()); socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $receiveTimeout->toArray()); $this->socket = $socket; } /** * Writes data to the socket. */ public function write(string $data): void { $socket = $this->getSocket(); while ($data !== "") { $written = @socket_write($socket, $data); if ($written === false) { $this->throwException($socket); } $data = substr($data, $written); } } private function throwException(Socket $socket): never { $error = socket_last_error($socket); throw new ConnectionException($error, socket_strerror($error)); } private function getSocket(): Socket { if (!isset($this->socket)) { throw new ConnectionException(0, 'The connection was closed'); } return $this->socket; } /** * Reads up to $length bytes from the socket. * * @return string */ public function read(int $length): string { $socket = $this->getSocket(); $buffer = ''; while (mb_strlen($buffer, '8bit') < $length) { $result = @socket_read($socket, $length - mb_strlen($buffer, '8bit')); if ($result === false) { $this->throwException($socket); } $buffer .= $result; } return $buffer; } public function getLine(): string { $socket = $this->getSocket(); $buffer = ''; // Reading stops at \r or \n. In case it stopped at \r we must continue reading. while (!str_ends_with($buffer, "\n")) { $result = @socket_read($socket, 1024, PHP_NORMAL_READ); if ($result === false) { $this->throwException($socket); } $buffer .= $result; } return rtrim($buffer); } /** * Disconnect the socket; subsequent usage of the socket will fail. * This function is idempotent * @idempotent */ public function disconnect(): void { if (isset($this->socket)) { socket_close($this->socket); unset($this->socket); } } } Exception/JobBuriedException.php 0000755 00000000554 00000000000 0012707 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; use Pheanstalk\Contract\JobIdInterface; /** * Indicates that the given job body was buried due to the server being OOM */ final class JobBuriedException extends ClientException { public function __construct(public readonly JobIdInterface $jobId) { parent::__construct(); } } Exception/JobTooBigException.php 0000755 00000000335 00000000000 0012655 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; /** * Indicates that the given job body is larger than the servers configured max-job-size */ final class JobTooBigException extends ClientException { } Exception/ClientException.php 0000755 00000000345 00000000000 0012256 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; use Pheanstalk\Exception; /** * An exception originating from the beanstalkd client. * @internal * @extensible */ class ClientException extends Exception { } Exception/TubeNotFoundException.php 0000755 00000000200 00000000000 0013402 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; final class TubeNotFoundException extends ClientException { } Exception/UnsupportedResponseException.php 0000755 00000000664 00000000000 0015113 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; use Pheanstalk\Values\ResponseType; final class UnsupportedResponseException extends ClientException { public function __construct(ResponseType|null $type = null) { if (isset($type)) { parent::__construct("Response type {$type->name} is not supported for this command"); } else { parent::__construct(); } } } Exception/ServerInternalErrorException.php 0000755 00000000311 00000000000 0015006 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; /** * An exception originating as a beanstalkd server error. */ final class ServerInternalErrorException extends ServerException { } Exception/JobNotFoundException.php 0000755 00000000303 00000000000 0013221 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; /** * Indicates that the given job was not found by the server */ final class JobNotFoundException extends ClientException { } Exception/ExpectedCrlfException.php 0000755 00000000200 00000000000 0013376 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; final class ExpectedCrlfException extends ClientException { } Exception/ServerDrainingException.php 0000755 00000000304 00000000000 0013755 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; /** * An exception originating as a beanstalkd server error. */ final class ServerDrainingException extends ServerException { } Exception/MalformedResponseException.php 0000755 00000002064 00000000000 0014465 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; /** * An exception indicating that the beanstalkd server sent a response in an incorrect or unsupported format */ final class MalformedResponseException extends ClientException { public static function expectedDataAndIntegerArgument(): self { return new self("Expected the response to contain data and an integer argument"); } public static function negativeDataLength(): self { return new self("Data length cannot be negative"); } public static function expectedData(): self { return new self("Expected the response to contain data"); } public static function expectedIntegerArgument(): self { return new self("Argument should be of type integer"); } public static function expectedStringArgument(): self { return new self("Argument should be of type string"); } public static function expectedArgument(): self { return new self("Argument the response to contain an argument"); } } Exception/ServerOutOfMemoryException.php 0000755 00000000307 00000000000 0014452 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; /** * An exception originating as a beanstalkd server error. */ final class ServerOutOfMemoryException extends ServerException { } Exception/DeadlineSoonException.php 0000755 00000000201 00000000000 0013373 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; final class DeadlineSoonException extends ClientException { } Exception/NotIgnoredException.php 0000755 00000000330 00000000000 0013102 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; /** * Exception thrown when a client tries to ignore the only tube in its watch list */ final class NotIgnoredException extends ClientException { } Exception/ServerBadFormatException.php 0000755 00000000514 00000000000 0014064 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; /** * An exception originating as a beanstalkd server error. */ final class ServerBadFormatException extends ServerException { public function __construct(string $commandLine) { parent::__construct("Bad format for command {$commandLine}"); } } Exception/TimedOutException.php 0000755 00000000175 00000000000 0012573 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; final class TimedOutException extends ClientException { } Exception/ServerUnknownCommandException.php 0000755 00000000312 00000000000 0015157 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; /** * An exception originating as a beanstalkd server error. */ final class ServerUnknownCommandException extends ServerException { } Exception/NoImplementationException.php 0000755 00000000204 00000000000 0014314 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Exception; final class NoImplementationException extends ClientException { } PheanstalkPublisher.php 0000755 00000002375 00000000000 0011200 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk; use Pheanstalk\Command\ListTubeUsedCommand; use Pheanstalk\Command\PutCommand; use Pheanstalk\Command\UseCommand; use Pheanstalk\Contract\JobIdInterface; use Pheanstalk\Contract\PheanstalkPublisherInterface; use Pheanstalk\Values\TubeName; /** * Implements the methods in the dispatcher interface. */ final class PheanstalkPublisher implements PheanstalkPublisherInterface { use StaticFactoryTrait; public function listTubeUsed(): TubeName { $command = new ListTubeUsedCommand(); return $command->interpret($this->connection->dispatchCommand($command)); } public function useTube(TubeName $tube): void { $command = new UseCommand($tube); $command->interpret($this->connection->dispatchCommand($command)); } public function put( string $data, int $priority = PheanstalkPublisherInterface::DEFAULT_PRIORITY, int $delay = PheanstalkPublisherInterface::DEFAULT_DELAY, int $timeToRelease = PheanstalkPublisherInterface::DEFAULT_TTR ): JobIdInterface { $command = new PutCommand($data, $priority, $delay, $timeToRelease); return $command->interpret($this->connection->dispatchCommand($command)); } } StaticFactoryTrait.php 0000755 00000001644 00000000000 0011011 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk; use Pheanstalk\Contract\SocketFactoryInterface; use Pheanstalk\Values\Timeout; /** * @internal */ trait StaticFactoryTrait { public function __construct(private readonly Connection $connection) { } /** * Static constructor that uses auto-detection to choose an underlying socket implementation */ public static function create( string $host, int $port = 11300, ?Timeout $connectTimeout = null, ?Timeout $receiveTimeout = null ): self { return self::createWithFactory(new SocketFactory($host, $port, null, $connectTimeout, $receiveTimeout)); } /** * Static constructor that uses a given socket factory for underlying connections */ public static function createWithFactory(SocketFactoryInterface $factory): self { return new self(new Connection($factory)); } } SocketFactory.php 0000755 00000006334 00000000000 0010007 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk; use Pheanstalk\Contract\SocketFactoryInterface; use Pheanstalk\Contract\SocketInterface; use Pheanstalk\Exception\NoImplementationException; use Pheanstalk\Socket\FsockopenSocket; use Pheanstalk\Socket\SocketSocket; use Pheanstalk\Socket\StreamSocket; use Pheanstalk\Values\SocketImplementation; use Pheanstalk\Values\Timeout; final class SocketFactory implements SocketFactoryInterface { private const DEFAULT_TIMEOUT = 10; public readonly SocketImplementation $implementation; private readonly Timeout $connectTimeout; private readonly Timeout $receiveTimeout; private readonly Timeout $sendTimeout; /** * @param string $host * @param int $port * @param SocketImplementation|null $implementation * @param Timeout|null $connectTimeout * @param Timeout|null $receiveTimeout * @param Timeout|null $sendTimeout the timeout used for sending data, not supported by all implementations * @throws NoImplementationException */ public function __construct( private readonly string $host, private readonly int $port = self::DEFAULT_PORT, null|SocketImplementation $implementation = null, ?Timeout $connectTimeout = null, ?Timeout $receiveTimeout = null, ?Timeout $sendTimeout = null, ) { $this->implementation = $implementation ?? $this->detectImplementation(); $this->connectTimeout = $connectTimeout ?? new Timeout(self::DEFAULT_TIMEOUT, 0); $this->receiveTimeout = $receiveTimeout ?? new Timeout(self::DEFAULT_TIMEOUT, 0); $this->sendTimeout = $sendTimeout ?? new Timeout(self::DEFAULT_TIMEOUT, 0); } private function detectImplementation(): SocketImplementation { // Prefer socket if (extension_loaded('sockets')) { return SocketImplementation::SOCKET; } // Then fall back to stream if (function_exists('stream_socket_client')) { return SocketImplementation::STREAM; } // Then fall back to fsockopen if (function_exists('fsockopen')) { return SocketImplementation::FSOCKOPEN; } throw new NoImplementationException(); } private function createStreamSocket(): StreamSocket { return new StreamSocket($this->host, $this->port, $this->connectTimeout, $this->receiveTimeout); } private function createSocketSocket(): SocketSocket { return new SocketSocket($this->host, $this->port, $this->connectTimeout, $this->sendTimeout, $this->receiveTimeout); } private function createFsockopenSocket(): FsockopenSocket { return new FsockopenSocket($this->host, $this->port, $this->connectTimeout, $this->receiveTimeout); } /** * This function must return a connected socket that is ready for reading / writing. * @return SocketInterface */ public function create(): SocketInterface { return match ($this->implementation) { SocketImplementation::SOCKET => $this->createSocketSocket(), SocketImplementation::STREAM => $this->createStreamSocket(), SocketImplementation::FSOCKOPEN => $this->createFsockopenSocket() }; } } Values/ServerStats.php 0000755 00000010330 00000000000 0010742 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; final class ServerStats { private const KEYS = [ 'current-jobs-urgent', 'current-jobs-ready', 'current-jobs-reserved', 'current-jobs-delayed', 'current-jobs-buried', 'cmd-put', 'cmd-peek', 'cmd-peek-ready', 'cmd-peek-delayed', 'cmd-peek-buried', 'cmd-reserve', 'cmd-use', 'cmd-watch', 'cmd-ignore', 'cmd-delete', 'cmd-release', 'cmd-bury', 'cmd-kick', 'cmd-stats', 'cmd-reserve-with-timeout', 'cmd-stats-job', 'cmd-stats-tube', 'cmd-list-tubes', 'cmd-list-tube-used', 'cmd-list-tubes-watched', 'cmd-pause-tube', 'job-timeouts', 'total-jobs', 'max-job-size', 'current-tubes', 'current-connections', 'current-producers', 'current-workers', 'current-waiting', 'total-connections', 'pid', 'version', 'rusage-utime', 'rusage-stime', 'binlog-oldest-index', 'binlog-current-index', 'binlog-max-size', 'binlog-records-written', 'draining', 'id', 'hostname', 'os', 'platform' ]; public function __construct( public readonly int $currentJobsUrgent, public readonly int $currentJobsReady, public readonly int $currentJobsReserved, public readonly int $currentJobsDelayed, public readonly int $currentJobsBuried, public readonly int $cmdPut, public readonly int $cmdPeek, public readonly int $cmdPeekReady, public readonly int $cmdPeekDelayed, public readonly int $cmdReserveWithTimeout, public readonly int $cmdPeekBuried, public readonly int $cmdReserve, public readonly int $cmdUse, public readonly int $cmdWatch, public readonly int $cmdIgnore, public readonly int $cmdDelete, public readonly int $cmdRelease, public readonly int $cmdBury, public readonly int $cmdKick, public readonly int $cmdStats, public readonly int $cmdStatsJob, public readonly int $cmdStatsTube, public readonly int $cmdListTubes, public readonly int $cmdListTubeUsed, public readonly int $cmdListTubesWatched, public readonly int $cmdPauseTube, public readonly int $jobTimeouts, public readonly int $totalJobs, public readonly int $maxJobSize, public readonly int $currentTubes, public readonly int $currentConnections, public readonly int $currentProducers, public readonly int $currentWorkers, public readonly int $currentWaiting, public readonly int $totalConnections, public readonly int $pid, public readonly string $version, public readonly float $rusageUtime, public readonly float $rusageStime, public readonly int $binlogOldestIndex, public readonly int $binlogCurrentIndex, public readonly int $binlogMaxSize, public readonly int $binlogRecordsWritten, public readonly bool $draining, public readonly string $id, public readonly string $hostname, public readonly string $os, public readonly string $platform, ) { } private static function camelize(string $key): string { $parts = explode('-', $key); $result = array_shift($parts); foreach ($parts as $part) { $result .= ucfirst($part); } return $result; } /** * @param array<string, string|int|bool|float> $data * @psalm-suppress ArgumentTypeCoercion */ public static function fromBeanstalkArray(array $data): self { // Check that all expected keys are there foreach (self::KEYS as $key) { if (!isset($data[$key])) { throw new \InvalidArgumentException("Data array is missing expected key $key"); } } $params = []; foreach (self::KEYS as $key) { $params[self::camelize($key)] = $data[$key]; } return new self(...$params); } } Values/JobId.php 0000755 00000001343 00000000000 0007450 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; use Pheanstalk\Contract\JobIdInterface; /** * This class implements a value object for beanstalkd job IDs. */ final class JobId implements JobIdInterface { private readonly string $id; public function __construct(int|string|JobIdInterface $id) { $this->id = match (true) { $id instanceof JobIdInterface => $id->getId(), is_string($id) && ctype_digit($id) => $id, is_int($id) && $id >= 0 => (string) $id, default => throw new \InvalidArgumentException('Id must a numeric string or an integer with value >= 0') }; } public function getId(): string { return $this->id; } } Values/TubeCommandTemplate.php 0000755 00000001130 00000000000 0012345 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; /** * Simple value object that has a very basic render function */ final class TubeCommandTemplate { private const PLACEHOLDER = '{tube}'; public function __construct(private readonly string $value) { if (!str_contains($value, self::PLACEHOLDER)) { throw new \InvalidArgumentException("Tube command template must contain tube name placeholder"); } } public function render(TubeName $tubeName): string { return strtr($this->value, [self::PLACEHOLDER => $tubeName]); } } Values/JobCommandTemplate.php 0000755 00000001254 00000000000 0012167 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; use Pheanstalk\Contract\JobIdInterface; /** * Simple value object that has a very basic render function */ final class JobCommandTemplate { private const PLACEHOLDER = '{id}'; private string $value; public function __construct(string $value) { if (!str_contains($value, self::PLACEHOLDER)) { throw new \InvalidArgumentException("Job command template must contain job id placeholder"); } $this->value = $value; } public function render(JobIdInterface $jobId): string { return strtr($this->value, [self::PLACEHOLDER => $jobId->getId()]); } } Values/Timeout.php 0000755 00000001066 00000000000 0010111 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; final class Timeout { public function __construct(public readonly int $seconds, public readonly int $microSeconds = 0) { } /** * @return array{sec: int, usec: int} Array for usage in socket functions */ public function toArray(): array { return [ 'sec' => $this->seconds, 'usec' => $this->microSeconds ]; } public function toFloat(): float { return $this->seconds + ($this->microSeconds / 1000000); } } Values/Job.php 0000755 00000001246 00000000000 0007175 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; use Pheanstalk\Contract\JobIdInterface; /** * A job in a beanstalkd server. */ final class Job implements JobIdInterface { private readonly JobIdInterface $id; public function __construct( JobIdInterface $id, private readonly string $data ) { $this->id = new JobId($id); } /** * The job ID, unique on the beanstalkd server. */ public function getId(): string { return $this->id->getId(); } /** * The job data. * * @return string */ public function getData(): string { return $this->data; } } Values/JobStats.php 0000755 00000004415 00000000000 0010215 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; final class JobStats { private const KEYS = [ 'id', 'tube', 'state', 'pri', 'age', 'delay', 'ttr', 'time-left', 'file', 'reserves', 'timeouts', 'releases', 'buries', 'kicks' ]; public function __construct( public readonly JobId $id, public readonly TubeName $tube, public readonly JobState $state, public readonly int $priority, public readonly int $age, public readonly int $delay, public readonly int $timeToRelease, public readonly int $timeLeft, public readonly int $file, public readonly int $reserves, public readonly int $timeouts, public readonly int $releases, public readonly int $buries, public readonly int $kicks ) { } /** * @param array<string, scalar> $data * @throws \InvalidArgumentException * @psalm-suppress PossiblyUndefinedArrayOffset * @psalm-suppress ArgumentTypeCoercion * @psalm-suppress PossiblyInvalidArgument */ public static function fromBeanstalkArray(array $data): self { // Check that all expected keys are there foreach (self::KEYS as $key) { if (!isset($data[$key])) { throw new \InvalidArgumentException("Data array is missing expected key $key"); } } $tube = new TubeName($data['tube']); try { $state = JobState::from($data['state']); } catch (\Throwable $t) { throw new \InvalidArgumentException("Invalid value given for state", 0, $t); } $id = new JobId($data['id']); return new self( id: $id, tube: $tube, state: $state, priority: $data['pri'], age: $data['age'], delay: $data['delay'], timeToRelease: $data['ttr'], timeLeft: $data['time-left'], file: $data['file'], reserves: $data['reserves'], timeouts: $data['timeouts'], releases: $data['releases'], buries: $data['buries'], kicks: $data['kicks'] ); } } Values/JobState.php 0000755 00000000315 00000000000 0010172 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; enum JobState: string { case DELAYED = "delayed"; case READY = "ready"; case BURIED = "buried"; case RESERVED = "reserved"; } Values/TubeList.php 0000755 00000001215 00000000000 0010212 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; use ArrayIterator; use Traversable; /** * This class wraps a list of tube names * @codeCoverageIgnore Remove this annotation if any significant functionality gets added to this class. * @implements \IteratorAggregate<int, TubeName> */ final class TubeList implements \IteratorAggregate { /** * @var list<TubeName> */ private readonly array $tubes; public function __construct(TubeName ...$tubes) { $this->tubes = array_values($tubes); } public function getIterator(): Traversable { return new ArrayIterator($this->tubes); } } Values/SocketImplementation.php 0000755 00000000223 00000000000 0012613 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; enum SocketImplementation { case STREAM; case SOCKET; case FSOCKOPEN; } Values/TubeName.php 0000755 00000001450 00000000000 0010160 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; use function mb_strlen; use Stringable; final class TubeName implements Stringable { private const NAME_REGEX = '~^[A-Za-z0-9+/;.$_()][A-Za-z0-9\-+/;.$_()]*$~'; public readonly string $value; public function __construct(int|string $value) { if (is_int($value)) { $value = (string)$value; } if (mb_strlen($value, '8bit') > 200) { throw new \InvalidArgumentException("Tube name must not exceed 200 bytes"); } elseif (preg_match(self::NAME_REGEX, $value) === 0) { throw new \InvalidArgumentException("Invalid tube name format"); } $this->value = $value; } public function __toString(): string { return $this->value; } } Values/Success.php 0000755 00000001023 00000000000 0010064 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; /** * This class indicates a successful operation with no (relevant) data * We don't use `null` for this because we cannot narrow return values to just null. * For example a command that either succeeds or throws an exception would look like this: * `public function interpret(RawResponse $response): null`, which is not valid. * Instead we use this class so that we get: * `public function interpret(RawResponse $response): Success` */ final class Success { } Values/ResponseType.php 0000755 00000002217 00000000000 0011122 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; enum ResponseType: string { // Global errors case OutOfMemory = 'OUT_OF_MEMORY'; case InternalError = 'INTERNAL_ERROR'; case Draining = 'DRAINING'; case BadFormat = 'BAD_FORMAT'; case UnknownCommand = 'UNKNOWN_COMMAND'; case Inserted = 'INSERTED'; case Buried = 'BURIED'; case ExpectedCrlf = 'EXPECTED_CRLF'; case JobTooBig = 'JOB_TOO_BIG'; case Using = 'USING'; case DeadlineSoon = 'DEADLINE_SOON'; case Reserved = 'RESERVED'; case Deleted = 'DELETED'; case NotFound = 'NOT_FOUND'; case Released = 'RELEASED'; case Watching = 'WATCHING'; case NotIgnored = 'NOT_IGNORED'; case Found = 'FOUND'; case Kicked = 'KICKED'; case Ok = 'OK'; case TimedOut = 'TIMED_OUT'; case Touched = 'TOUCHED'; case Paused = 'PAUSED'; /** * @x-codeCoverageIgnore this is configuration and there is no useful way to cover it */ public function hasData(): bool { return match ($this) { self::Reserved, self::Found, self::Ok => true, default => false }; } } Values/TubeStats.php 0000755 00000004627 00000000000 0010407 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; final class TubeStats { private const KEYS = [ 'name', 'current-jobs-urgent', 'current-jobs-ready', 'current-jobs-reserved', 'current-jobs-delayed', 'current-jobs-buried', 'total-jobs', 'current-using', 'current-waiting', 'current-watching', 'pause', 'cmd-delete', 'cmd-pause-tube', 'pause-time-left' ]; public function __construct( public readonly TubeName $name, public readonly int $currentJobsUrgent, public readonly int $currentJobsReady, public readonly int $currentJobsReserved, public readonly int $currentJobsDelayed, public readonly int $currentJobsBuried, public readonly int $totalJobs, public readonly int $currentUsing, public readonly int $currentWaiting, public readonly int $currentWatching, public readonly int $pause, public readonly int $cmdDelete, public readonly int $cmdPauseTube, public readonly int $pauseTimeLeft ) { } /** * @param array<string, string|int|bool|float> $data * @psalm-suppress ArgumentTypeCoercion * @psalm-suppress PossiblyUndefinedArrayOffset */ public static function fromBeanstalkArray(array $data): self { // Check that all expected keys are there foreach (self::KEYS as $key) { if (!isset($data[$key])) { throw new \InvalidArgumentException("Data array is missing expected key $key"); } } $tube = new TubeName($data['name']); return new self( name: $tube, currentJobsUrgent: $data['current-jobs-urgent'], currentJobsReady: $data['current-jobs-ready'], currentJobsReserved: $data['current-jobs-reserved'], currentJobsDelayed: $data['current-jobs-delayed'], currentJobsBuried: $data['current-jobs-buried'], totalJobs: $data['total-jobs'], currentUsing: $data['current-using'], currentWaiting: $data['current-waiting'], currentWatching: $data['current-watching'], pause: $data['pause'], cmdDelete: $data['cmd-delete'], cmdPauseTube: $data['cmd-pause-tube'], pauseTimeLeft: $data['pause-time-left'] ); } } Values/RawResponse.php 0000755 00000001301 00000000000 0010723 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; /** * Wraps the parts of a response to a beanstalkd command */ final class RawResponse { public readonly int|string|null $argument; public function __construct( public readonly ResponseType $type, string|null $argument = null, public readonly null|string $data = null ) { // Cast numeric strings to integers, if possible. $this->argument = match (true) { $argument === null => null, ctype_digit($argument) && (!str_starts_with($argument, "0") || $argument === '0') && $argument < PHP_INT_MAX => (int)$argument, default => $argument }; } } Values/CommandType.php 0000755 00000001560 00000000000 0010702 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Values; enum CommandType: string { case PUT = 'put'; case USE = 'use'; case RESERVE = 'reserve'; case RESERVE_WITH_TIMEOUT = 'reserve-with-timeout'; case RESERVE_JOB = 'reserve-job'; case DELETE = 'delete'; case RELEASE = 'release'; case TOUCH = 'touch'; case BURY = 'bury'; case WATCH = 'watch'; case IGNORE = 'ignore'; case PEEK = 'peek'; case PEEK_READY = 'peek-ready'; case PEEK_BURIED = 'peek-buried'; case PEEK_DELAYED = 'peek-delayed'; case KICK = 'kick'; case KICK_JOB = 'kick-job'; case STATS_JOB = 'stats-job'; case STATS = 'stats'; case STATS_TUBE = 'stats-tube'; case LIST_TUBES = 'list-tubes'; case LIST_TUBE_USED = 'list-tube-used'; case LIST_TUBES_WATCHED = 'list-tubes-watched'; case PAUSE_TUBE = 'pause-tube'; } Parser/YamlDictionaryParser.php 0000755 00000003610 00000000000 0012562 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Parser; /** * A parser that parses Yaml a simple dictionary * This is a very basic implementation that only supports single line strings and not any of the advanced YAML features * like multiline strings. * @internal */ final class YamlDictionaryParser { /** * @return array<string, bool|float|int|string> */ public function parse(string $data): array { $dictionary = []; /** @var list<string> $matches */ $matches = []; foreach (explode("\n", $data) as $line) { if (preg_match('#(\S+):\s*(.*)#', $line, $matches) === 1) { $dictionary[$matches[1]] = $this->cast($matches[2], $matches[1]); } } return $dictionary; } private function cast(string $value, string $key): bool|float|int|string { /** * Special case due to upstream issue: https://github.com/beanstalkd/beanstalkd/issues/610 * Also using the workaround for the `os` key since while fixed for 16 months it's not yet been part of a release */ if (in_array($key, ['hostname', 'os', 'tube', 'name', 'platform'], true)) { // We still check if it's quoted so that when the fixes are applied to beanstalkd our code still works. if (preg_match('/^".*"$/', $value) === 1) { return substr($value, 1, -1); } else { return $value; } } // Quoted strings if (preg_match('/^".*"$/', $value) === 1) { return substr($value, 1, -1); } elseif (preg_match('/^\d+$/', $value) === 1) { return (int)$value; } elseif (is_numeric($value)) { return (float) $value; } elseif (in_array($value, ["true", "false"], true)) { return $value === "true"; } return trim($value); } } Parser/YamlListParser.php 0000755 00000001264 00000000000 0011373 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Parser; /** * A parser that parses Yaml lists of strings * This is a very basic implementation that only supports single line strings and not any of the advanced YAML features * like multiline strings. * @internal */ final class YamlListParser { /** * @param string $data * @return list<string> */ public function parse(string $data): array { $lines = []; foreach (explode("\n", $data) as $line) { $trimmed = trim($line); if (str_starts_with($trimmed, '- ')) { $lines[] = substr($trimmed, 2); } } return $lines; } } Pheanstalk.php 0000755 00000012232 00000000000 0007313 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk; use Pheanstalk\Contract\JobIdInterface; use Pheanstalk\Contract\PheanstalkManagerInterface; use Pheanstalk\Contract\PheanstalkPublisherInterface; use Pheanstalk\Contract\PheanstalkSubscriberInterface; use Pheanstalk\Contract\SocketFactoryInterface; use Pheanstalk\Values\Job; use Pheanstalk\Values\JobStats; use Pheanstalk\Values\ServerStats; use Pheanstalk\Values\Timeout; use Pheanstalk\Values\TubeList; use Pheanstalk\Values\TubeName; use Pheanstalk\Values\TubeStats; /** * Pheanstalk is a PHP client for the beanstalkd work queue. * This class implements all functionality in one big object. * It is recommended to instead inject instances of the more specific interface implementations. * For example, your frontend is unlikely to subscribe to requests so probably does not need `PheanstalkPublisherInterface` * or `PheanstalkManagerInterface`. */ final class Pheanstalk implements PheanstalkManagerInterface, PheanstalkPublisherInterface, PheanstalkSubscriberInterface { private PheanstalkManagerInterface $manager; private PheanstalkPublisherInterface $publisher; private PheanstalkSubscriberInterface $subscriber; public function __construct(private readonly Connection $connection) { $this->manager = new PheanstalkManager($this->connection); $this->subscriber = new PheanstalkSubscriber($this->connection); $this->publisher = new PheanstalkPublisher($this->connection); } /** * Static constructor that uses auto-detection to choose an underlying socket implementation */ public static function create( string $host, int $port = 11300, ?Timeout $connectTimeout = null, ?Timeout $receiveTimeout = null ): static { return static::createWithFactory(new SocketFactory($host, $port, null, $connectTimeout, $receiveTimeout)); } /** * Static constructor that uses a given socket factory for underlying connections */ public static function createWithFactory(SocketFactoryInterface $factory): static { return new static(new Connection($factory)); } public function kick(int $max): int { return $this->manager->kick($max); } public function kickJob(JobIdInterface $job): void { $this->manager->kickJob($job); } public function listTubes(): TubeList { return $this->manager->listTubes(); } public function pauseTube(TubeName $tube, int $delay): void { $this->manager->pauseTube($tube, $delay); } public function resumeTube(TubeName $tube): void { $this->manager->resumeTube($tube); } public function peek(JobIdInterface $job): Job { return $this->manager->peek($job); } public function peekReady(): ?Job { return $this->manager->peekReady(); } public function peekDelayed(): ?Job { return $this->manager->peekDelayed(); } public function peekBuried(): ?Job { return $this->manager->peekBuried(); } public function statsJob(JobIdInterface $job): JobStats { return $this->manager->statsJob($job); } public function statsTube(TubeName $tube): TubeStats { return $this->manager->statsTube($tube); } public function stats(): ServerStats { return $this->manager->stats(); } public function bury(JobIdInterface $job, int $priority = self::DEFAULT_PRIORITY): void { $this->subscriber->bury($job, $priority); } public function listTubeUsed(): TubeName { return $this->publisher->listTubeUsed(); } public function put( string $data, int $priority = self::DEFAULT_PRIORITY, int $delay = self::DEFAULT_DELAY, int $timeToRelease = self::DEFAULT_TTR ): JobIdInterface { return $this->publisher->put($data, $priority, $delay, $timeToRelease); } public function useTube(TubeName $tube): void { $this->publisher->useTube($tube); } public function delete(JobIdInterface $job): void { $this->subscriber->delete($job); } public function ignore(TubeName $tube): int { return $this->subscriber->ignore($tube); } public function listTubesWatched(): TubeList { return $this->subscriber->listTubesWatched(); } public function release( JobIdInterface $job, int $priority = PheanstalkPublisherInterface::DEFAULT_PRIORITY, int $delay = PheanstalkPublisherInterface::DEFAULT_DELAY ): void { $this->subscriber->release($job, $priority, $delay); } public function reserve(): Job { return $this->subscriber->reserve(); } public function reserveJob(JobIdInterface $job): Job { return $this->subscriber->reserveJob($job); } public function reserveWithTimeout(int $timeout): ?Job { return $this->subscriber->reserveWithTimeout($timeout); } public function touch(JobIdInterface $job): void { $this->subscriber->touch($job); } public function watch(TubeName $tube): int { return $this->subscriber->watch($tube); } } Command/PeekCommand.php 0000755 00000003351 00000000000 0010764 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\Job; use Pheanstalk\Values\JobId; use Pheanstalk\Values\JobState; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\Success; /** * The 'peek', 'peek-ready', 'peek-delayed' and 'peek-buried' commands. * * The peek commands let the client inspect a job in the system. There are four * variations. All but the first (peek) operate only on the currently used tube. * */ final class PeekCommand implements CommandInterface { private readonly string $command; public function __construct(JobState $state) { $this->command = match ($state) { JobState::BURIED => 'peek-buried', JobState::DELAYED => 'peek-delayed', JobState::READY => 'peek-ready', JobState::RESERVED => throw new \InvalidArgumentException("Peeking at reserved jobs is not supported") }; } public function getCommandLine(): string { return $this->command; } public function interpret(RawResponse $response): Job|Success { if ($response->type === ResponseType::Found && isset($response->argument) && isset($response->data)) { return new Job(new JobId($response->argument), $response->data); } return match ($response->type) { ResponseType::NotFound => new Success(), ResponseType::Found => throw MalformedResponseException::expectedDataAndIntegerArgument(), default => throw new UnsupportedResponseException($response->type) }; } } Command/StatsJobCommand.php 0000755 00000002352 00000000000 0011631 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Exception\JobNotFoundException; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Parser\YamlDictionaryParser; use Pheanstalk\Values\JobCommandTemplate; use Pheanstalk\Values\JobStats; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; /** * The 'stats-job' command. * * Gives statistical information about the specified job if it exists. * */ final class StatsJobCommand extends JobCommand { public function interpret(RawResponse $response): JobStats { return match (true) { $response->type === ResponseType::Ok && isset($response->data) => JobStats::fromBeanstalkArray((new YamlDictionaryParser())->parse($response->data)), $response->type === ResponseType::NotFound => throw new JobNotFoundException(), $response->type === ResponseType::Ok => throw MalformedResponseException::expectedData(), default => throw new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): JobCommandTemplate { return new JobCommandTemplate("stats-job {id}"); } } Command/KickCommand.php 0000755 00000002172 00000000000 0010761 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; /** * The 'kick' command. * * Kicks buried or delayed jobs into a 'ready' state. * If there are buried jobs, it will kick up to $max of them. * Otherwise, it will kick up to $max delayed jobs. * */ final class KickCommand implements CommandInterface { public function __construct(private readonly int $max) { } public function getCommandLine(): string { return "kick {$this->max}"; } public function interpret( RawResponse $response ): int { if ($response->type === ResponseType::Kicked && is_int($response->argument)) { return $response->argument; } throw match ($response->type) { ResponseType::Kicked => MalformedResponseException::expectedIntegerArgument(), default => new UnsupportedResponseException($response->type), }; } } Command/PutCommand.php 0000755 00000004327 00000000000 0010654 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Contract\CommandWithDataInterface; use Pheanstalk\Exception; use Pheanstalk\Values\JobId; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; /** * The 'put' command. * * Inserts a job into the client's currently used tube. * * @see UseCommand * */ final class PutCommand implements CommandInterface, CommandWithDataInterface { /** * Puts a job on the queue. * * @param string $data The job data * @param int $priority From 0 (most urgent) to 0xFFFFFFFF (least urgent) * @param int $delay Seconds to wait before job becomes ready * @param int $ttr Time To Run: seconds a job can be reserved for */ public function __construct(private readonly string $data, private readonly int $priority, private readonly int $delay, private readonly int $ttr) { } public function getCommandLine(): string { return "put {$this->priority} {$this->delay} {$this->ttr} {$this->getDataLength()}"; } public function getData(): string { return $this->data; } private function getDataLength(): int { return mb_strlen($this->data, '8bit'); } public function interpret(RawResponse $response): JobId { if ($response->type === ResponseType::Inserted && isset($response->argument)) { return new JobId($response->argument); } elseif ($response->type === ResponseType::Buried && isset($response->argument)) { throw new Exception\JobBuriedException(new JobId($response->argument)); } throw match ($response->type) { ResponseType::Buried => Exception\MalformedResponseException::expectedIntegerArgument(), ResponseType::ExpectedCrlf => new Exception\ExpectedCrlfException(), ResponseType::Draining => new Exception\ServerDrainingException(), ResponseType::JobTooBig => new Exception\JobTooBigException(), ResponseType::Inserted => Exception\MalformedResponseException::expectedData(), default => new Exception\UnsupportedResponseException($response->type) }; } } Command/IgnoreCommand.php 0000755 00000002121 00000000000 0011315 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\NotIgnoredException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\TubeCommandTemplate; /** * The 'ignore' command. * Removes a tube from the watch list to reserve jobs from. */ final class IgnoreCommand extends TubeCommand { public function interpret(RawResponse $response): int { if ($response->type === ResponseType::Watching && is_int($response->argument)) { return $response->argument; } throw match ($response->type) { ResponseType::NotIgnored => new NotIgnoredException(), ResponseType::Watching => MalformedResponseException::expectedIntegerArgument(), default => new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): TubeCommandTemplate { return new TubeCommandTemplate("ignore {tube}"); } } Command/KickJobCommand.php 0000755 00000002141 00000000000 0011410 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Exception; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\JobCommandTemplate; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\Success; /** * The 'kick-job' command. * * Kicks a specific buried or delayed job into a 'ready' state. * * A variant of kick that operates with a single job. If the given job * exists and is in a buried or delayed state, it will be moved to the * ready queue of the same tube where it currently belongs. * */ final class KickJobCommand extends JobCommand { public function interpret(RawResponse $response): Success { return match ($response->type) { ResponseType::NotFound => throw new Exception\JobNotFoundException(), ResponseType::Kicked => new Success(), default => throw new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): JobCommandTemplate { return new JobCommandTemplate("kick-job {id}"); } } Command/WatchCommand.php 0000755 00000002246 00000000000 0011150 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\TubeCommandTemplate; /** * The 'watch' command. * Adds a tube to the watchlist to reserve jobs from. * */ final class WatchCommand extends TubeCommand { /** * @param RawResponse $response * @return int The number of tubes currently in the watch list * @throws MalformedResponseException * @throws UnsupportedResponseException */ public function interpret(RawResponse $response): int { if ($response->type === ResponseType::Watching && is_int($response->argument)) { return $response->argument; } throw match ($response->type) { ResponseType::Watching => MalformedResponseException::expectedIntegerArgument(), default => new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): TubeCommandTemplate { return new TubeCommandTemplate("watch {tube}"); } } Command/TubeCommand.php 0000755 00000001117 00000000000 0010775 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Values\TubeCommandTemplate; use Pheanstalk\Values\TubeName; /** * A command that is executed against a tube * @internal */ abstract class TubeCommand implements CommandInterface { abstract protected function getCommandTemplate(): TubeCommandTemplate; public function __construct(protected readonly TubeName $tube) { } final public function getCommandLine(): string { return $this->getCommandTemplate()->render($this->tube); } } Command/DeleteCommand.php 0000755 00000001605 00000000000 0011302 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Exception\JobNotFoundException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\JobCommandTemplate; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\Success; /** * The 'delete' command. * Permanently deletes an already-reserved job. */ final class DeleteCommand extends JobCommand { public function interpret(RawResponse $response): Success { return match ($response->type) { ResponseType::NotFound => throw new JobNotFoundException(), ResponseType::Deleted => new Success(), default => throw new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): JobCommandTemplate { return new JobCommandTemplate("delete {id}"); } } Command/ReleaseCommand.php 0000755 00000002165 00000000000 0011462 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\JobIdInterface; use Pheanstalk\Exception; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\JobCommandTemplate; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\Success; /** * The 'release' command. * * Releases a reserved job back onto the ready queue. * */ final class ReleaseCommand extends JobCommand { public function __construct(JobIdInterface $job, private readonly int $priority, private readonly int $delay) { parent::__construct($job); } public function interpret(RawResponse $response): Success { return match ($response->type) { ResponseType::NotFound => throw new Exception\JobNotFoundException(), ResponseType::Released => new Success(), default => throw new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): JobCommandTemplate { return new JobCommandTemplate("release {id} {$this->priority} {$this->delay}"); } } Command/StatsCommand.php 0000755 00000002062 00000000000 0011174 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Parser\YamlDictionaryParser; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\ServerStats; /** * The 'stats' command. * * Statistical information about the system as a whole. * */ final class StatsCommand implements CommandInterface { public function getCommandLine(): string { return 'stats'; } public function interpret(RawResponse $response): ServerStats { if ($response->type === ResponseType::Ok && is_string($response->data)) { return ServerStats::fromBeanstalkArray((new YamlDictionaryParser())->parse($response->data)); } throw match ($response->type) { ResponseType::Ok => MalformedResponseException::expectedData(), default => new UnsupportedResponseException($response->type) }; } } Command/StatsTubeCommand.php 0000755 00000002340 00000000000 0012013 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\TubeNotFoundException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Parser\YamlDictionaryParser; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\TubeCommandTemplate; use Pheanstalk\Values\TubeStats; /** * The 'stats-tube' command. * Gives statistical information about the specified tube if it exists. */ final class StatsTubeCommand extends TubeCommand { public function interpret(RawResponse $response): TubeStats { if ($response->type === ResponseType::Ok && isset($response->data)) { return TubeStats::fromBeanstalkArray((new YamlDictionaryParser())->parse($response->data)); } throw match ($response->type) { ResponseType::NotFound => new TubeNotFoundException(), ResponseType::Ok => MalformedResponseException::expectedData(), default => new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): TubeCommandTemplate { return new TubeCommandTemplate("stats-tube {tube}"); } } Command/PeekJobCommand.php 0000755 00000002310 00000000000 0011411 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Exception\JobNotFoundException; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\Job; use Pheanstalk\Values\JobCommandTemplate; use Pheanstalk\Values\JobId; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; /** * The 'peek' command. * * The peek command let the client inspect a specific job in the system. * */ final class PeekJobCommand extends JobCommand { public function interpret(RawResponse $response): Job { if ($response->type === ResponseType::Found && isset($response->argument, $response->data)) { return new Job(new JobId($response->argument), $response->data); } throw match ($response->type) { ResponseType::NotFound => new JobNotFoundException(), ResponseType::Found => MalformedResponseException::expectedDataAndIntegerArgument(), default => new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): JobCommandTemplate { return new JobCommandTemplate("peek {id}"); } } Command/ListTubesCommand.php 0000755 00000002222 00000000000 0012012 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Parser\YamlListParser; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\TubeList; use Pheanstalk\Values\TubeName; /** * The 'list-tubes' command. * * List all existing tubes. */ final class ListTubesCommand implements CommandInterface { public function getCommandLine(): string { return "list-tubes"; } public function interpret(RawResponse $response): TubeList { if ($response->type === ResponseType::Ok && is_string($response->data)) { return new TubeList(...array_map( fn (string $rawName): TubeName => new TubeName($rawName), (new YamlListParser())->parse($response->data) )); } throw match ($response->type) { ResponseType::Ok => MalformedResponseException::expectedData(), default => new UnsupportedResponseException($response->type) }; } } Command/ReserveWithTimeoutCommand.php 0000755 00000003310 00000000000 0013711 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Exception\DeadlineSoonException; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\Job; use Pheanstalk\Values\JobId; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\Success; /** * The 'reserve' command. * Reserves/locks a ready job in a watched tube. * */ final class ReserveWithTimeoutCommand implements CommandInterface { /** * A timeout value of 0 will cause the server to immediately return either a * response or TIMED_OUT. A positive value of timeout will limit the amount of * time the client will block on the reserve request until a job becomes * available. * @param int<0, max> $timeout */ public function __construct(private readonly int $timeout) { } public function getCommandLine(): string { return "reserve-with-timeout {$this->timeout}"; } public function interpret(RawResponse $response): Success|Job { return match (true) { $response->type === ResponseType::Reserved && isset($response->argument, $response->data) => new Job(new JobId($response->argument), $response->data), $response->type === ResponseType::Reserved => throw MalformedResponseException::expectedDataAndIntegerArgument(), $response->type === ResponseType::TimedOut => new Success(), $response->type === ResponseType::DeadlineSoon => throw new DeadlineSoonException(), default => throw new UnsupportedResponseException($response->type) }; } } Command/BuryCommand.php 0000755 00000002135 00000000000 0011020 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\JobIdInterface; use Pheanstalk\Exception; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\JobCommandTemplate; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\Success; /** * The 'bury' command. * Puts a job into a 'buried' state, revived only by 'kick' command. */ final class BuryCommand extends JobCommand { public function __construct( JobIdInterface $jobId, private readonly int $priority ) { parent::__construct($jobId); } protected function getCommandTemplate(): JobCommandTemplate { return new JobCommandTemplate("bury {id} {$this->priority}"); } public function interpret(RawResponse $response): Success { return match ($response->type) { ResponseType::NotFound => throw new Exception\JobNotFoundException(), ResponseType::Buried => new Success(), default => throw new UnsupportedResponseException($response->type) }; } } Command/ReserveJobCommand.php 0000755 00000002155 00000000000 0012147 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Exception; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\Job; use Pheanstalk\Values\JobCommandTemplate; use Pheanstalk\Values\JobId; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; /** * The 'reserve-job' command. */ final class ReserveJobCommand extends JobCommand { public function interpret(RawResponse $response): Job { return match (true) { $response->type === ResponseType::Reserved && isset($response->argument, $response->data) => new Job(new JobId($response->argument), $response->data), $response->type === ResponseType::Reserved => throw Exception\MalformedResponseException::expectedDataAndIntegerArgument(), $response->type === ResponseType::NotFound => throw new Exception\JobNotFoundException(), default => throw new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): JobCommandTemplate { return new JobCommandTemplate("reserve-job {id}"); } } Command/UseCommand.php 0000755 00000002333 00000000000 0010633 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\TubeCommandTemplate; use Pheanstalk\Values\TubeName; /** * The 'use' command. * * The "use" command is for producers. Subsequent put commands will put jobs into * the tube specified by this command. If no use command has been issued, jobs * will be put into the tube named "default". * */ final class UseCommand extends TubeCommand { public function interpret( RawResponse $response ): TubeName { return match (true) { $response->type === ResponseType::Using && isset($response->argument) => new TubeName(is_int($response->argument) ? (string) $response->argument : $response->argument), $response->type === ResponseType::Using => throw MalformedResponseException::expectedIntegerArgument(), default => throw new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): TubeCommandTemplate { return new TubeCommandTemplate("use {tube}"); } } Command/PauseTubeCommand.php 0000755 00000002251 00000000000 0011773 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Exception; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\Success; use Pheanstalk\Values\TubeCommandTemplate; use Pheanstalk\Values\TubeName; /** * The 'pause-tube' command. * * Temporarily prevent jobs being reserved from the given tube. */ final class PauseTubeCommand extends TubeCommand { /** * @param int $delay Seconds before jobs may be reserved from this queue. */ public function __construct(TubeName $tube, private readonly int $delay) { parent::__construct($tube); } public function interpret(RawResponse $response): Success { return match ($response->type) { ResponseType::NotFound => throw new Exception\TubeNotFoundException(), ResponseType::Paused => new Success(), default => throw new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): TubeCommandTemplate { return new TubeCommandTemplate("pause-tube {tube} {$this->delay}"); } } Command/TouchCommand.php 0000755 00000002266 00000000000 0011166 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Exception; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\JobCommandTemplate; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\Success; /** * The 'touch' command. * The 'touch' command allows a worker to request more time to work on a job. * This is useful for jobs that potentially take a long time, but you still want * the benefits of a TTR pulling a job away from an unresponsive worker. A worker * may periodically tell the server that it's still alive and processing a job * (e.g. it may do this on DEADLINE_SOON). * */ final class TouchCommand extends JobCommand { public function interpret(RawResponse $response): Success { return match ($response->type) { ResponseType::NotFound => throw new Exception\JobNotFoundException(), ResponseType::Touched => new Success(), default => throw new UnsupportedResponseException($response->type) }; } protected function getCommandTemplate(): JobCommandTemplate { return new JobCommandTemplate("touch {id}"); } } Command/ListTubeUsedCommand.php 0000755 00000001756 00000000000 0012463 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\TubeName; /** * The 'list-tube-used' command. * * Returns the tube currently being used by the client. * */ final class ListTubeUsedCommand implements CommandInterface { public function interpret(RawResponse $response): TubeName { return match (true) { $response->type === ResponseType::Using && isset($response->argument) => new TubeName($response->argument), $response->type === ResponseType::Using => throw MalformedResponseException::expectedStringArgument(), default => throw new UnsupportedResponseException($response->type) }; } public function getCommandLine(): string { return "list-tube-used"; } } Command/ReserveCommand.php 0000755 00000002463 00000000000 0011516 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Exception\DeadlineSoonException; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\TimedOutException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Values\Job; use Pheanstalk\Values\JobId; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; /** * The 'reserve' command. * * Reserves/locks a ready job in a watched tube. * */ final class ReserveCommand implements CommandInterface { public function getCommandLine(): string { return 'reserve'; } public function interpret(RawResponse $response): Job { return match (true) { $response->type === ResponseType::Reserved && isset($response->argument, $response->data) => new Job(new JobId($response->argument), $response->data), $response->type === ResponseType::Reserved => throw MalformedResponseException::expectedDataAndIntegerArgument(), $response->type === ResponseType::DeadlineSoon => throw new DeadlineSoonException(), $response->type === ResponseType::TimedOut => throw new TimedOutException(), default => throw new UnsupportedResponseException($response->type) }; } } Command/JobCommand.php 0000755 00000001140 00000000000 0010604 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Contract\JobIdInterface; use Pheanstalk\Values\JobCommandTemplate; /** * A command that is executed against a single job * @internal */ abstract class JobCommand implements CommandInterface { abstract protected function getCommandTemplate(): JobCommandTemplate; final public function getCommandLine(): string { return $this->getCommandTemplate()->render($this->jobId); } public function __construct(private readonly JobIdInterface $jobId) { } } Command/ListTubesWatchedCommand.php 0000755 00000002262 00000000000 0013316 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk\Command; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Exception\MalformedResponseException; use Pheanstalk\Exception\UnsupportedResponseException; use Pheanstalk\Parser\YamlListParser; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ResponseType; use Pheanstalk\Values\TubeList; use Pheanstalk\Values\TubeName; /** * The 'list-tubes-watched' command. * * Lists the tubes on the watchlist. */ final class ListTubesWatchedCommand implements CommandInterface { public function interpret(RawResponse $response): TubeList { if ($response->type === ResponseType::Ok && is_string($response->data)) { return new TubeList(...array_map( fn (string $rawName): TubeName => new TubeName($rawName), (new YamlListParser())->parse($response->data) )); } throw match ($response->type) { ResponseType::Ok => MalformedResponseException::expectedData(), default => new UnsupportedResponseException($response->type) }; } public function getCommandLine(): string { return "list-tubes-watched"; } } PheanstalkSubscriber.php 0000755 00000006016 00000000000 0011342 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk; use Pheanstalk\Command\BuryCommand; use Pheanstalk\Command\ReserveJobCommand; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Contract\JobIdInterface; use Pheanstalk\Contract\PheanstalkPublisherInterface; use Pheanstalk\Contract\PheanstalkSubscriberInterface; use Pheanstalk\Values\Job; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\Success; use Pheanstalk\Values\TubeList; use Pheanstalk\Values\TubeName; final class PheanstalkSubscriber implements PheanstalkSubscriberInterface { use StaticFactoryTrait; private function dispatch(CommandInterface $command): RawResponse { return $this->connection->dispatchCommand($command); } public function delete(JobIdInterface $job): void { $command = new Command\DeleteCommand($job); $command->interpret($this->dispatch($command)); } public function ignore(TubeName $tube): int { $command = new Command\IgnoreCommand($tube); return $command->interpret($this->dispatch($command)); } public function listTubesWatched(): TubeList { $command = new Command\ListTubesWatchedCommand(); $response = $this->dispatch($command); return $command->interpret($response); } public function release( JobIdInterface $job, int $priority = PheanstalkPublisherInterface::DEFAULT_PRIORITY, int $delay = PheanstalkPublisherInterface::DEFAULT_DELAY ): void { $command = new Command\ReleaseCommand($job, $priority, $delay); $command->interpret($this->dispatch($command)); } public function reserve(): Job { $command = new Command\ReserveCommand(); return $command->interpret($this->dispatch($command)); } /** * @param int<0, max> $timeout * @throws Exception\UnsupportedResponseException|Exception\MalformedResponseException|Exception\DeadlineSoonException */ public function reserveWithTimeout(int $timeout): null|Job { $command = new Command\ReserveWithTimeoutCommand($timeout); $response = $command->interpret($this->dispatch($command)); if ($response instanceof Success) { return null; } return $response; } public function touch(JobIdInterface $job): void { $command = new Command\TouchCommand($job); $command->interpret($this->dispatch($command)); } public function watch(TubeName $tube): int { $command = new Command\WatchCommand($tube); return $command->interpret($this->dispatch($command)); } public function bury(JobIdInterface $job, int $priority = PheanstalkPublisherInterface::DEFAULT_PRIORITY): void { $command = new BuryCommand($job, $priority); $command->interpret($this->connection->dispatchCommand($command)); } public function reserveJob(JobIdInterface $job): Job { $command = new ReserveJobCommand($job); return $command->interpret($this->dispatch($command)); } } PheanstalkManager.php 0000755 00000005657 00000000000 0010623 0 ustar 00 <?php declare(strict_types=1); namespace Pheanstalk; use Pheanstalk\Contract\CommandInterface; use Pheanstalk\Contract\JobIdInterface; use Pheanstalk\Contract\PheanstalkManagerInterface; use Pheanstalk\Values\Job; use Pheanstalk\Values\JobState; use Pheanstalk\Values\JobStats; use Pheanstalk\Values\RawResponse; use Pheanstalk\Values\ServerStats; use Pheanstalk\Values\Success; use Pheanstalk\Values\TubeList; use Pheanstalk\Values\TubeName; use Pheanstalk\Values\TubeStats; /** * This class implements "management" functions for the beanstalk protocol. * */ final class PheanstalkManager implements PheanstalkManagerInterface { use StaticFactoryTrait; public function kick(int $max): int { $command = new Command\KickCommand($max); return $command->interpret($this->dispatch($command)); } public function kickJob(JobIdInterface $job): void { $command = new Command\KickJobCommand($job); $command->interpret($this->dispatch($command)); } private function dispatch(CommandInterface $command): RawResponse { return $this->connection->dispatchCommand($command); } public function listTubes(): TubeList { $command = new Command\ListTubesCommand(); return $command->interpret($this->dispatch($command)); } public function pauseTube(TubeName $tube, int $delay): void { $command = new Command\PauseTubeCommand($tube, $delay); $command->interpret($this->dispatch($command)); } public function resumeTube(TubeName $tube): void { // Pause a tube with zero delay will resume the tube $this->pauseTube($tube, 0); } public function peek(JobIdInterface $job): Job { $command = new Command\PeekJobCommand($job); return $command->interpret($this->dispatch($command)); } public function peekReady(): null|Job { return $this->peekState(JobState::READY); } public function peekDelayed(): null|Job { return $this->peekState(JobState::DELAYED); } private function peekState(JobState $state): null|Job { $command = new Command\PeekCommand($state); $response = $command->interpret($this->dispatch($command)); return $response instanceof Success ? null : $response; } public function peekBuried(): null|Job { return $this->peekState(JobState::BURIED); } public function statsJob(JobIdInterface $job): JobStats { $command = new Command\StatsJobCommand($job); return $command->interpret($this->dispatch($command)); } public function statsTube(TubeName $tube): TubeStats { $command = new Command\StatsTubeCommand($tube); return $command->interpret($this->dispatch($command)); } public function stats(): ServerStats { $command = new Command\StatsCommand(); return $command->interpret($this->dispatch($command)); } }
| ver. 1.4 |
Github
|
.
| PHP 8.3.30 | Generation time: 0.15 |
proxy
|
phpinfo
|
Settings