diff --git a/.editorconfig b/.editorconfig index 5cadf7b..3fe4879 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,3 +12,6 @@ trim_trailing_whitespace = true [*.yml] indent_size = 2 + +[*.neon] +indent_style = tab diff --git a/.gitattributes b/.gitattributes index 43b70c6..371212d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,3 +8,5 @@ phpunit.xml.dist export-ignore phpcs.xml.dist export-ignore .github export-ignore +phpstan.neon export-ignore +psalm.xml export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f13f908..aa2c840 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,78 +8,14 @@ on: branches: - '*' -jobs: - testsuite-linux: - runs-on: ubuntu-18.04 - strategy: - fail-fast: false - matrix: - php-version: ['5.6', '7.4', '8.0'] - prefer-lowest: [''] - include: - - php-version: '5.6' - prefer-lowest: 'prefer-lowest' - - steps: - - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - coverage: none - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl - - - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Get date part for cache key - id: key-date - run: echo "::set-output name=date::$(date +'%Y-%m')" - - - name: Cache composer dependencies - uses: actions/cache@v1 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}-${{ matrix.prefer-lowest }} - - - name: Composer install - run: | - if ${{ matrix.prefer-lowest == 'prefer-lowest' }}; then - composer update --prefer-lowest --prefer-stable - else - composer install - fi +permissions: + contents: read - - name: Run PHPUnit - run: | - if [[ ${{ matrix.php-version }} == '5.6' ]]; then - vendor/bin/phpunit tests/php56/ - else - vendor/bin/phpunit - fi +jobs: + testsuite: + uses: cakephp/.github/.github/workflows/testsuite-without-db.yml@5.x + secrets: inherit cs-stan: - name: Coding Standard & Static Analysis - runs-on: ubuntu-18.04 - - steps: - - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '7.4' - extensions: mbstring, intl - coverage: none - tools: psalm:~4.1.0, cs2pr - - - name: Composer Install - run: composer install - - - name: Run phpcs - run: vendor/bin/phpcs --report=checkstyle src/ tests/ | cs2pr - - - name: Run psalm - run: psalm --output-format=github + uses: cakephp/.github/.github/workflows/cs-stan.yml@5.x + secrets: inherit diff --git a/composer.json b/composer.json index 3a14a04..bfa86d3 100644 --- a/composer.json +++ b/composer.json @@ -10,13 +10,13 @@ } ], "require": { - "php": ">=5.6.0", + "php": ">=8.1", "composer-plugin-api": "^1.0 || ^2.0" }, "require-dev": { - "cakephp/cakephp-codesniffer": "^3.3", + "cakephp/cakephp-codesniffer": "5.x-dev", "composer/composer": "^2.0", - "phpunit/phpunit": "^5.7 || ^6.5 || ^8.5 || ^9.3" + "phpunit/phpunit": "^9.5.19" }, "autoload": { "psr-4": { @@ -25,15 +25,17 @@ }, "autoload-dev": { "psr-4": { - "Cake\\Test\\TestCase\\Composer\\": "tests/TestCase/", - "Cake\\Test\\TestCase\\Composer\\Php56\\": "tests/php56/TestCase/" + "Cake\\Test\\TestCase\\Composer\\": "tests/TestCase/" } }, "extra": { "class": "Cake\\Composer\\Plugin" }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } }, "prefer-stable": true } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..df36ddf --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 6 + paths: + - src/ diff --git a/psalm.xml b/psalm.xml index 8dc065a..44c6df8 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ + + + + + diff --git a/src/Installer/PluginInstaller.php b/src/Installer/PluginInstaller.php deleted file mode 100644 index f1fa998..0000000 --- a/src/Installer/PluginInstaller.php +++ /dev/null @@ -1,77 +0,0 @@ - ' . str_pad($text, $width) . ''; - }; - - $messages = [ - '', - '', - $wrap(''), - $wrap($title), - $wrap(''), - ]; - - $lines = explode("\n", wordwrap($text, 68)); - foreach ($lines as $line) { - $messages[] = $wrap($line); - } - - $messages = array_merge($messages, [$wrap(''), '', '']); - - $io->write($messages); - } - - /** - * Called whenever composer (re)generates the autoloader - * - * Recreates CakePHP's plugin path map, based on composer information - * and available app-plugins. - * - * @param \Composer\Script\Event $event the composer event object - * @return void - */ - public static function postAutoloadDump(Event $event) - { - $scripts = $event->getComposer()->getPackage()->getScripts(); - if (!isset($scripts['post-autoload-dump'])) { - return; - } - - $postAutoloadDump = 'Cake\Composer\Installer\PluginInstaller::postAutoloadDump'; - if ( - $scripts['post-autoload-dump'] === $postAutoloadDump - || (is_array($scripts['post-autoload-dump']) - && in_array($postAutoloadDump, $scripts['post-autoload-dump']) - ) - ) { - static::warnUser( - 'Action required!', - 'The CakePHP plugin installer v1.3+ no longer requires the "post-autoload-dump" hook.' . - ' Please update your app\'s composer.json file and remove usage of ' . $postAutoloadDump, - $event->getIO() - ); - } - } -} diff --git a/src/Plugin.php b/src/Plugin.php index 621ec86..cf65fb9 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -1,4 +1,6 @@ getComposer(); $config = $composer->getConfig(); @@ -78,16 +81,16 @@ public function postAutoloadDump(Event $event) * Add all composer packages of type `cakephp-plugin`, and all plugins located * in the plugins directory to a plugin-name indexed array of paths. * - * @param \Composer\Package\PackageInterface[] $packages Array of \Composer\Package\PackageInterface objects. - * @param array $pluginDirs The path to the plugins dir. + * @param array<\Composer\Package\PackageInterface> $packages Array of \Composer\Package\PackageInterface objects. + * @param array $pluginDirs The path to the plugins dir. * @param string $vendorDir The path to the vendor dir. - * @return array Plugin name indexed paths to plugins. + * @return array Plugin name indexed paths to plugins. */ public function findPlugins( array $packages, array $pluginDirs = ['plugins'], - $vendorDir = 'vendor' - ) { + string $vendorDir = 'vendor' + ): array { $plugins = []; foreach ($packages as $package) { @@ -103,7 +106,7 @@ public function findPlugins( foreach ($pluginDirs as $path) { $path = $this->getFullPath($path, $vendorDir); if (is_dir($path)) { - $dir = new \DirectoryIterator($path); + $dir = new DirectoryIterator($path); foreach ($dir as $info) { if (!$info->isDir() || $info->isDot()) { continue; @@ -131,7 +134,7 @@ public function findPlugins( * @param string $vendorDir The path to the vendor dir. * @return string */ - public function getFullPath($path, $vendorDir) + public function getFullPath(string $path, string $vendorDir): string { if (preg_match('{^(?:/|[a-z]:|[a-z0-9.]+://)}i', $path)) { return rtrim($path, '/'); @@ -148,11 +151,11 @@ public function getFullPath($path, $vendorDir) * Rewrite the config file with a complete list of plugins. * * @param string $configFile The path to the config file. - * @param array $plugins Array of plugins. + * @param array $plugins Array of plugins. * @param string|null $root The root directory. Defaults to a value generated from `$configFile`. * @return void */ - public function writeConfigFile($configFile, array $plugins, $root = null) + public function writeConfigFile(string $configFile, array $plugins, ?string $root = null): void { $root = $root ?: dirname(dirname($configFile)); @@ -212,7 +215,7 @@ public function writeConfigFile($configFile, array $plugins, $root = null) * @param string $vendorDir Path to composer-vendor dir. * @return string Absolute file path. */ - public function getConfigFilePath($vendorDir) + public function getConfigFilePath(string $vendorDir): string { return $vendorDir . DIRECTORY_SEPARATOR . 'cakephp-plugins.php'; } @@ -224,10 +227,11 @@ public function getConfigFilePath($vendorDir) * @return string The package's primary namespace. * @throws \RuntimeException When the package's primary namespace cannot be determined. */ - public function getPrimaryNamespace(PackageInterface $package) + public function getPrimaryNamespace(PackageInterface $package): string { $primaryNs = null; $autoLoad = $package->getAutoload(); + /** @var array $pathMap */ foreach ($autoLoad as $type => $pathMap) { if ($type !== 'psr-4') { continue; @@ -265,6 +269,6 @@ public function getPrimaryNamespace(PackageInterface $package) ); } - return trim((string)$primaryNs, '\\'); + return trim($primaryNs, '\\'); } } diff --git a/tests/TestCase/Installer/PluginInstallerTest.php b/tests/TestCase/Installer/PluginInstallerTest.php deleted file mode 100644 index 219214d..0000000 --- a/tests/TestCase/Installer/PluginInstallerTest.php +++ /dev/null @@ -1,44 +0,0 @@ -composer = new Composer(); - $this->io = $this->getMockBuilder(IOInterface::class)->getMock(); - } - - public function testPostAutoloadDump() - { - $rootPackage = new RootPackage('cakephp/app', '1.0', '1.0'); - $rootPackage->setType('project'); - $rootPackage->setScripts([ - 'post-autoload-dump' => 'Cake\Composer\Installer\PluginInstaller::postAutoloadDump', - ]); - - $this->composer->setPackage($rootPackage); - $this->io->expects($this->once()) - ->method('write'); - - $event = new Event('post-autoload-dump', $this->composer, $this->io); - - PluginInstaller::postAutoloadDump($event); - } -} diff --git a/tests/php56/TestCase/Installer/PluginInstallerTest.php b/tests/php56/TestCase/Installer/PluginInstallerTest.php deleted file mode 100644 index 92f548c..0000000 --- a/tests/php56/TestCase/Installer/PluginInstallerTest.php +++ /dev/null @@ -1,42 +0,0 @@ -composer = new Composer(); - $this->io = $this->getMockBuilder(IOInterface::class)->getMock(); - } - - public function testPostAutoloadDump() - { - $rootPackage = new RootPackage('cakephp/app', '1.0', '1.0'); - $rootPackage->setType('project'); - $rootPackage->setScripts([ - 'post-autoload-dump' => 'Cake\Composer\Installer\PluginInstaller::postAutoloadDump', - ]); - - $this->composer->setPackage($rootPackage); - $this->io->expects($this->once()) - ->method('write'); - - $event = new Event('post-autoload-dump', $this->composer, $this->io); - - PluginInstaller::postAutoloadDump($event); - } -} diff --git a/tests/php56/TestCase/PHPUnitAssertionCompatTrait.php b/tests/php56/TestCase/PHPUnitAssertionCompatTrait.php deleted file mode 100644 index d3c67ac..0000000 --- a/tests/php56/TestCase/PHPUnitAssertionCompatTrait.php +++ /dev/null @@ -1,11 +0,0 @@ -assertContains($needle, $haystack, $message); - } -} diff --git a/tests/php56/TestCase/PluginTest.php b/tests/php56/TestCase/PluginTest.php deleted file mode 100644 index 6c3c2ac..0000000 --- a/tests/php56/TestCase/PluginTest.php +++ /dev/null @@ -1,334 +0,0 @@ -package = new Package('cake/plugin', '1.0', '1.0'); - $this->package->setType('cakephp-plugin'); - - $this->path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'plugin-installer-test'; - - foreach ($this->testDirs as $dir) { - if (!is_dir($this->path . '/' . $dir)) { - mkdir($this->path . '/' . $dir); - } - } - - $this->composer = new Composer(); - $config = new Config(); - $config->merge([ - 'vendor-dir' => $this->path . '/vendor', - ]); - - $this->composer->setConfig($config); - - /** @var \Composer\IO\IOInterface&\PHPUnit\Framework\MockObject\MockObject $io */ - $io = $this->getMockBuilder(IOInterface::class)->getMock(); - $this->io = $io; - - $httpDownloader = new HttpDownloader($this->io, $config); - - $rm = new RepositoryManager( - $this->io, - $config, - $httpDownloader - ); - $this->composer->setRepositoryManager($rm); - - $this->plugin = new Plugin(); - } - - public function tearDown() - { - parent::tearDown(); - - $dirs = array_reverse($this->testDirs); - - if (is_file($this->path . '/vendor/cakephp-plugins.php')) { - unlink($this->path . '/vendor/cakephp-plugins.php'); - } - - foreach ($dirs as $dir) { - if (is_dir($this->path . '/' . $dir)) { - rmdir($this->path . '/' . $dir); - } - } - } - - public function testGetSubscribedEvents() - { - $expected = [ - 'post-autoload-dump' => 'postAutoloadDump', - ]; - - $this->assertSame($expected, $this->plugin->getSubscribedEvents()); - } - - public function testGetConfigFilePath() - { - $path = $this->plugin->getConfigFilePath(''); - $this->assertFileExists(dirname($path)); - } - - public function testGetPrimaryNamespace() - { - $autoload = [ - 'psr-4' => [ - 'FOC\\Authenticate' => '', - ], - ]; - $this->package->setAutoload($autoload); - - $ns = $this->plugin->getPrimaryNamespace($this->package); - $this->assertEquals('FOC\Authenticate', $ns); - - $autoload = [ - 'psr-4' => [ - 'FOC\Acl\Test' => './tests', - 'FOC\Acl' => '', - ], - ]; - $this->package->setAutoload($autoload); - $ns = $this->plugin->getPrimaryNamespace($this->package); - $this->assertEquals('FOC\Acl', $ns); - - $autoload = [ - 'psr-4' => [ - 'Foo\Bar' => 'foo', - 'Acme\Plugin' => './src', - ], - ]; - $this->package->setAutoload($autoload); - $ns = $this->plugin->getPrimaryNamespace($this->package); - $this->assertEquals('Acme\Plugin', $ns); - - $autoload = [ - 'psr-4' => [ - 'Foo\Bar' => 'bar', - 'Foo\\' => '', - ], - ]; - $this->package->setAutoload($autoload); - $ns = $this->plugin->getPrimaryNamespace($this->package); - $this->assertEquals('Foo', $ns); - - $autoload = [ - 'psr-4' => [ - 'Foo\Bar' => 'bar', - 'Foo' => '.', - ], - ]; - $this->package->setAutoload($autoload); - $ns = $this->plugin->getPrimaryNamespace($this->package); - $this->assertEquals('Foo', $ns); - - $autoload = [ - 'psr-4' => [ - 'Acme\Foo\Bar' => 'bar', - 'Acme\Foo\\' => '', - ], - ]; - $this->package->setAutoload($autoload); - $ns = $this->plugin->getPrimaryNamespace($this->package); - $this->assertEquals('Acme\Foo', $ns); - - $autoload = [ - 'psr-4' => [ - 'Acme\Foo\Bar' => '', - 'Acme\Foo' => 'src', - ], - ]; - $this->package->setAutoload($autoload); - $name = $this->plugin->getPrimaryNamespace($this->package); - $this->assertEquals('Acme\Foo', $name); - } - - public function testFindPlugins() - { - $plugin1 = new Package('cakephp/the-thing', '1.0', '1.0'); - $plugin1->setType('cakephp-plugin'); - $plugin1->setAutoload([ - 'psr-4' => [ - 'TheThing' => 'src/', - ], - ]); - - $plugin2 = new Package('cakephp/princess', '1.0', '1.0'); - $plugin2->setType('cakephp-plugin'); - $plugin2->setAutoload([ - 'psr-4' => [ - 'Princess' => 'src/', - ], - ]); - - $packages = [ - $plugin1, - new Package('SomethingElse', '1.0', '1.0'), - $plugin2, - ]; - - $return = $this->plugin->findPlugins( - $packages, - [$this->path . '/doesnt-exist'], - $this->path . '/vendor' - ); - - $expected = [ - 'Princess' => $this->path . '/vendor/cakephp/princess', - 'TheThing' => $this->path . '/vendor/cakephp/the-thing', - ]; - $this->assertSame($expected, $return, 'Only composer-loaded plugins should be listed'); - - $return = $this->plugin->findPlugins( - $packages, - [$this->path . '/plugins'], - $this->path . '/vendor' - ); - - $expected = [ - 'Fee' => $this->path . '/plugins/Fee', - 'Foe' => $this->path . '/plugins/Foe', - 'Foo' => $this->path . '/plugins/Foo', - 'Fum' => $this->path . '/plugins/Fum', - 'Princess' => $this->path . '/vendor/cakephp/princess', - 'TheThing' => $this->path . '/vendor/cakephp/the-thing', - ]; - $this->assertSame($expected, $return, 'Composer and application plugins should be listed'); - - $return = $this->plugin->findPlugins( - $packages, - [$this->path . '/plugins', $this->path . '/app_plugins'], - $this->path . '/vendor' - ); - - $expected = [ - 'Bar' => $this->path . '/app_plugins/Bar', - 'Fee' => $this->path . '/plugins/Fee', - 'Foe' => $this->path . '/plugins/Foe', - 'Foo' => $this->path . '/plugins/Foo', - 'Fum' => $this->path . '/plugins/Fum', - 'Princess' => $this->path . '/vendor/cakephp/princess', - 'TheThing' => $this->path . '/vendor/cakephp/the-thing', - ]; - $this->assertSame($expected, $return, 'Composer and application plugins should be listed'); - } - - public function testWriteConfigFile() - { - $plugins = [ - 'Fee' => $this->path . '/plugins/Fee', - 'Foe' => $this->path . '/plugins/Foe', - 'Foo' => $this->path . '/plugins/Foo', - 'Fum' => $this->path . '/plugins/Fum', - 'OddOneOut' => '/some/other/path', - 'Princess' => $this->path . '/vendor/cakephp/princess', - 'TheThing' => $this->path . '/vendor/cakephp/the-thing', - 'Vendor\Plugin' => $this->path . '/vendor/vendor/plugin', - ]; - - $path = $this->path . '/vendor/cakephp-plugins.php'; - $this->plugin->writeConfigFile($path, $plugins); - - $this->assertFileExists($path); - $contents = file_get_contents($path); - - $this->assertStringContainsString('assertStringContainsString('$baseDir = dirname(dirname(__FILE__));', $contents); - $this->assertStringContainsString( - "'Fee' => \$baseDir . '/plugins/Fee/'", - $contents, - 'paths should be relative for app-plugins' - ); - $this->assertStringContainsString( - "'Princess' => \$baseDir . '/vendor/cakephp/princess/'", - $contents, - 'paths should be relative for vendor-plugins' - ); - $this->assertStringContainsString( - "'OddOneOut' => '/some/other/path/'", - $contents, - 'paths should stay absolute if it\'s not under the application root' - ); - $this->assertStringContainsString( - "'Vendor/Plugin' => \$baseDir . '/vendor/vendor/plugin/'", - $contents, - 'Plugin namespaces should use forward slash' - ); - - // Ensure all plugin paths are slash terminated - foreach ($plugins as &$plugin) { - $plugin .= '/'; - } - unset($plugin); - - $result = require $path; - $expected = [ - 'plugins' => $plugins, - ]; - $expected['plugins']['Vendor/Plugin'] = $expected['plugins']['Vendor\Plugin']; - unset($expected['plugins']['Vendor\Plugin']); - - $this->assertSame( - $expected, - $result, - 'The evaluated result should be the same as the input except for namespaced plugin' - ); - } -}