diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8886457..802a12c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,10 @@ permissions: jobs: testsuite: - uses: ADmad/.github/.github/workflows/testsuite-with-db.yml@master + uses: ADmad/.github/.github/workflows/testsuite-with-db.yml@cake6 secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} cs-stan: - uses: ADmad/.github/.github/workflows/cs-stan.yml@master + uses: ADmad/.github/.github/workflows/cs-stan.yml@cake6 secrets: inherit diff --git a/README.md b/README.md index 1058a16..d8473a4 100755 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Add the `FootprintMiddleware` to the middleware queue in your `Application::midd method: ```php -$middleware->add('Muffin/Footprint.Footprint'); +$middleware->add(new \Muffin\Footprint\Middleware\FootprintMiddleware()); ``` It must be added **after** `AuthenticationMiddleware` to ensure that it can read diff --git a/composer.json b/composer.json index 65c6000..853353b 100755 --- a/composer.json +++ b/composer.json @@ -30,11 +30,12 @@ "source": "https://github.com/usemuffin/footprint" }, "require": { - "cakephp/cakephp": "^5.0" + "php": ">=8.5", + "cakephp/cakephp": "^6.0" }, "require-dev": { - "cakephp/cakephp-codesniffer": "^5.0", - "phpunit/phpunit": "^11.5.3 || ^12.4 || ^13.0" + "cakephp/cakephp-codesniffer": "^6.0", + "phpunit/phpunit": "^13.0" }, "scripts": { "cs-check": "phpcs --colors --parallel=16 -p src/ tests/", @@ -52,6 +53,8 @@ "Cake\\Test\\Fixture\\": "vendor/cakephp/cakephp/tests/Fixture" } }, + "minimum-stability": "dev", + "prefer-stable": true, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true diff --git a/phpstan.neon b/phpstan.neon index a9e3fc4..1c08d53 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,8 +3,3 @@ parameters: treatPhpDocTypesAsCertain: false paths: - src/ - ignoreErrors: - - - message: "#^Call to an undefined method object\\:\\:getEventManager\\(\\)\\.$#" - count: 1 - path: src/FootprintPlugin.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 36952e4..73635b5 100755 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ - + - + ./tests/ diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 46cbdcc..7c28b48 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,11 +1,5 @@ - - - - - - diff --git a/src/Event/FootprintListener.php b/src/Event/FootprintListener.php index f4bb605..b4b829c 100644 --- a/src/Event/FootprintListener.php +++ b/src/Event/FootprintListener.php @@ -19,7 +19,7 @@ class FootprintListener implements EventListenerInterface * * @var array */ - protected array $_defaultConfig = [ + protected array $defaultConfig = [ 'events' => [ 'Model.beforeFind' => -100, 'Model.beforeRules' => -100, @@ -34,7 +34,7 @@ class FootprintListener implements EventListenerInterface * * @var \Cake\Datasource\EntityInterface|null */ - protected ?EntityInterface $_currentUser; + protected ?EntityInterface $currentUser; /** * Constructor. @@ -45,7 +45,7 @@ class FootprintListener implements EventListenerInterface public function __construct(?EntityInterface $user = null, array $config = []) { $this->setConfig($config); - $this->_currentUser = $user; + $this->currentUser = $user; } /** @@ -70,7 +70,7 @@ public function implementedEvents(): array */ public function setUser(EntityInterface $entity): void { - $this->_currentUser = $entity; + $this->currentUser = $entity; } /** @@ -80,7 +80,7 @@ public function setUser(EntityInterface $entity): void */ public function getUser(): ?EntityInterface { - return $this->_currentUser; + return $this->currentUser; } /** @@ -97,8 +97,8 @@ public function handleEvent( ArrayObject $options, ): void { $key = $this->getConfig('optionKey'); - if ($this->_currentUser && empty($options[$key])) { - $options[$key] = $this->_currentUser; + if ($this->currentUser && empty($options[$key])) { + $options[$key] = $this->currentUser; } } } diff --git a/src/FootprintPlugin.php b/src/FootprintPlugin.php index bbb68fa..6203bc7 100644 --- a/src/FootprintPlugin.php +++ b/src/FootprintPlugin.php @@ -6,6 +6,7 @@ use Cake\Core\BasePlugin; use Cake\Core\PluginApplicationInterface; use Cake\Event\EventInterface; +use Cake\ORM\Table; use Muffin\Footprint\Event\FootprintListener; class FootprintPlugin extends BasePlugin @@ -25,7 +26,7 @@ class FootprintPlugin extends BasePlugin /** * Bootstrap hook * - * @param \Cake\Core\PluginApplicationInterface $app Application instance. + * @param \Cake\Core\PluginApplicationInterface $app Application instance. * @return void */ public function bootstrap(PluginApplicationInterface $app): void @@ -34,7 +35,10 @@ public function bootstrap(PluginApplicationInterface $app): void 'Model.initialize', /** @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event */ function (EventInterface $event): void { - $event->getSubject()->getEventManager()->on(static::getListener()); + $table = $event->getSubject(); + if ($table instanceof Table) { + $table->getEventManager()->on(static::getListener()); + } }, ); } diff --git a/src/Model/Behavior/FootprintBehavior.php b/src/Model/Behavior/FootprintBehavior.php index 0c0b92a..aacb0cb 100644 --- a/src/Model/Behavior/FootprintBehavior.php +++ b/src/Model/Behavior/FootprintBehavior.php @@ -21,7 +21,7 @@ class FootprintBehavior extends Behavior * * @var array */ - protected array $_defaultConfig = [ + protected array $defaultConfig = [ 'events' => [ 'Model.beforeSave' => [ 'created_by' => 'new', @@ -34,9 +34,9 @@ class FootprintBehavior extends Behavior ]; /** - * Intialize the behavior. + * Initialize the behavior. * - * @param array $config Config options. + * @param array $config Config options. * @return void */ public function initialize(array $config): void @@ -54,7 +54,7 @@ public function initialize(array $config): void $config['propertiesMap'][] = $field; } } - $this->_config['events'][$name] = $options; + $this->config['events'][$name] = $options; } foreach ($config['propertiesMap'] as $property => $map) { @@ -82,7 +82,7 @@ public function implementedEvents(): array { /** @phpstan-ignore-next-line */ return array_fill_keys( - array_keys($this->_config['events']), + array_keys($this->config['events']), 'dispatch', ); } @@ -100,19 +100,19 @@ public function implementedEvents(): array public function dispatch(EventInterface $event, EntityInterface|SelectQuery $data, ArrayObject $options): void { $eventName = $event->getName(); - if (empty($this->_config['events'][$eventName])) { + if (empty($this->config['events'][$eventName])) { return; } - $fields = $this->_config['events'][$eventName]; + $fields = $this->config['events'][$eventName]; if ($data instanceof EntityInterface) { - $this->_injectEntity($data, $options, $fields); + $this->injectEntity($data, $options, $fields); return; } - $this->_injectConditions($data, $options, $fields); + $this->injectConditions($data, $options, $fields); } /** @@ -123,7 +123,7 @@ public function dispatch(EventInterface $event, EntityInterface|SelectQuery $dat * @param array $fields Field configuration. * @return void */ - protected function _injectConditions(SelectQuery $query, ArrayObject $options, array $fields): void + protected function injectConditions(SelectQuery $query, ArrayObject $options, array $fields): void { foreach (array_keys($fields) as $field) { $path = $this->getConfig('propertiesMap.' . $field); @@ -135,7 +135,7 @@ protected function _injectConditions(SelectQuery $query, ArrayObject $options, a return; } - $alias = $this->_table->aliasField($field); + $alias = $this->table->aliasField($field); !$check && $check = preg_match( '/^' . $alias . '/', // TODO: Add test to show that cloning is necessary here to avoid issue mentioned in @@ -146,7 +146,7 @@ protected function _injectConditions(SelectQuery $query, ArrayObject $options, a $value = Hash::get((array)$options, $path); if (!$check && $value) { - $query->where([$this->_table->aliasField($field) => $value]); + $query->where([$this->table->aliasField($field) => $value]); } } } @@ -159,7 +159,7 @@ protected function _injectConditions(SelectQuery $query, ArrayObject $options, a * @param array $fields Field configuration. * @return void */ - protected function _injectEntity(EntityInterface $entity, ArrayObject $options, array $fields): void + protected function injectEntity(EntityInterface $entity, ArrayObject $options, array $fields): void { $new = $entity->isNew() !== false; diff --git a/tests/TestCase/FootprintPluginTest.php b/tests/TestCase/FootprintPluginTest.php new file mode 100644 index 0000000..e75097e --- /dev/null +++ b/tests/TestCase/FootprintPluginTest.php @@ -0,0 +1,76 @@ +setValue(null, null); + + parent::tearDown(); + } + + public function testMiddlewarePassesIdentityToDefaultBehavior(): void + { + $eventManager = new EventManager(); + $app = $this->createStub(PluginApplicationInterface::class); + $app->method('getEventManager')->willReturn($eventManager); + (new FootprintPlugin())->bootstrap($app); + + $table = new Table([ + 'table' => 'articles', + 'alias' => 'Articles', + 'connection' => ConnectionManager::get('test'), + 'eventManager' => $eventManager, + ]); + $table->addBehavior('Muffin/Footprint.Footprint'); + + $user = new Entity(['id' => 42]); + $request = (new ServerRequest())->withAttribute('identity', $user); + $response = new Response(); + $handler = $this->createMock(RequestHandlerInterface::class); + $handler->expects($this->once())->method('handle')->with($request) + ->willReturnCallback(function () use ($table, $response): Response { + $entity = $table->newEntity(['title' => 'With request identity']); + $saved = $table->saveOrFail($entity); + + $this->assertSame(42, $saved->created_by); + $this->assertSame(42, $saved->modified_by); + + return $response; + }); + + $this->assertSame($response, (new FootprintMiddleware())->process($request, $handler)); + } + + public function testInitializeWithoutSubject(): void + { + $eventManager = new EventManager(); + $app = $this->createStub(PluginApplicationInterface::class); + $app->method('getEventManager')->willReturn($eventManager); + (new FootprintPlugin())->bootstrap($app); + + $event = new Event('Model.initialize'); + $this->assertSame($event, $eventManager->dispatch($event)); + } +}