diff --git a/composer.json b/composer.json
index f728d07..9631e10 100644
--- a/composer.json
+++ b/composer.json
@@ -36,7 +36,7 @@
},
"require-dev": {
"cakephp/cakephp": "^5.0.0",
- "phpunit/phpunit": "^10.1.0",
+ "phpunit/phpunit": "^10.5.58 || ^11.5.3 || ^12.4",
"cakephp/cakephp-codesniffer": "^5.0"
},
"scripts": {
diff --git a/phpcs.xml b/phpcs.xml
index 3feda30..9e6cb3e 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -1,6 +1,7 @@
-
+ src/
+ tests/
diff --git a/phpstan.neon b/phpstan.neon
index 1d3456a..1bd2f44 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -3,6 +3,5 @@ parameters:
paths:
- src/
ignoreErrors:
- - '#Call to an undefined method Cake\\ORM\\Table::cascadingRestoreTrash\(\)#'
- identifier: missingType.iterableValue
- identifier: missingType.generics
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 6c6d9f9..c5212ca 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -3,7 +3,6 @@
-
diff --git a/psalm.xml b/psalm.xml
index b192091..7cf2785 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -23,5 +23,8 @@
+
+
+
diff --git a/src/Model/Behavior/TrashBehavior.php b/src/Model/Behavior/TrashBehavior.php
index 3afdc2e..848b42e 100644
--- a/src/Model/Behavior/TrashBehavior.php
+++ b/src/Model/Behavior/TrashBehavior.php
@@ -160,7 +160,12 @@ public function trash(EntityInterface $entity, array $options = []): bool
}
}
- $entity->patch([$this->getTrashField(false) => new DateTime()]);
+ /** @phpstan-ignore function.alreadyNarrowedType */
+ if (method_exists($entity, 'patch')) {
+ $entity->patch([$this->getTrashField(false) => new DateTime()]);
+ } else {
+ $entity->set($this->getTrashField(false), new DateTime());
+ }
return (bool)$this->_table->save($entity, $options);
}
@@ -258,7 +263,7 @@ public function trashAll(mixed $conditions): int
{
return $this->_table->updateAll(
[$this->getTrashField(false) => new DateTime()],
- $conditions
+ $conditions,
);
}
@@ -287,7 +292,13 @@ public function restoreTrash(?EntityInterface $entity = null, array $options = [
if ($entity->isDirty()) {
throw new CakeException('Can not restore from a dirty entity.');
}
- $entity->patch($data, ['guard' => false]);
+
+ /** @phpstan-ignore function.alreadyNarrowedType */
+ if (method_exists($entity, 'patch')) {
+ $entity->patch($data, ['guard' => false]);
+ } else {
+ $entity->set($data, ['guard' => false]);
+ }
return $this->_table->save($entity, $options);
}
@@ -304,18 +315,21 @@ public function restoreTrash(?EntityInterface $entity = null, array $options = [
*/
public function cascadingRestoreTrash(
?EntityInterface $entity = null,
- array $options = []
+ array $options = [],
): bool|int|EntityInterface {
$result = $this->restoreTrash($entity, $options);
+ $return = $result;
$associations = $this->_table->associations()->getByType(['HasOne', 'HasMany']);
foreach ($associations as $association) {
if ($this->_isRecursable($association, $this->_table)) {
if ($entity === null) {
- if ($result > 1) {
+ if ($result > 0) {
/** @var \Muffin\Trash\Model\Behavior\TrashBehavior $behavior */
$behavior = $association->getTarget()->getBehavior('Trash');
- $result += $behavior->cascadingRestoreTrash(null, $options);
+ if ($behavior->cascadingRestoreTrash(null, $options) === false) {
+ $return = false;
+ }
}
} else {
/** @var list $foreignKey */
@@ -324,20 +338,21 @@ public function cascadingRestoreTrash(
$bindingKey = (array)$association->getBindingKey();
$conditions = array_combine($foreignKey, $entity->extract($bindingKey));
- foreach ($association->find('withTrashed')->where($conditions) as $related) {
+ /** @var \Cake\Datasource\EntityInterface $related */
+ foreach ($association->find('withTrashed')->where($conditions)->all() as $related) {
/** @var \Muffin\Trash\Model\Behavior\TrashBehavior $behavior */
$behavior = $association->getTarget()->getBehavior('Trash');
if (
- !$behavior->cascadingRestoreTrash($related, ['_primary' => false] + $options)
+ $behavior->cascadingRestoreTrash($related, ['_primary' => false] + $options) === false
) {
- $result = false;
+ $return = false;
}
}
}
}
}
- return $result;
+ return $return;
}
/**
diff --git a/tests/TestCase/Model/Behavior/TrashBehaviorTest.php b/tests/TestCase/Model/Behavior/TrashBehaviorTest.php
index 7d3cc04..7f7b2c1 100644
--- a/tests/TestCase/Model/Behavior/TrashBehaviorTest.php
+++ b/tests/TestCase/Model/Behavior/TrashBehaviorTest.php
@@ -16,6 +16,7 @@
use Cake\TestSuite\TestCase;
use InvalidArgumentException;
use Muffin\Trash\Model\Behavior\TrashBehavior;
+use PHPUnit\Framework\Attributes\DataProvider;
class TrashBehaviorTest extends TestCase
{
@@ -58,7 +59,7 @@ public function setUp(): void
$this->CompositeArticlesUsers = $this->getTableLocator()->get(
'Muffin/Trash.CompositeArticlesUsers',
- ['table' => 'trash_composite_articles_users']
+ ['table' => 'trash_composite_articles_users'],
);
$this->CompositeArticlesUsers->addBehavior('Muffin/Trash.Trash');
@@ -129,7 +130,7 @@ public function testBeforeFindWithTrashFieldInComparison()
$query = $this->Articles->find('all');
$result = $query->where(
- [$this->Articles->aliasField('trashed') . ' >= ' => new DateTime('-1 day')]
+ [$this->Articles->aliasField('trashed') . ' >= ' => new DateTime('-1 day')],
)->toArray();
$this->assertCount(2, $result);
}
@@ -179,7 +180,7 @@ function (Event $event, EntityInterface $entity, ArrayObject $options) {
$entity->setError('id', 'Save aborted');
$event->setResult(false);
$event->stopPropagation();
- }
+ },
);
$result = $this->Articles->delete($article);
@@ -223,7 +224,7 @@ function (Event $event, EntityInterface $entity, ArrayObject $options) use (&$ha
if (isset($options['deleteOptions'])) {
$hasDeleteOptionsBefore = true;
}
- }
+ },
);
$this->Comments->getEventManager()->on(
'Model.afterDelete',
@@ -231,7 +232,7 @@ function (Event $event, EntityInterface $entity, ArrayObject $options) use (&$ha
if (isset($options['deleteOptions'])) {
$hasDeleteOptionsAfter = true;
}
- }
+ },
);
$article = $this->Articles->get(1);
@@ -265,24 +266,24 @@ function (Event $event, EntityInterface $entity, ArrayObject $options) use (&$ma
if (isset($options['deleteOptions'])) {
$mainHasDeleteOptions = true;
}
- }
+ },
);
$this->Comments->getEventManager()->on(
'Model.beforeSave',
function (
Event $event,
EntityInterface $entity,
- ArrayObject $options
+ ArrayObject $options,
) use (
&$dependentHasDeleteOptions,
- &$dependentIsNotPrimary
+ &$dependentIsNotPrimary,
) {
if (isset($options['deleteOptions'])) {
$dependentHasDeleteOptions = true;
}
$dependentIsNotPrimary = $options['_primary'] === false;
- }
+ },
);
$article = $this->Articles->get(1);
@@ -329,7 +330,7 @@ public function testTrash()
$this->getTableLocator()
->get('ArticlesUsers', ['table' => 'trash_articles_users'])
->find()
- ->count()
+ ->count(),
);
}
@@ -595,24 +596,24 @@ function (Event $event, EntityInterface $entity, ArrayObject $options) use (&$ma
if (isset($options['restoreOptions'])) {
$mainHasRestoreOptions = true;
}
- }
+ },
);
$this->Comments->getEventManager()->on(
'Model.beforeSave',
function (
Event $event,
EntityInterface $entity,
- ArrayObject $options
+ ArrayObject $options,
) use (
&$dependentHasRestoreOptions,
- &$dependentIsNotPrimary
+ &$dependentIsNotPrimary,
) {
if (isset($options['restoreOptions'])) {
$dependentHasRestoreOptions = true;
}
$dependentIsNotPrimary = $options['_primary'] === false;
- }
+ },
);
$result = $this->Articles->getBehavior('Trash')->cascadingRestoreTrash($article, [
@@ -690,7 +691,7 @@ public function testCascadingUntrashEntity()
$this->assertInstanceOf(
EntityInterface::class,
- $this->Articles->getBehavior('Trash')->cascadingRestoreTrash($article)
+ $this->Articles->getBehavior('Trash')->cascadingRestoreTrash($article),
);
$article = $this->Articles
@@ -766,7 +767,7 @@ public function testCascadingUntrashAll()
$this->assertNotEmpty($article->composite_articles_users[0]->trashed);
$this->assertInstanceOf(DateTime::class, $article->composite_articles_users[0]->trashed);
- $this->assertEquals(8, $this->Articles->getBehavior('Trash')->cascadingRestoreTrash());
+ $this->assertEquals(3, $this->Articles->getBehavior('Trash')->cascadingRestoreTrash());
$article = $this->Articles
->find()
@@ -903,7 +904,7 @@ public function testGetTrashFieldSchemaIntrospection()
{
$this->assertEquals(
'Articles.trashed',
- $this->Articles->behaviors()->get('Trash')->getTrashField()
+ $this->Articles->behaviors()->get('Trash')->getTrashField(),
);
}
@@ -923,11 +924,11 @@ public function testImpEInvalidArgumentException()
/**
* Test the implementedEvents method.
*
- * @dataProvider provideConfigsForImplementedEventsTest
* @param array $config Initial behavior config.
* @param array $implementedEvents Expected implementedEvents.
* @return void
*/
+ #[DataProvider('provideConfigsForImplementedEventsTest')]
public function testImplementedEvents(array $config, array $implementedEvents)
{
$trash = new TrashBehavior($this->Users, $config);
@@ -942,6 +943,9 @@ public function testImplementedEvents(array $config, array $implementedEvents)
*/
public static function provideConfigsForImplementedEventsTest()
{
+ $callable = function () {
+ };
+
return [
'No event config inherits default events' => [
[],
@@ -1001,8 +1005,7 @@ public static function provideConfigsForImplementedEventsTest()
[
'events' => [
'Model.beforeDelete' => [
- 'callable' => function () {
- },
+ 'callable' => $callable,
],
'Model.beforeFind' => [
'callable' => ['', 'beforeDelete'],
@@ -1012,8 +1015,7 @@ public static function provideConfigsForImplementedEventsTest()
],
[
'Model.beforeDelete' => [
- 'callable' => function () {
- },
+ 'callable' => $callable,
],
'Model.beforeFind' => [
'callable' => ['', 'beforeDelete'],