diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e09554f5..b050623d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,9 +8,15 @@ jobs: strategy: fail-fast: false matrix: - php-version: ['7.2', '7.4', '8.0'] + php-version: ['7.4', '8.0'] db-type: [sqlite, mysql, pgsql] - name: PHP ${{ matrix.php-version }} & ${{ matrix.db-type }} + prefer-lowest: [''] + include: + - php-version: '7.2' + db-type: 'mysql' + prefer-lowest: 'prefer-lowest' + - php-version: '8.1' + db-type: 'mysql' services: postgres: @@ -21,9 +27,7 @@ jobs: POSTGRES_PASSWORD: postgres steps: - - uses: actions/checkout@v1 - with: - fetch-depth: 1 + - uses: actions/checkout@v2 - name: Setup Service if: matrix.db-type == 'mysql' @@ -39,7 +43,12 @@ jobs: coverage: pcov - name: Composer install - run: composer install + run: | + if ${{ matrix.prefer-lowest == 'prefer-lowest' }}; then + composer update --prefer-lowest --prefer-stable + else + composer update + fi - name: Run PHPUnit run: | @@ -47,24 +56,22 @@ jobs: if [[ ${{ matrix.db-type }} == 'mysql' ]]; then export DB_URL='mysql://root:root@127.0.0.1/cakephp'; fi if [[ ${{ matrix.db-type }} == 'pgsql' ]]; then export DB_URL='postgres://postgres:postgres@127.0.0.1/postgres'; fi - if [[ ${{ matrix.php-version }} == '7.2' && ${{ matrix.db-type }} == 'sqlite' ]]; then + if [[ ${{ matrix.php-version }} == '7.4' && ${{ matrix.db-type }} == 'sqlite' ]]; then vendor/bin/phpunit --coverage-clover=coverage.xml else vendor/bin/phpunit fi - name: Code Coverage Report - if: success() && matrix.php-version == '7.2' && matrix.db-type == 'sqlite' - uses: codecov/codecov-action@v1 + if: success() && matrix.php-version == '7.4' && matrix.db-type == 'sqlite' + uses: codecov/codecov-action@v2 cs-stan: name: Coding Standard & Static Analysis runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v1 - with: - fetch-depth: 1 + - uses: actions/checkout@v2 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -74,7 +81,7 @@ jobs: coverage: none - name: Composer Install - run: composer require --dev cakephp/cakephp-codesniffer:^4.1 psalm/phar:^3.18 phpstan/phpstan:^0.12 + run: composer require --dev cakephp/cakephp-codesniffer:^4.1 psalm/phar:~4.19 phpstan/phpstan:~1.4 - name: Run phpcs run: vendor/bin/phpcs --report=checkstyle --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/ diff --git a/composer.json b/composer.json index ec651fd30..8c128aae3 100644 --- a/composer.json +++ b/composer.json @@ -42,8 +42,8 @@ "cakephp/cakephp": "^4.0" }, "require-dev": { - "phpunit/phpunit": "~8.5.0", - "friendsofcake/cakephp-test-utilities": "^2.0", + "phpunit/phpunit": "^8.5.23 || ^9.3", + "friendsofcake/cakephp-test-utilities": "^2.0.1", "friendsofcake/search": "^6.0" }, "autoload": { diff --git a/phpstan.neon b/phpstan.neon index 5fca1c0da..2dcad0f48 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,7 +6,7 @@ parameters: paths: - src checkMissingIterableValueType: false - excludes_analyse: + excludePaths: - */src/TestSuite/* universalObjectCratesClasses: - Crud\Event\Subject diff --git a/psalm.xml b/psalm.xml index f1a9e621e..f2505098c 100644 --- a/psalm.xml +++ b/psalm.xml @@ -14,6 +14,10 @@ + + + + diff --git a/src/Action/BaseAction.php b/src/Action/BaseAction.php index 8ba621dad..efd76dda1 100644 --- a/src/Action/BaseAction.php +++ b/src/Action/BaseAction.php @@ -314,7 +314,7 @@ protected function _deriveResourceName(): string /** * Additional auxiliary events emitted if certain traits are loaded * - * @return array + * @return array */ public function implementedEvents(): array { diff --git a/src/Controller/Component/CrudComponent.php b/src/Controller/Component/CrudComponent.php index 11ebb4078..fe9aa2c57 100644 --- a/src/Controller/Component/CrudComponent.php +++ b/src/Controller/Component/CrudComponent.php @@ -108,7 +108,7 @@ class CrudComponent extends Component * * `eventLogging` boolean to determine whether the class should log triggered events. * - * @var array + * @var array */ protected $_defaultConfig = [ 'actions' => [], @@ -611,13 +611,12 @@ public function eventLog(): array /** * Sets the model class to be used during the action execution. * - * @param string $modelName The name of the model to load. + * @param string $modelName The name of the model to use. * @return void */ public function useModel(string $modelName): void { - $this->_controller->loadModel($modelName); - [, $this->_modelName] = pluginSplit($modelName); + $this->_modelName = $modelName; } /** @@ -627,7 +626,15 @@ public function useModel(string $modelName): void */ public function table(): Table { - return $this->_controller->{$this->_modelName}; + if (method_exists($this->_controller, 'fetchTable')) { + return $this->_controller->fetchTable($this->_modelName); + } + + /** + * @var \Cake\ORM\Table + * @psalm-suppress DeprecatedMethod + */ + return $this->_controller->loadModel($this->_modelName); } /** diff --git a/src/Core/BaseObject.php b/src/Core/BaseObject.php index ed49facfb..1baf03507 100644 --- a/src/Core/BaseObject.php +++ b/src/Core/BaseObject.php @@ -50,7 +50,7 @@ public function __construct(Controller $Controller, array $config = []) /** * List of implemented events * - * @return array + * @return array */ public function implementedEvents(): array { diff --git a/src/Core/ProxyTrait.php b/src/Core/ProxyTrait.php index a03bd2c1e..9f9bb01a1 100644 --- a/src/Core/ProxyTrait.php +++ b/src/Core/ProxyTrait.php @@ -127,10 +127,17 @@ protected function _response(): Response */ protected function _table() { + $modelType = $this->getConfig('modelFactory'); + + if (!$modelType && method_exists($this->_controller(), 'fetchTable')) { + return $this->_controller()->fetchTable(); + } + + /** @psalm-suppress DeprecatedMethod */ return $this->_controller() ->loadModel( null, - $this->getConfig('modelFactory') ?: $this->_controller()->getModelType() + $modelType ?: $this->_controller()->getModelType() ); } diff --git a/src/Error/Exception/ValidationException.php b/src/Error/Exception/ValidationException.php index feaccd888..f04963718 100644 --- a/src/Error/Exception/ValidationException.php +++ b/src/Error/Exception/ValidationException.php @@ -37,7 +37,7 @@ class ValidationException extends BadRequestException */ public function __construct(EntityInterface $entity, int $code = 422, ?Throwable $previous = null) { - $this->_validationErrors = array_filter((array)$entity->getErrors()); + $this->_validationErrors = array_filter($entity->getErrors()); $flat = Hash::flatten($this->_validationErrors); $errorCount = $this->_validationErrorCount = count($flat); diff --git a/src/Listener/ApiListener.php b/src/Listener/ApiListener.php index 58623ccb7..6a5aa0037 100644 --- a/src/Listener/ApiListener.php +++ b/src/Listener/ApiListener.php @@ -52,7 +52,7 @@ class ApiListener extends BaseListener * * We attach at priority 10 so normal bound events can run before us * - * @return array + * @return array */ public function implementedEvents(): array { diff --git a/src/Listener/ApiPaginationListener.php b/src/Listener/ApiPaginationListener.php index db81822ef..10dac4031 100644 --- a/src/Listener/ApiPaginationListener.php +++ b/src/Listener/ApiPaginationListener.php @@ -20,7 +20,7 @@ class ApiPaginationListener extends BaseListener * * We attach at priority 10 so normal bound events can run before us * - * @return array + * @return array */ public function implementedEvents(): array { diff --git a/src/Listener/ApiQueryLogListener.php b/src/Listener/ApiQueryLogListener.php index 132556b64..94ecb8370 100644 --- a/src/Listener/ApiQueryLogListener.php +++ b/src/Listener/ApiQueryLogListener.php @@ -37,7 +37,7 @@ class ApiQueryLogListener extends BaseListener * * We attach at priority 10 so normal bound events can run before us * - * @return array + * @return array */ public function implementedEvents(): array { @@ -65,6 +65,7 @@ public function setupLogging(EventInterface $event): void try { $connection = $this->_getSource($connectionName); $connection->enableQueryLogging(true); + /** @psalm-suppress InternalMethod */ $connection->setLogger(new QueryLogger()); } catch (MissingDatasourceConfigException $e) { //Safe to ignore this :-) diff --git a/src/Listener/BaseListener.php b/src/Listener/BaseListener.php index bfcb86052..87bb01f55 100644 --- a/src/Listener/BaseListener.php +++ b/src/Listener/BaseListener.php @@ -29,7 +29,7 @@ abstract class BaseListener extends BaseObject implements EventListenerInterface * - invalidId : Called if the ID format validation failed * - setFlash : Called before any FlashComponent::set() * - * @return array + * @return array */ public function implementedEvents(): array { diff --git a/src/Listener/RedirectListener.php b/src/Listener/RedirectListener.php index 1869c3e95..ecdcb1444 100644 --- a/src/Listener/RedirectListener.php +++ b/src/Listener/RedirectListener.php @@ -30,7 +30,7 @@ class RedirectListener extends BaseListener * Returns a list of all events that will fire in the controller during its lifecycle. * You can override this function to add your own listener callbacks * - * @return array + * @return array */ public function implementedEvents(): array { diff --git a/src/Listener/RelatedModelsListener.php b/src/Listener/RelatedModelsListener.php index b32302482..a8c130e2c 100644 --- a/src/Listener/RelatedModelsListener.php +++ b/src/Listener/RelatedModelsListener.php @@ -21,7 +21,7 @@ class RelatedModelsListener extends BaseListener /** * Returns a list of events this listener is interested in. * - * @return array + * @return array */ public function implementedEvents(): array { diff --git a/src/Listener/SearchListener.php b/src/Listener/SearchListener.php index 903c2295a..ec8876ba7 100644 --- a/src/Listener/SearchListener.php +++ b/src/Listener/SearchListener.php @@ -26,7 +26,7 @@ class SearchListener extends BaseListener * Returns a list of all events that will fire in the controller during its lifecycle. * You can override this function to add your own listener callbacks * - * @return array + * @return array */ public function implementedEvents(): array { diff --git a/src/TestSuite/IntegrationTestCase.php b/src/TestSuite/IntegrationTestCase.php index 74c5a39c6..c94cd706d 100644 --- a/src/TestSuite/IntegrationTestCase.php +++ b/src/TestSuite/IntegrationTestCase.php @@ -49,7 +49,9 @@ public function setUp(): void Router::extensions('json'); - Router::connect('/:controller', ['action' => 'index'], ['routeClass' => 'DashedRoute']); - Router::connect('/:controller/:action/*', [], ['routeClass' => 'DashedRoute']); + $routeBuilder = Router::createRouteBuilder('/'); + + $routeBuilder->connect('/{controller}', ['action' => 'index'], ['routeClass' => 'DashedRoute']); + $routeBuilder->connect('/{controller}/{action}/*', [], ['routeClass' => 'DashedRoute']); } } diff --git a/src/Traits/FindMethodTrait.php b/src/Traits/FindMethodTrait.php index 6784054f6..0d7a7dcdb 100644 --- a/src/Traits/FindMethodTrait.php +++ b/src/Traits/FindMethodTrait.php @@ -61,7 +61,10 @@ protected function _findRecord(?string $id, Subject $subject): EntityInterface [$finder, $options] = $this->_extractFinder(); $query = $repository->find($finder, $options); - /** @psalm-suppress PossiblyInvalidArgument */ + /** + * @psalm-suppress PossiblyInvalidArgument + * @psalm-suppress InvalidArrayOffset + */ $query->where([current($query->aliasField($repository->getPrimaryKey())) => $id]); $subject->set([ diff --git a/src/Traits/SerializeTrait.php b/src/Traits/SerializeTrait.php index 589a99950..97e2746e0 100644 --- a/src/Traits/SerializeTrait.php +++ b/src/Traits/SerializeTrait.php @@ -20,6 +20,6 @@ public function serialize(?array $keys = null) return (array)$this->getConfig('serialize'); } - return $this->setConfig('serialize', (array)$keys); + return $this->setConfig('serialize', $keys); } } diff --git a/tests/TestCase/Action/AddActionTest.php b/tests/TestCase/Action/AddActionTest.php index 2587b84fc..a34631d9f 100644 --- a/tests/TestCase/Action/AddActionTest.php +++ b/tests/TestCase/Action/AddActionTest.php @@ -4,6 +4,7 @@ namespace Crud\Test\TestCase\Action; use Cake\Controller\Component\FlashComponent; +use Cake\Core\Configure; use Cake\Routing\Router; use Crud\TestSuite\IntegrationTestCase; @@ -239,16 +240,17 @@ function ($event) { $this->_subscribeToEvents($this->_controller); - $this->_controller->Blogs = $this->getMockForModel( + $blogs = $this->getMockForModel( $this->tableClass, ['save'], ['alias' => 'Blogs', 'table' => 'blogs'] ); - - $this->_controller->Blogs + $blogs ->expects($this->once()) ->method('save') ->will($this->returnValue(false)); + + $this->getTableLocator()->set('Blogs', $blogs); } ); @@ -314,7 +316,11 @@ function ($event) { $this->assertFalse($this->_subject->success); $this->assertFalse($this->_subject->created); - $expected = '
Name need to be at least 10 characters long
'; + if (version_compare(Configure::version(), '4.3.0', '>=')) { + $expected = '
Name need to be at least 10 characters long
'; + } else { + $expected = '
Name need to be at least 10 characters long
'; + } $this->assertStringContainsString( $expected, (string)$this->_response->getBody(), @@ -344,10 +350,9 @@ public function apiGetHttpMethodProvider() */ public function testApiGet($method) { - Router::scope('/', function ($routes) { - $routes->setExtensions(['json']); - $routes->fallbacks(); - }); + Router::createRouteBuilder('/') + ->setExtensions(['json']) + ->fallbacks(); $this->{$method}('/Blogs/add.json'); diff --git a/tests/TestCase/Action/BaseActionTest.php b/tests/TestCase/Action/BaseActionTest.php index 5e2b961c2..0d680c8bc 100644 --- a/tests/TestCase/Action/BaseActionTest.php +++ b/tests/TestCase/Action/BaseActionTest.php @@ -46,9 +46,9 @@ public function setUp(): void ->addMethods(['foobar']) ->getMock(); $this->Controller->Crud = $this->Crud; - $this->Controller->modelClass = 'CrudExamples'; - $this->Controller->CrudExamples = $this->getTableLocator()->get('Crud.CrudExamples'); - $this->Controller->CrudExamples->setAlias('MyModel'); + $this->Controller->defaultTable = 'CrudExamples'; + + $this->getTableLocator()->get('CrudExamples')->setAlias('MyModel'); $this->actionClassName = $this->getMockBuilder(BaseAction::class) ->addMethods(['_handle']) diff --git a/tests/TestCase/Action/DeleteActionTest.php b/tests/TestCase/Action/DeleteActionTest.php index 743781cae..567d50901 100644 --- a/tests/TestCase/Action/DeleteActionTest.php +++ b/tests/TestCase/Action/DeleteActionTest.php @@ -70,16 +70,17 @@ function ($event) { $this->_subscribeToEvents($this->_controller); - $this->_controller->Blogs = $this->getMockForModel( + $blogs = $this->getMockForModel( $this->tableClass, ['delete'], ['alias' => 'Blogs', 'table' => 'blogs'] ); - - $this->_controller->Blogs + $blogs ->expects($this->once()) ->method('delete') ->will($this->returnValue(true)); + + $this->getTableLocator()->set('Blogs', $blogs); } ); @@ -124,15 +125,16 @@ function ($event) { $event->stopPropagation(); }); - $this->_controller->Blogs = $this->getMockForModel( + $blogs = $this->getMockForModel( $this->tableClass, ['delete'], ['alias' => 'Blogs', 'table' => 'blogs'] ); - - $this->_controller->Blogs + $blogs ->expects($this->never()) ->method('delete'); + + $this->getTableLocator()->set('Blogs', $blogs); } ); @@ -178,16 +180,17 @@ function ($event) { $event->stopPropagation(); }); - $this->_controller->Blogs = $this->getMockForModel( + $blogs = $this->getMockForModel( $this->tableClass, ['delete'], ['alias' => 'Blogs', 'table' => 'blogs'] ); - - $this->_controller->Blogs + $blogs ->expects($this->once()) ->method('delete') ->will($this->returnValue(true)); + + $this->getTableLocator()->set('Blogs', $blogs); } ); diff --git a/tests/TestCase/Action/EditActionTest.php b/tests/TestCase/Action/EditActionTest.php index 95bf9507f..cd80acd9e 100644 --- a/tests/TestCase/Action/EditActionTest.php +++ b/tests/TestCase/Action/EditActionTest.php @@ -4,6 +4,7 @@ namespace Crud\Test\TestCase\Action; use Cake\Controller\Component\FlashComponent; +use Cake\Core\Configure; use Crud\TestSuite\IntegrationTestCase; /** @@ -167,16 +168,17 @@ function ($event) { $this->_subscribeToEvents($this->_controller); - $this->_controller->Blogs = $this->getMockForModel( + $blogs = $this->getMockForModel( $this->tableClass, ['save'], ['alias' => 'Blogs', 'table' => 'blogs'] ); - - $this->_controller->Blogs + $blogs ->expects($this->once()) ->method('save') ->will($this->returnValue(false)); + + $this->getTableLocator()->set('Blogs', $blogs); } ); @@ -242,7 +244,11 @@ function ($event) { $this->assertFalse($this->_subject->success); $this->assertFalse($this->_subject->created); - $expected = '
Name need to be at least 10 characters long
'; + if (version_compare(Configure::version(), '4.3.0', '>=')) { + $expected = '
Name need to be at least 10 characters long
'; + } else { + $expected = '
Name need to be at least 10 characters long
'; + } $this->assertStringContainsString( $expected, (string)$this->_response->getBody(), diff --git a/tests/TestCase/Controller/Component/CrudComponentTest.php b/tests/TestCase/Controller/Component/CrudComponentTest.php index 614f793fd..9e1f85b4e 100644 --- a/tests/TestCase/Controller/Component/CrudComponentTest.php +++ b/tests/TestCase/Controller/Component/CrudComponentTest.php @@ -57,6 +57,7 @@ public function setUp(): void ->onlyMethods(['redirect', 'render']) ->setConstructorArgs([$this->request, $response, 'CrudExamples', EventManager::instance()]) ->getMock(); + $this->controller->defaultTable = 'CrudExamples'; $this->Registry = $this->controller->components(); diff --git a/tests/TestCase/Error/ExceptionRendererTest.php b/tests/TestCase/Error/ExceptionRendererTest.php index 8c79ee68d..f7a2eb1bd 100644 --- a/tests/TestCase/Error/ExceptionRendererTest.php +++ b/tests/TestCase/Error/ExceptionRendererTest.php @@ -63,6 +63,11 @@ public function testNormalExceptionRendering() ], ]; + if (version_compare(Configure::version(), '4.2.0', '<')) { + $expected['exception']['class'] = 'Cake\Core\Exception\Exception'; + $expected['exception']['code'] = 500; + } + $actual = $viewVars['data']; unset($actual['trace'], $actual['file'], $actual['line']); $this->assertEquals($expected, $actual); @@ -139,6 +144,11 @@ public function testNormalExceptionRenderingQueryLog() ], ]; + if (version_compare(Configure::version(), '4.2.0', '<')) { + $expected['exception']['class'] = 'Cake\Core\Exception\Exception'; + $expected['exception']['code'] = 500; + } + $actual = $viewVars['data']; $queryLog = $viewVars['queryLog']; @@ -209,6 +219,11 @@ public function testNormalNestedExceptionRendering() ], ]; + if (version_compare(Configure::version(), '4.2.0', '<')) { + $expected['exception']['class'] = 'Cake\Core\Exception\Exception'; + $expected['exception']['code'] = 500; + } + $actual = $viewVars['data']; unset($actual['trace'], $actual['file'], $actual['line']); $this->assertEquals($expected, $actual); @@ -269,6 +284,11 @@ public function testMissingViewExceptionDuringRendering() ], ]; + if (version_compare(Configure::version(), '4.2.0', '<')) { + $expected['exception']['class'] = 'Cake\Core\Exception\Exception'; + $expected['exception']['code'] = 500; + } + $actual = $viewVars['data']; unset($actual['trace'], $actual['file'], $actual['line']); $this->assertEquals($expected, $actual); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e83cbd630..d53343c50 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,5 +1,6 @@ create(TMP . 'cache/models', 0777); -$TMP->create(TMP . 'cache/persistent', 0777); -$TMP->create(TMP . 'cache/views', 0777); - $cache = [ 'default' => [ 'engine' => 'File' @@ -90,3 +86,8 @@ ]); Plugin::getCollection()->add(new \Crud\Plugin()); + +Configure::write( + 'Error.ignoredDeprecationPaths', + ['vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php'] +); diff --git a/tests/test_app/config/routes.php b/tests/test_app/config/routes.php index 1e3361239..f92e7cbbb 100644 --- a/tests/test_app/config/routes.php +++ b/tests/test_app/config/routes.php @@ -1,11 +1,9 @@ scope('/', function ($routes) { + $routes->setExtensions(['json']); -Router::scope('/', function ($routes) { - $routes->extensions(['json']); - - $routes->connect('/:controller', ['action' => 'index'], ['routeClass' => 'InflectedRoute']); - $routes->connect('/:controller/:action/*', [], ['routeClass' => 'InflectedRoute']); + $routes->connect('/{controller}', ['action' => 'index'], ['routeClass' => 'InflectedRoute']); + $routes->connect('/{controller}/{action}/*', [], ['routeClass' => 'InflectedRoute']); }); diff --git a/tests/test_app/src/Model/Table/CrudExamplesTable.php b/tests/test_app/src/Model/Table/CrudExamplesTable.php index 80db5f25f..5b45f5e0d 100644 --- a/tests/test_app/src/Model/Table/CrudExamplesTable.php +++ b/tests/test_app/src/Model/Table/CrudExamplesTable.php @@ -14,14 +14,7 @@ */ class CrudExamplesTable extends Table { - public $alias = 'CrudExamples'; - - public $findMethods = [ - 'published' => true, - 'unpublished' => true, - 'firstPublished' => true, - 'firstUnpublished' => true, - ]; + protected $_alias = 'CrudExamples'; /** * [initialize description]