diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c92404c07..52cee4dd7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ a release. ## [Unreleased] ### Added - Support for Symfony 8 +- SoftDeleteable: Add support for `date_point` type (#3013) +- Timestampable: Add support for `date_point` type (#3013) ## [3.21.0] - 2025-09-22 ### Added diff --git a/composer.json b/composer.json index b03176a53f..aea4470e23 100644 --- a/composer.json +++ b/composer.json @@ -66,6 +66,7 @@ "phpstan/phpstan-phpunit": "^2.0.3", "phpunit/phpunit": "^9.6", "rector/rector": "^2.2.6", + "symfony/clock": "^6.4 || ^7.3 || ^8.0", "symfony/console": "^5.4 || ^6.4 || ^7.3 || ^8.0", "symfony/doctrine-bridge": "^5.4 || ^6.4 || ^7.3 || ^8.0", "symfony/phpunit-bridge": "^6.4 || ^7.3 || ^8.0", diff --git a/src/SoftDeleteable/Mapping/Event/Adapter/ORM.php b/src/SoftDeleteable/Mapping/Event/Adapter/ORM.php index e20b7f1b70..d1d68bbd58 100644 --- a/src/SoftDeleteable/Mapping/Event/Adapter/ORM.php +++ b/src/SoftDeleteable/Mapping/Event/Adapter/ORM.php @@ -61,7 +61,7 @@ private function getRawDateValue($mapping) return (int) $datetime->format('U'); } - if (in_array($type, ['date_immutable', 'time_immutable', 'datetime_immutable', 'datetimetz_immutable'], true)) { + if (in_array($type, ['date_immutable', 'time_immutable', 'datetime_immutable', 'datetimetz_immutable', 'date_point'], true)) { return $datetime; } diff --git a/src/SoftDeleteable/Mapping/Validator.php b/src/SoftDeleteable/Mapping/Validator.php index fe896cb32c..474683d5b4 100644 --- a/src/SoftDeleteable/Mapping/Validator.php +++ b/src/SoftDeleteable/Mapping/Validator.php @@ -37,6 +37,7 @@ class Validator 'datetimetz', 'datetimetz_immutable', 'timestamp', + 'date_point', ]; /** diff --git a/src/Timestampable/Mapping/Driver/Attribute.php b/src/Timestampable/Mapping/Driver/Attribute.php index cf4c1c099e..934b14f0f0 100644 --- a/src/Timestampable/Mapping/Driver/Attribute.php +++ b/src/Timestampable/Mapping/Driver/Attribute.php @@ -47,6 +47,7 @@ class Attribute extends AbstractAnnotationDriver 'timestamp', 'vardatetime', 'integer', + 'date_point', ]; public function readExtendedMetadata($meta, array &$config) diff --git a/src/Timestampable/Mapping/Driver/Xml.php b/src/Timestampable/Mapping/Driver/Xml.php index 0f15e876bd..97dcd9cfe2 100644 --- a/src/Timestampable/Mapping/Driver/Xml.php +++ b/src/Timestampable/Mapping/Driver/Xml.php @@ -43,6 +43,7 @@ class Xml extends BaseXml 'timestamp', 'vardatetime', 'integer', + 'date_point', ]; public function readExtendedMetadata($meta, array &$config) diff --git a/src/Timestampable/Mapping/Driver/Yaml.php b/src/Timestampable/Mapping/Driver/Yaml.php index 5b9747a0fe..ac2afac720 100644 --- a/src/Timestampable/Mapping/Driver/Yaml.php +++ b/src/Timestampable/Mapping/Driver/Yaml.php @@ -45,6 +45,7 @@ class Yaml extends File implements Driver 'timestamp', 'vardatetime', 'integer', + 'date_point', ]; /** diff --git a/src/Timestampable/Mapping/Event/Adapter/ORM.php b/src/Timestampable/Mapping/Event/Adapter/ORM.php index 276bc05c02..1a1ee90a4e 100644 --- a/src/Timestampable/Mapping/Event/Adapter/ORM.php +++ b/src/Timestampable/Mapping/Event/Adapter/ORM.php @@ -61,7 +61,7 @@ private function getRawDateValue($mapping) return (int) $datetime->format('U'); } - if (in_array($type, ['date_immutable', 'time_immutable', 'datetime_immutable', 'datetimetz_immutable'], true)) { + if (in_array($type, ['date_immutable', 'time_immutable', 'datetime_immutable', 'datetimetz_immutable', 'date_point'], true)) { return $datetime; } diff --git a/tests/Gedmo/SoftDeleteable/DatePointTest.php b/tests/Gedmo/SoftDeleteable/DatePointTest.php new file mode 100644 index 0000000000..63ca8ea88c --- /dev/null +++ b/tests/Gedmo/SoftDeleteable/DatePointTest.php @@ -0,0 +1,85 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\SoftDeleteable; + +use Carbon\Doctrine\DateTimeType; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Types\Type as DoctrineType; +use Doctrine\DBAL\Types\Types; +use Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter; +use Gedmo\SoftDeleteable\SoftDeleteableListener; +use Gedmo\Tests\SoftDeleteable\Fixture\Entity\BookDatePoint; +use Gedmo\Tests\Tool\BaseTestCaseORM; +use Symfony\Component\Clock\DatePoint; + +final class DatePointTest extends BaseTestCaseORM +{ + private const SOFT_DELETEABLE_FILTER_NAME = 'soft-deleteable'; + + private SoftDeleteableListener $softDeleteableListener; + + protected function setUp(): void + { + parent::setUp(); + + $evm = new EventManager(); + $this->softDeleteableListener = new SoftDeleteableListener(); + $evm->addEventSubscriber($this->softDeleteableListener); + $config = $this->getDefaultConfiguration(); + $config->addFilter(self::SOFT_DELETEABLE_FILTER_NAME, SoftDeleteableFilter::class); + $this->em = $this->getDefaultMockSqliteEntityManager($evm, $config); + $this->em->getFilters()->enable(self::SOFT_DELETEABLE_FILTER_NAME); + + DoctrineType::overrideType(Types::DATETIME_MUTABLE, DateTimeType::class); + } + + protected function tearDown(): void + { + parent::tearDown(); + + DoctrineType::overrideType(Types::DATETIME_MUTABLE, \Doctrine\DBAL\Types\DateTimeType::class); + } + + public function testSoftDeleteable(): void + { + $repo = $this->em->getRepository(BookDatePoint::class); + + $book0 = new BookDatePoint(); + $field = 'title'; + $value = 'Title 1'; + $book0->setTitle($value); + + $this->em->persist($book0); + $this->em->flush(); + + static::assertNull($book0->getDeletedAt()); + + $book = $repo->findOneBy([$field => $value]); + $this->em->remove($book); + $this->em->flush(); + + $book = $repo->findOneBy([$field => $value]); + static::assertNull($book); + + // Now we deactivate the filter so we test if the entity appears in the result + $this->em->getFilters()->disable(self::SOFT_DELETEABLE_FILTER_NAME); + + $book = $repo->findOneBy([$field => $value]); + static::assertIsObject($book); + static::assertInstanceOf(DatePoint::class, $book->getDeletedAt()); + } + + protected function getUsedEntityFixtures(): array + { + return [ + BookDatePoint::class, + ]; + } +} diff --git a/tests/Gedmo/SoftDeleteable/Fixture/Entity/BookDatePoint.php b/tests/Gedmo/SoftDeleteable/Fixture/Entity/BookDatePoint.php new file mode 100644 index 0000000000..43dc30d15e --- /dev/null +++ b/tests/Gedmo/SoftDeleteable/Fixture/Entity/BookDatePoint.php @@ -0,0 +1,76 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\SoftDeleteable\Fixture\Entity; + +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Gedmo\Mapping\Annotation as Gedmo; +use Symfony\Component\Clock\DatePoint; + +/** + * @ORM\Entity + * + * @Gedmo\SoftDeleteable(fieldName="deletedAt") + */ +#[ORM\Entity] +#[Gedmo\SoftDeleteable(fieldName: 'deletedAt')] +class BookDatePoint +{ + /** + * @var int|null + * + * @ORM\Id + * @ORM\GeneratedValue(strategy="IDENTITY") + * @ORM\Column(type="integer") + */ + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'IDENTITY')] + #[ORM\Column(type: Types::INTEGER)] + private $id; + + /** + * @ORM\Column(name="title", type="string") + */ + #[ORM\Column(name: 'title', type: Types::STRING)] + private ?string $title = null; + + /** + * @ORM\Column(name="deletedAt", type="date_point", nullable=true) + */ + #[ORM\Column(name: 'deletedAt', type: 'date_point', nullable: true)] + private ?DatePoint $deletedAt = null; + + public function getId(): ?int + { + return $this->id; + } + + public function setTitle(?string $title): void + { + $this->title = $title; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setDeletedAt(?DatePoint $deletedAt): void + { + $this->deletedAt = $deletedAt; + } + + public function getDeletedAt(): ?DatePoint + { + return $this->deletedAt; + } +} diff --git a/tests/Gedmo/Timestampable/DatePointTest.php b/tests/Gedmo/Timestampable/DatePointTest.php new file mode 100644 index 0000000000..6be8d03148 --- /dev/null +++ b/tests/Gedmo/Timestampable/DatePointTest.php @@ -0,0 +1,135 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\Timestampable; + +use Doctrine\Common\EventManager; +use Gedmo\Tests\Timestampable\Fixture\ArticleDatePoint; +use Gedmo\Tests\Timestampable\Fixture\Author; +use Gedmo\Tests\Timestampable\Fixture\CommentDatePoint; +use Gedmo\Tests\Timestampable\Fixture\Type; +use Gedmo\Tests\Tool\BaseTestCaseORM; +use Gedmo\Timestampable\TimestampableListener; +use Symfony\Component\Clock\DatePoint; +use Symfony\Component\Clock\NativeClock; + +final class DatePointTest extends BaseTestCaseORM +{ + protected function setUp(): void + { + parent::setUp(); + + $evm = new EventManager(); + $evm->addEventSubscriber(new TimestampableListener()); + + $this->getDefaultMockSqliteEntityManager($evm); + } + + protected function tearDown(): void + { + parent::tearDown(); + } + + public function testShouldHandleStandardBehavior(): void + { + $sport = new ArticleDatePoint(); + $sport->setTitle('Sport'); + $sport->setBody('Sport article body.'); + + $sportComment = new CommentDatePoint(); + $sportComment->setMessage('hello'); + $sportComment->setArticle($sport); + $sportComment->setStatus(0); + + $author = new Author(); + $author->setName('Original author'); + $author->setEmail('original@author.dev'); + + $sport->setAuthor($author); + + $this->em->persist($sport); + $this->em->persist($sportComment); + $this->em->flush(); + + /** @var ArticleDatePoint $sport */ + $sport = $this->em->getRepository(ArticleDatePoint::class)->findOneBy(['title' => 'Sport']); + static::assertInstanceOf(DatePoint::class, $su = $sport->getUpdated()); + + static::assertNull($sport->getContentChanged()); + static::assertNull($sport->getPublished()); + static::assertNull($sport->getAuthorChanged()); + + $author = $sport->getAuthor(); + $author->setName('New author'); + $sport->setAuthor($author); + + /** @var CommentDatePoint $sportComment */ + $sportComment = $this->em->getRepository(CommentDatePoint::class)->findOneBy(['message' => 'hello']); + static::assertInstanceOf(DatePoint::class, $sc = $sportComment->getModified()); + + static::assertNotNull($sportComment->getModified()); + static::assertNull($sportComment->getClosed()); + + $sportComment->setStatus(1); + $published = new Type(); + $published->setTitle('Published'); + + $sport->setType($published); + $this->em->persist($sport); + $this->em->persist($published); + $this->em->persist($sportComment); + $this->em->flush(); + + $sportComment = $this->em->getRepository(CommentDatePoint::class)->findOneBy(['message' => 'hello']); + static::assertInstanceOf(DatePoint::class, $scc = $sportComment->getClosed()); + static::assertInstanceOf(DatePoint::class, $sp = $sport->getPublished()); + static::assertInstanceOf(DatePoint::class, $sa = $sport->getAuthorChanged()); + + $sport->setTitle('Updated'); + $this->em->persist($sport); + $this->em->persist($published); + $this->em->persist($sportComment); + $this->em->flush(); + + // prevent "Failed asserting that two variables reference the same object." error, hence compare unix epoch + static::assertEquals($sport->getCreated()->format('U'), $sc->format('U'), 'Date created should remain same after update'); + static::assertNotSame($su2 = $sport->getUpdated(), $su, 'Date updated should change after update'); + static::assertInstanceOf(DatePoint::class, $sport->getUpdated()); + static::assertSame($sport->getPublished(), $sp, 'Date published should remain the same after update'); + static::assertNotSame($scc2 = $sport->getContentChanged(), $scc, 'Content must have changed after update'); + static::assertInstanceOf(DatePoint::class, $sport->getContentChanged()); + static::assertSame($sport->getAuthorChanged(), $sa, 'Author should remain same after update'); + + $author = $sport->getAuthor(); + $author->setName('Third author'); + $sport->setAuthor($author); + + $sport->setBody('Body updated'); + $this->em->persist($sport); + $this->em->persist($published); + $this->em->persist($sportComment); + $this->em->flush(); + + // prevent "Failed asserting that two variables reference the same object." error, hence compare unix epoch + static::assertEquals($sport->getCreated()->format('U'), $sc->format('U'), 'Date created should remain same after update'); + static::assertNotSame($sport->getUpdated(), $su2, 'Date updated should change after update'); + static::assertSame($sport->getPublished(), $sp, 'Date published should remain the same after update'); + static::assertNotSame($sport->getContentChanged(), $scc2, 'Content must have changed after update'); + static::assertNotSame($sport->getAuthorChanged(), $sa, 'Author must have changed after update'); + } + + protected function getUsedEntityFixtures(): array + { + return [ + ArticleDatePoint::class, + CommentDatePoint::class, + Type::class, + ]; + } +} diff --git a/tests/Gedmo/Timestampable/Fixture/ArticleDatePoint.php b/tests/Gedmo/Timestampable/Fixture/ArticleDatePoint.php new file mode 100644 index 0000000000..84e391d66b --- /dev/null +++ b/tests/Gedmo/Timestampable/Fixture/ArticleDatePoint.php @@ -0,0 +1,255 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\Timestampable\Fixture; + +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Gedmo\Mapping\Annotation as Gedmo; +use Gedmo\Timestampable\Timestampable; +use Symfony\Component\Clock\DatePoint; + +/** + * @ORM\Entity + */ +#[ORM\Entity] +class ArticleDatePoint implements Timestampable +{ + /** + * @var int|null + * + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: Types::INTEGER)] + private $id; + + /** + * @ORM\Column(name="title", type="string", length=128) + */ + #[ORM\Column(name: 'title', type: Types::STRING, length: 128)] + private ?string $title = null; + + /** + * @ORM\Column(name="body", type="string") + */ + #[ORM\Column(name: 'body', type: Types::STRING)] + private ?string $body = null; + + /** + * @var Collection + * + * @ORM\OneToMany(targetEntity="Gedmo\Tests\Timestampable\Fixture\CommentDatePoint", mappedBy="article") + */ + #[ORM\OneToMany(targetEntity: CommentDatePoint::class, mappedBy: 'article')] + private Collection $comments; + + /** + * @ORM\Embedded(class="Gedmo\Tests\Timestampable\Fixture\Author") + */ + #[ORM\Embedded(class: Author::class)] + private ?Author $author = null; + + /** + * @Gedmo\Timestampable(on="create") + * + * @ORM\Column(name="created", type="date_point") + */ + #[Gedmo\Timestampable(on: 'create')] + #[ORM\Column(name: 'created', type: 'date_point')] + private ?DatePoint $created = null; + + /** + * @ORM\Column(name="updated", type="date_point") + * + * @Gedmo\Timestampable + */ + #[ORM\Column(name: 'updated', type: 'date_point')] + #[Gedmo\Timestampable] + private ?DatePoint $updated = null; + + /** + * @ORM\Column(name="published", type="date_point", nullable=true) + * + * @Gedmo\Timestampable(on="change", field="type.title", value="Published") + */ + #[ORM\Column(name: 'published', type: 'date_point', nullable: true)] + #[Gedmo\Timestampable(on: 'change', field: 'type.title', value: 'Published')] + private ?DatePoint $published = null; + + /** + * @ORM\Column(name="content_changed", type="date_point", nullable=true) + * + * @Gedmo\Timestampable(on="change", field={"title", "body"}) + */ + #[ORM\Column(name: 'content_changed', type: 'date_point', nullable: true)] + #[Gedmo\Timestampable(on: 'change', field: ['title', 'body'])] + private ?DatePoint $contentChanged = null; + /** + * @var DatePoint|null + * + * @ORM\Column(name="author_changed", type="date_point", nullable=true) + * + * @Gedmo\Timestampable(on="change", field={"author.name", "author.email"}) + */ + #[ORM\Column(name: 'author_changed', type: 'date_point', nullable: true)] + #[Gedmo\Timestampable(on: 'change', field: ['author.name', 'author.email'])] + private $authorChanged; + + /** + * @ORM\ManyToOne(targetEntity="Type", inversedBy="articles") + */ + #[ORM\ManyToOne(targetEntity: Type::class, inversedBy: 'articles')] + private ?Type $type = null; + + /** + * @ORM\Column(name="level", type="integer") + */ + #[ORM\Column(name: 'level', type: Types::INTEGER)] + private int $level = 0; + + /** + * We use the value "10" as string here in order to check the behavior of `AbstractTrackingListener` + * + * @var DatePoint|null + * + * @ORM\Column(name="reached_relevant_level", type="date_point", nullable=true) + * + * @Gedmo\Timestampable(on="change", field="level", value="10") + */ + #[ORM\Column(name: 'reached_relevant_level', type: 'date_point', nullable: true)] + #[Gedmo\Timestampable(on: 'change', field: 'level', value: '10')] + private $reachedRelevantLevel; + + public function __construct() + { + $this->comments = new ArrayCollection(); + } + + public function setType(?Type $type): void + { + $this->type = $type; + } + + public function getId(): ?int + { + return $this->id; + } + + public function setTitle(?string $title): void + { + $this->title = $title; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setBody(?string $body): void + { + $this->body = $body; + } + + public function getBody(): ?string + { + return $this->body; + } + + public function addComment(Comment $comment): void + { + $comment->setArticle($this); + $this->comments[] = $comment; + } + + /** + * @return Collection + */ + public function getComments(): Collection + { + return $this->comments; + } + + public function getAuthor(): ?Author + { + return $this->author; + } + + public function setAuthor(Author $author): void + { + $this->author = $author; + } + + public function getCreated(): ?DatePoint + { + return $this->created; + } + + public function setCreated(DatePoint $created): void + { + $this->created = $created; + } + + public function getPublished(): ?DatePoint + { + return $this->published; + } + + public function setPublished(DatePoint $published): void + { + $this->published = $published; + } + + public function getUpdated(): ?DatePoint + { + return $this->updated; + } + + public function setUpdated(DatePoint $updated): void + { + $this->updated = $updated; + } + + public function setContentChanged(DatePoint $contentChanged): void + { + $this->contentChanged = $contentChanged; + } + + public function getContentChanged(): ?DatePoint + { + return $this->contentChanged; + } + + public function getAuthorChanged(): ?DatePoint + { + return $this->authorChanged; + } + + public function setLevel(int $level): void + { + $this->level = $level; + } + + public function getLevel(): int + { + return $this->level; + } + + public function getReachedRelevantLevel(): ?\DateTimeInterface + { + return $this->reachedRelevantLevel; + } +} diff --git a/tests/Gedmo/Timestampable/Fixture/CommentDatePoint.php b/tests/Gedmo/Timestampable/Fixture/CommentDatePoint.php new file mode 100644 index 0000000000..bed25c96a4 --- /dev/null +++ b/tests/Gedmo/Timestampable/Fixture/CommentDatePoint.php @@ -0,0 +1,122 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\Timestampable\Fixture; + +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Gedmo\Mapping\Annotation as Gedmo; +use Gedmo\Timestampable\Timestampable; +use Symfony\Component\Clock\DatePoint; + +/** + * @ORM\Entity + */ +#[ORM\Entity] +class CommentDatePoint implements Timestampable +{ + /** + * @var int|null + * + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: Types::INTEGER)] + private $id; + + /** + * @ORM\Column(name="message", type="text") + */ + #[ORM\Column(name: 'message', type: Types::TEXT)] + private ?string $message = null; + + /** + * @var ArticleDatepoint|null + * + * @ORM\ManyToOne(targetEntity="Gedmo\Tests\Timestampable\Fixture\ArticleDatepoint", inversedBy="comments") + */ + #[ORM\ManyToOne(targetEntity: ArticleDatepoint::class, inversedBy: 'comments')] + private $article; + + /** + * @ORM\Column(type="integer") + */ + #[ORM\Column(type: Types::INTEGER)] + private ?int $status = null; + + /** + * @var DatePoint|null + * + * @ORM\Column(name="closed", type="date_point", nullable=true) + * + * @Gedmo\Timestampable(on="change", field="status", value=1) + */ + #[ORM\Column(name: 'closed', type: 'date_point', nullable: true)] + #[Gedmo\Timestampable(on: 'change', field: 'status', value: 1)] + private $closed; + + /** + * @var DatePoint|null + * + * @ORM\Column(name="modified", type="date_point") + * + * @Gedmo\Timestampable(on="update") + */ + #[ORM\Column(name: 'modified', type: 'date_point')] + #[Gedmo\Timestampable(on: 'update')] + private $modified; + + /** + * @param ArticleDatepoint|null $article + */ + public function setArticle(?ArticleDatepoint $article): void + { + $this->article = $article; + } + + public function getId(): ?int + { + return $this->id; + } + + public function setStatus(?int $status): void + { + $this->status = $status; + } + + public function getStatus(): ?int + { + return $this->status; + } + + public function setMessage(?string $message): void + { + $this->message = $message; + } + + public function getMessage(): ?string + { + return $this->message; + } + + public function getModified(): ?DatePoint + { + return $this->modified; + } + + public function getClosed(): ?DatePoint + { + return $this->closed; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index db25b31b03..7e33c2397b 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -14,6 +14,7 @@ use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; +use Symfony\Bridge\Doctrine\Types\DatePointType; use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -46,6 +47,7 @@ } Type::addType('uuid', UuidType::class); +Type::addType('date_point', DatePointType::class); // Ignore unfixable deprecations Deprecation::ignoreDeprecations(