diff --git a/.circleci/config.yml b/.circleci/config.yml index 5574ea2ecd..95fef6271d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,14 +37,14 @@ commands: fi executors: - mysql-stable: + mysql-lowest: docker: - image: wodby/php:$WODBY_TAG environment: - MYSQL_HOST=127.0.0.1 - UNISH_DB_URL=mysql://root:@127.0.0.1/unish_dev?module=mysql - image: cimg/mysql:5.7.38 - sqlite-stable: + sqlite-lowest: docker: - image: wodby/php:$WODBY_TAG environment: @@ -56,7 +56,7 @@ executors: - MYSQL_HOST=127.0.0.1 - UNISH_DB_URL=mysql://root:@127.0.0.1/unish_dev?module=mysql - image: cimg/mysql:5.7.38 - postgres-stable: + postgres-lowest: docker: - image: wodby/php:$WODBY_TAG environment: @@ -117,13 +117,13 @@ jobs: - when: condition: and: - - equal: [ stable, << parameters.release >> ] + - equal: [ lowest, << parameters.release >> ] steps: - - run: composer -n install + - run: composer -n update --prefer-lowest - unless: condition: and: - - equal: [ stable, << parameters.release >> ] + - equal: [ lowest, << parameters.release >> ] steps: - run: composer -n config platform.php --unset - run: composer -n require --dev drupal/core-recommended:11.x-dev --no-update @@ -164,7 +164,7 @@ workflows: #sqlite removed pending https://github.com/wodby/php/issues/194 dbms: [ mysql, postgres ] suite: [integration, functional] - release: [stable, highest] + release: [ lowest, highest ] exclude: # Only run highest test on mysql. Excluding each suite is unfortunate but needed. - release: highest diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000000..fac61b8434 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,46 @@ +parameters: + ignoreErrors: + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Style\\\\SymfonyStyle\\:\\:askWithCompletion\\(\\)\\.$#" + count: 1 + path: src/Commands/DrushCommands.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Style\\\\SymfonyStyle\\:\\:secret\\(\\)\\.$#" + count: 1 + path: src/Commands/DrushCommands.php + + - + message: "#^Call to an undefined method Drupal\\\\migrate\\\\Plugin\\\\MigrationInterface\\:\\:set\\(\\)\\.$#" + count: 1 + path: src/Commands/core/MigrateRunnerCommands.php + + - + message: "#^Result of method Drush\\\\Style\\\\DrushStyle\\:\\:text\\(\\) \\(void\\) is used\\.$#" + count: 6 + path: src/Commands/core/SiteInstallCommands.php + + - + message: "#^Result of method Drush\\\\Style\\\\DrushStyle\\:\\:text\\(\\) \\(void\\) is used\\.$#" + count: 2 + path: src/Commands/field/FieldBaseOverrideCreateCommands.php + + - + message: "#^Result of method Drush\\\\Style\\\\DrushStyle\\:\\:text\\(\\) \\(void\\) is used\\.$#" + count: 4 + path: src/Commands/field/FieldCreateCommands.php + + - + message: "#^Call to an undefined method Drupal\\\\Core\\\\DependencyInjection\\\\ServiceModifierInterface\\:\\:check\\(\\)\\.$#" + count: 1 + path: src/Drupal/DrupalKernel.php + + - + message: "#^Call to an undefined method Drupal\\\\Core\\\\DependencyInjection\\\\ServiceModifierInterface\\:\\:check\\(\\)\\.$#" + count: 1 + path: src/Drupal/InstallerKernel.php + + - + message: "#^Call to an undefined method Drupal\\\\Core\\\\DependencyInjection\\\\ServiceModifierInterface\\:\\:check\\(\\)\\.$#" + count: 1 + path: src/Drupal/UpdateKernel.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 925101c565..ed51b1147b 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 1 + level: 2 editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%' bootstrapFiles: - phpstan-bootstrap.php @@ -27,3 +27,4 @@ parameters: - '#Constant TIDEWAYS_XHPROF_FLAGS_MEMORY not found.#' includes: - vendor/mglaman/phpstan-drupal/extension.neon + - phpstan-baseline.neon diff --git a/src/Boot/BootstrapManager.php b/src/Boot/BootstrapManager.php index 80c9f1d2ad..50af7e0979 100644 --- a/src/Boot/BootstrapManager.php +++ b/src/Boot/BootstrapManager.php @@ -137,7 +137,7 @@ public function bootstrap(): DrupalBoot8 /** * For use in testing */ - public function injectBootstrap(Boot $bootstrap): void + public function injectBootstrap(DrupalBoot8 $bootstrap): void { $bootstrap->setLogger($this->logger()); $this->bootstrap = $bootstrap; diff --git a/src/Boot/DrupalBoot8.php b/src/Boot/DrupalBoot8.php index 1af96b673e..984f29aef3 100644 --- a/src/Boot/DrupalBoot8.php +++ b/src/Boot/DrupalBoot8.php @@ -21,6 +21,7 @@ use Robo\Robo; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\TerminableInterface; class DrupalBoot8 extends DrupalBoot { @@ -321,6 +322,7 @@ public function terminate(): void } else { $response = new HtmlResponse(); } + assert($this->kernel instanceof TerminableInterface); $this->kernel->terminate($this->getRequest(), $response); } } diff --git a/src/Commands/config/ConfigPullCommands.php b/src/Commands/config/ConfigPullCommands.php index 8c6e52a0b4..cbcf632e6a 100644 --- a/src/Commands/config/ConfigPullCommands.php +++ b/src/Commands/config/ConfigPullCommands.php @@ -9,6 +9,7 @@ use Consolidation\OutputFormatters\StructuredData\PropertyList; use Consolidation\SiteAlias\HostPath; use Consolidation\SiteAlias\SiteAliasManagerInterface; +use Consolidation\SiteProcess\SiteProcess; use Drush\Attributes as CLI; use Drush\Boot\DrupalBootLevels; use Drush\Commands\AutowireTrait; @@ -96,6 +97,7 @@ public function validateConfigPull(CommandData $commandData): void { if ($commandData->input()->getOption('safe')) { $destinationRecord = $this->siteAliasManager->get($commandData->input()->getArgument('destination')); + /** @var SiteProcess $process */ $process = $this->processManager()->siteProcess($destinationRecord, ['git', 'diff', '--quiet']); $process->chdirToSiteRoot(); $process->run(); diff --git a/src/Commands/core/QueueCommands.php b/src/Commands/core/QueueCommands.php index a0016a376e..6e0c755619 100644 --- a/src/Commands/core/QueueCommands.php +++ b/src/Commands/core/QueueCommands.php @@ -76,7 +76,9 @@ public function run(string $name, $options = ['time-limit' => self::REQ, 'items- while ((!$time_limit || $remaining > 0) && (!$items_limit || $count < $items_limit) && ($item = $queue->claimItem($lease_time))) { try { + // @phpstan-ignore-next-line $this->logger()->info(dt('Processing item @id from @name queue.', ['@name' => $name, '@id' => $item->item_id ?? $item->qid])); + // @phpstan-ignore-next-line $worker->processItem($item->data); $queue->deleteItem($item); $count++; diff --git a/src/Commands/core/RsyncCommands.php b/src/Commands/core/RsyncCommands.php index 984d44d21a..a7946cb770 100644 --- a/src/Commands/core/RsyncCommands.php +++ b/src/Commands/core/RsyncCommands.php @@ -140,6 +140,7 @@ protected function injectAliasPathParameterOptions($input, $parameterName) // options into the alias config context so that we pick up // things like ssh-options. if ($aliasRecord->isRemote()) { + assert($aliasConfigContext instanceof \Consolidation\Config\Config); $aliasConfigContext->combine($aliasRecord->export()); } diff --git a/src/Commands/core/SiteInstallCommands.php b/src/Commands/core/SiteInstallCommands.php index 24c9dd2247..3076ea8629 100644 --- a/src/Commands/core/SiteInstallCommands.php +++ b/src/Commands/core/SiteInstallCommands.php @@ -13,6 +13,7 @@ use Drupal\Core\Database\Database; use Drupal\Core\Installer\Exception\AlreadyInstalledException; use Drupal\Core\Installer\Exception\InstallerException; +use Drupal\Core\Installer\InstallerKernel; use Drupal\Core\Mail\MailFormatHelper; use Drupal\Core\Site\Settings; use Drush\Attributes as CLI; @@ -201,7 +202,9 @@ protected function determineProfile($profile, $options): string|bool if (empty($profile)) { $boot = $this->bootstrapManager->bootstrap(); - $profile = $boot->getKernel()->getInstallProfile(); + $kernel = $boot->getKernel(); + assert($kernel instanceof InstallerKernel); + $profile = $kernel->getInstallProfile(); } if (empty($profile)) { diff --git a/src/Commands/core/UpdateDBCommands.php b/src/Commands/core/UpdateDBCommands.php index 1d0d635047..5bc2a071f7 100644 --- a/src/Commands/core/UpdateDBCommands.php +++ b/src/Commands/core/UpdateDBCommands.php @@ -4,11 +4,11 @@ namespace Drush\Commands\core; -use Drupal\Core\Update\UpdateRegistry; use Consolidation\OutputFormatters\StructuredData\RowsOfFields; use Consolidation\OutputFormatters\StructuredData\UnstructuredListData; use Consolidation\SiteAlias\SiteAliasManagerInterface; use Drupal\Core\Database\Database; +use Drupal\Core\Update\UpdateRegistry; use Drupal\Core\Utility\Error; use Drush\Attributes as CLI; use Drush\Boot\DrupalBootLevels; @@ -271,14 +271,7 @@ public static function updateDoOnePostUpdate(string $function, array $context): } list($extension, $name) = explode('_post_update_', $function, 2); - $update_registry = \Drupal::service('update.post_update_registry'); - // https://www.drupal.org/project/drupal/issues/3259188 Support theme's - // having post update functions when it is supported in Drupal core. - if (method_exists($update_registry, 'getUpdateFunctions')) { - \Drupal::service('update.post_update_registry')->getUpdateFunctions($extension); - } else { - \Drupal::service('update.post_update_registry')->getModuleUpdateFunctions($extension); - } + \Drupal::service('update.post_update_registry')->getUpdateFunctions($extension); if (function_exists($function)) { if (empty($context['results'][$extension][$name]['type'])) { diff --git a/src/Commands/field/FieldCreateCommands.php b/src/Commands/field/FieldCreateCommands.php index c0816128f6..40e4c0e09d 100644 --- a/src/Commands/field/FieldCreateCommands.php +++ b/src/Commands/field/FieldCreateCommands.php @@ -489,6 +489,7 @@ protected function createFieldDisplay(string $context): void $storage->save(); } + assert($storage instanceof EntityDisplayInterface); $storage->setComponent($fieldName, $values)->save(); } diff --git a/src/Commands/field/FieldDeleteCommands.php b/src/Commands/field/FieldDeleteCommands.php index b03c285703..12ed84e00d 100644 --- a/src/Commands/field/FieldDeleteCommands.php +++ b/src/Commands/field/FieldDeleteCommands.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\field\Entity\FieldConfig; use Drupal\field\FieldConfigInterface; +use Drupal\field\FieldStorageConfigInterface; use Drush\Attributes as CLI; use Drush\Commands\AutowireTrait; use Drush\Commands\DrushCommands; @@ -251,6 +252,8 @@ protected function getFieldConfigs(string $entityType, ?string $bundle): array protected function deleteFieldConfig(FieldConfigInterface $fieldConfig): void { $fieldStorage = $fieldConfig->getFieldStorageDefinition(); + assert($fieldStorage instanceof FieldStorageConfigInterface); + $bundles = $this->entityTypeBundleInfo->getBundleInfo($fieldConfig->getTargetEntityTypeId()); $bundleLabel = $bundles[$fieldConfig->getTargetBundle()]['label']; diff --git a/src/Config/ConfigLocator.php b/src/Config/ConfigLocator.php index 1458533975..962dbfb7c9 100644 --- a/src/Config/ConfigLocator.php +++ b/src/Config/ConfigLocator.php @@ -5,7 +5,7 @@ namespace Drush\Config; use Consolidation\Config\ConfigInterface; -use Consolidation\Config\Loader\ConfigLoaderInterface; +use Consolidation\Config\Loader\ConfigLoader; use Consolidation\Config\Loader\ConfigProcessor; use Consolidation\Config\Util\EnvConfig; use Drush\Config\Loader\YamlConfigLoader; @@ -32,12 +32,9 @@ */ class ConfigLocator { - /** - * @var ConfigInterface - */ - protected $config; + protected DrushConfig $config; - protected $isLocal; + protected bool $isLocal; protected $sources = false; @@ -194,7 +191,9 @@ public function config(): ConfigInterface */ public function addEnvironment(Environment $environment): self { - $this->config->getContext(self::ENVIRONMENT_CONTEXT)->import($environment->exportConfigData()); + /** @var DrushConfig $context */ + $context = $this->config->getContext(self::ENVIRONMENT_CONTEXT); + $context->replace($environment->exportConfigData()); return $this; } @@ -317,7 +316,7 @@ public function addConfigPaths(string $contextName, array $paths): self /** * Adds $configFiles to the list of config files. */ - protected function addConfigFiles(ConfigProcessor $processor, ConfigLoaderInterface $loader, array $configFiles): void + protected function addConfigFiles(ConfigProcessor $processor, ConfigLoader $loader, array $configFiles): void { foreach ($configFiles as $configFile) { $processor->extend($loader->load($configFile)); diff --git a/src/Drupal/Migrate/MigrateExecutable.php b/src/Drupal/Migrate/MigrateExecutable.php index ecf120bf00..81fd9c6e87 100644 --- a/src/Drupal/Migrate/MigrateExecutable.php +++ b/src/Drupal/Migrate/MigrateExecutable.php @@ -4,7 +4,6 @@ namespace Drush\Drupal\Migrate; -use Drupal\migrate\MigrateException; use Drupal\Component\Utility\Timer; use Drupal\migrate\Event\MigrateEvents; use Drupal\migrate\Event\MigrateImportEvent; @@ -22,6 +21,7 @@ use Drush\Drupal\Migrate\MigrateEvents as MigrateRunnerEvents; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class MigrateExecutable extends MigrateExecutableBase { @@ -164,8 +164,10 @@ public function __construct(MigrationInterface $migration, MigrateMessageInterfa $this->listeners[MigrateRunnerEvents::DRUSH_MIGRATE_PREPARE_ROW] = [$this, 'onPrepareRow']; $this->listeners[MigrateMissingSourceRowsEvent::class] = [$this, 'onMissingSourceRows']; + $eventDispatcher = $this->getEventDispatcher(); + assert($eventDispatcher instanceof EventDispatcherInterface); foreach ($this->listeners as $event => $listener) { - $this->getEventDispatcher()->addListener($event, $listener); + $eventDispatcher->addListener($event, $listener); } } @@ -612,8 +614,10 @@ public function progressFinish(): void */ public function unregisterListeners(): void { + $eventDispatcher = $this->getEventDispatcher(); + assert($eventDispatcher instanceof EventDispatcherInterface); foreach ($this->listeners as $event => $listener) { - $this->getEventDispatcher()->removeListener($event, $listener); + $eventDispatcher->removeListener($event, $listener); } } } diff --git a/src/Preflight/PreflightSiteLocator.php b/src/Preflight/PreflightSiteLocator.php index f15d7dddf7..866c48ba4c 100644 --- a/src/Preflight/PreflightSiteLocator.php +++ b/src/Preflight/PreflightSiteLocator.php @@ -26,10 +26,10 @@ public function __construct(SiteAliasManager $siteAliasManager) * If 'false' is returned, that indicates that there was an alias name * provided on the commandline that is either missing or invalid. * - * @param PreflightArgsInterface $preflightArgs An alias name or site specification + * @param PreflightArgs $preflightArgs An alias name or site specification * @param string $root The default Drupal root (from site:set, --root or cwd) */ - public function findSite(PreflightArgsInterface $preflightArgs, Environment $environment, string $root): SiteAlias|false + public function findSite(PreflightArgs $preflightArgs, Environment $environment, string $root): SiteAlias|false { $self = $this->determineSelf($preflightArgs, $environment, $root); @@ -47,7 +47,7 @@ public function findSite(PreflightArgsInterface $preflightArgs, Environment $env * or, if those are invalid, then generate one from * the provided root and URI. */ - protected function determineSelf(PreflightArgsInterface $preflightArgs, Environment $environment, $root): SiteAlias|false + protected function determineSelf(PreflightArgs $preflightArgs, Environment $environment, $root): SiteAlias|false { if ($preflightArgs->hasAlias()) { $aliasName = $preflightArgs->alias(); @@ -85,7 +85,7 @@ protected function determineSelf(PreflightArgsInterface $preflightArgs, Environm /** * Generate @self from the provided root and URI. */ - protected function buildSelf(PreflightArgsInterface $preflightArgs, ?string $root): SiteAlias + protected function buildSelf(PreflightArgs $preflightArgs, ?string $root): SiteAlias { // If there is no root, then return '@none' if (!$root) { diff --git a/src/Psysh/DrushCommand.php b/src/Psysh/DrushCommand.php index ca189ada9f..cfadd4f16d 100644 --- a/src/Psysh/DrushCommand.php +++ b/src/Psysh/DrushCommand.php @@ -6,8 +6,9 @@ use Consolidation\AnnotatedCommand\AnnotatedCommand; use Drush\Drush; -use Symfony\Component\Console\Command\Command; use Psy\Command\Command as BaseCommand; +use Psy\Output\ShellOutput; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -62,6 +63,8 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): int { + assert($output instanceof ShellOutput); + $args = $input->getArguments(); $first = array_shift($args); diff --git a/src/Psysh/DrushHelpCommand.php b/src/Psysh/DrushHelpCommand.php index f46bf22724..636115a77a 100644 --- a/src/Psysh/DrushHelpCommand.php +++ b/src/Psysh/DrushHelpCommand.php @@ -5,7 +5,9 @@ namespace Drush\Psysh; use Drush\Commands\DrushCommands; +use Psy\Command\Command; use Psy\Command\Command as BaseCommand; +use Psy\Output\ShellOutput; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -44,9 +46,13 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): int { + assert($output instanceof ShellOutput); + if ($name = $input->getArgument('command_name')) { // Help for an individual command. - $output->page($this->getApplication()->get($name)->asText()); + /** @var Command $command */ + $command = $this->getApplication()->get($name); + $output->page($command->asText()); } else { $namespaces = [];