diff --git a/composer.json b/composer.json index f00efdc..591b40f 100755 --- a/composer.json +++ b/composer.json @@ -48,7 +48,8 @@ "moneyphp/money": "^3.2|^4.0", "ext-json": "*", "ramsey/uuid": "^4.3", - "ramsey/uuid-doctrine": "^1.4" + "ramsey/uuid-doctrine": "^1.4", + "thecodingmachine/safe": "^3.4" }, "config": { "allow-plugins": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e76475b..b494d8d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,6 +1,13 @@ parameters: ignoreErrors: - - message: "#^Throwing checked exception InvalidArgumentException in yielding method is denied as it gets thrown upon Generator iteration$#" + message: '#^Call to an undefined method object\:\:getId\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Serializer/LogSerializer.php + + - + message: '#^Throwing checked exception InvalidArgumentException in yielding method is denied as it gets thrown upon Generator iteration$#' + identifier: shipmonk.checkedExceptionInYieldingMethod count: 2 path: tests/Serializer/LogSerializerTest.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 381c1b2..c65ef82 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,4 @@ parameters: - level: 5 paths: - src/ - tests/ diff --git a/src/Entity/Log.php b/src/Entity/Log.php index df4d3a8..cb46968 100755 --- a/src/Entity/Log.php +++ b/src/Entity/Log.php @@ -23,7 +23,7 @@ public function __construct( ) { $this->entityClass = $entity::class; $this->entityColumn = $entityColumn; - $this->entityOldValue = mb_substr($entityOldValue, 0, Log::MAX_STRING_LENGTH); + $this->entityOldValue = $entityOldValue !== null ? mb_substr($entityOldValue, 0, Log::MAX_STRING_LENGTH) : null; $this->requestTrace = $requestTrace; $this->createdAt = new DateTimeImmutable(); } diff --git a/src/Exception/UnsupportObjectException.php b/src/Exception/UnsupportedObjectException.php similarity index 59% rename from src/Exception/UnsupportObjectException.php rename to src/Exception/UnsupportedObjectException.php index 2c472f5..b60413f 100644 --- a/src/Exception/UnsupportObjectException.php +++ b/src/Exception/UnsupportedObjectException.php @@ -4,9 +4,9 @@ namespace AssoConnect\LogBundle\Exception; -class UnsupportObjectException extends \DomainException +class UnsupportedObjectException extends \DomainException { - public function __construct($object, $code = 0, ?\Throwable $previous = null) + public function __construct(object $object, int $code = 0, ?\Throwable $previous = null) { $message = sprintf('Unhandled object of class %s', $object::class); parent::__construct($message, $code, $previous); diff --git a/src/Factory/LogDataFactory.php b/src/Factory/LogDataFactory.php index aa006b7..1025203 100644 --- a/src/Factory/LogDataFactory.php +++ b/src/Factory/LogDataFactory.php @@ -11,6 +11,10 @@ /** @phpstan-type LogData array{entity: object, entityColumn: string, entityOldValue: ?string} */ class LogDataFactory { + /** + * @param list $includedEntities + * @param list $excludedEntities + */ public function __construct( private readonly LogSerializer $formatter, private readonly array $includedEntities, @@ -54,7 +58,7 @@ public function createFromEvent(OnFlushEventArgs $eventArgs): iterable } } - private function isLoggable($entity): bool + private function isLoggable(object $entity): bool { if ($this->isSubClassFromList($entity, $this->excludedEntities)) { return false; @@ -63,7 +67,10 @@ private function isLoggable($entity): bool return [] === $this->includedEntities || $this->isSubClassFromList($entity, $this->includedEntities); } - private function isSubClassFromList($entity, array $classes): bool + /** + * @param list $classes + */ + private function isSubClassFromList(object $entity, array $classes): bool { foreach ($classes as $class) { if (is_a($entity, $class)) { @@ -75,7 +82,7 @@ private function isSubClassFromList($entity, array $classes): bool } /** @return LogData[] */ - private function getLogsForEntityFields($entity, EntityManagerInterface $entityManager): iterable + private function getLogsForEntityFields(object $entity, EntityManagerInterface $entityManager): iterable { $unitOfWork = $entityManager->getUnitOfWork(); diff --git a/src/Serializer/LogSerializer.php b/src/Serializer/LogSerializer.php index 0f5db0d..7fce797 100755 --- a/src/Serializer/LogSerializer.php +++ b/src/Serializer/LogSerializer.php @@ -5,13 +5,15 @@ namespace AssoConnect\LogBundle\Serializer; use AssoConnect\LogBundle\Entity\Log; -use AssoConnect\LogBundle\Exception\UnsupportObjectException; +use AssoConnect\LogBundle\Exception\UnsupportedObjectException; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Money\Money; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessor; +use function Safe\json_encode; + class LogSerializer { // Maximum number of associations to log in order to avoid column oversize @@ -50,7 +52,7 @@ public function formatEntity(EntityManagerInterface $entityManager, object $enti return json_encode($data, JSON_PRETTY_PRINT); } - public function formatValueAsString($value): string + public function formatValueAsString(mixed $value): string { return json_encode($this->formatValue($value)); } @@ -71,7 +73,7 @@ private function formatField(object $entity, string $field): mixed /** * Returns a formatted value depending on the given value's type. */ - private function formatValue($value): mixed + private function formatValue(mixed $value): mixed { return match (gettype($value)) { 'string' => mb_substr($value, 0, Log::MAX_STRING_LENGTH), @@ -122,6 +124,6 @@ private function formatObject(object $value): mixed return $value->__toString(); } - throw new UnsupportObjectException($value); + throw new UnsupportedObjectException($value); } } diff --git a/tests/Factory/LogDataFactoryTest.php b/tests/Factory/LogDataFactoryTest.php index 95d39e6..8e4c0ba 100644 --- a/tests/Factory/LogDataFactoryTest.php +++ b/tests/Factory/LogDataFactoryTest.php @@ -19,6 +19,7 @@ class LogDataFactoryTest extends KernelTestCase public function testExcludedEntityIsIgnored(): void { $em = self::getContainer()->get(EntityManagerInterface::class); + self::assertInstanceOf(EntityManagerInterface::class, $em); $em->persist(new Author()); $em->getUnitOfWork()->computeChangeSets(); @@ -31,6 +32,7 @@ public function testExcludedEntityIsIgnored(): void public function testNewEntityIsLogged(): void { $em = self::getContainer()->get(EntityManagerInterface::class); + self::assertInstanceOf(EntityManagerInterface::class, $em); $em->persist($createdAuthor = new Author()); $em->getUnitOfWork()->computeChangeSets(); @@ -94,6 +96,7 @@ public function testUpdatedEntityIsLogged(): void private function mockEntityManager(): array { $emReal = self::getContainer()->get(EntityManagerInterface::class); + self::assertInstanceOf(EntityManagerInterface::class, $emReal); $em = $this->createMock(EntityManagerInterface::class); $unitOfWork = $this->createMock(UnitOfWork::class); diff --git a/tests/Functional/Entity/Address.php b/tests/Functional/Entity/Address.php index ab35594..b6d8c94 100755 --- a/tests/Functional/Entity/Address.php +++ b/tests/Functional/Entity/Address.php @@ -21,7 +21,7 @@ public function getStreetName(): ?string return $this->streetName; } - public function setStreetName(?string $streetName): self + public function setStreetName(string $streetName): self { $this->streetName = $streetName; diff --git a/tests/Functional/Entity/FunctionalLog.php b/tests/Functional/Entity/FunctionalLog.php index c41f356..4ed95e7 100755 --- a/tests/Functional/Entity/FunctionalLog.php +++ b/tests/Functional/Entity/FunctionalLog.php @@ -16,7 +16,7 @@ class FunctionalLog extends Log protected UuidInterface $id; public function __construct( - object $entity, + AbstractEntity $entity, string $entityColumn, ?string $entityOldValue, string $requestTrace, diff --git a/tests/Functional/Entity/Post.php b/tests/Functional/Entity/Post.php index d21fcd9..da9c897 100755 --- a/tests/Functional/Entity/Post.php +++ b/tests/Functional/Entity/Post.php @@ -21,7 +21,7 @@ public function __construct( } #[Assert\NotBlank] - protected $title; + protected ?string $title = null; #[ORM\ManyToOne(targetEntity: Author::class, inversedBy: 'posts')] protected Author $author; @@ -31,9 +31,11 @@ public function getAuthor(): Author return $this->author; } + /** @var ArrayCollection */ #[ORM\ManyToMany(targetEntity: Tag::class, inversedBy: 'posts')] protected ArrayCollection $tags; + /** @return Collection */ public function getTags(): Collection { return $this->tags; diff --git a/tests/Functional/Entity/Tag.php b/tests/Functional/Entity/Tag.php index 04cd666..7e589ec 100755 --- a/tests/Functional/Entity/Tag.php +++ b/tests/Functional/Entity/Tag.php @@ -16,6 +16,7 @@ public function __construct() $this->posts = new ArrayCollection(); } + /** @var ArrayCollection */ #[ORM\ManyToMany(Post::class, 'tags')] protected ArrayCollection $posts; diff --git a/tests/Functional/Service/LogFactory.php b/tests/Functional/Service/LogFactory.php index bd2e7a8..5824214 100755 --- a/tests/Functional/Service/LogFactory.php +++ b/tests/Functional/Service/LogFactory.php @@ -6,6 +6,7 @@ use AssoConnect\LogBundle\Entity\Log; use AssoConnect\LogBundle\Factory\LogFactoryInterface; +use AssoConnect\LogBundle\Tests\Functional\Entity\AbstractEntity; use AssoConnect\LogBundle\Tests\Functional\Entity\FunctionalLog; class LogFactory implements LogFactoryInterface @@ -16,6 +17,7 @@ public function createLogFromEntity( ?string $entityOldValue, string $requestTrace, ): Log { + assert($entity instanceof AbstractEntity); return new FunctionalLog( $entity, $entityColumn, diff --git a/tests/Serializer/LogSerializerTest.php b/tests/Serializer/LogSerializerTest.php index 2caa74b..85d0e59 100755 --- a/tests/Serializer/LogSerializerTest.php +++ b/tests/Serializer/LogSerializerTest.php @@ -5,7 +5,7 @@ namespace AssoConnect\LogBundle\Tests\Serializer; use AssoConnect\LogBundle\Entity\Log; -use AssoConnect\LogBundle\Exception\UnsupportObjectException; +use AssoConnect\LogBundle\Exception\UnsupportedObjectException; use AssoConnect\LogBundle\Serializer\LogSerializer; use AssoConnect\LogBundle\Tests\Functional\Entity\AbstractEntity; use AssoConnect\LogBundle\Tests\Functional\Entity\Author; @@ -34,8 +34,10 @@ public function testFormatEntity(): void $post->addTag($tag); $entityManager = self::getContainer()->get(EntityManagerInterface::class); + self::assertInstanceOf(EntityManagerInterface::class, $entityManager); $formatter = self::getContainer()->get(LogSerializer::class); + self::assertInstanceOf(LogSerializer::class, $formatter); self::assertSame( json_encode(array_merge( $this->helperFormatEntity($author), @@ -64,6 +66,7 @@ public function testFormatEntity(): void ); } + /** @return array */ public function helperFormatEntity(AbstractEntity $entity): array { return [ @@ -76,7 +79,7 @@ public function helperFormatEntity(AbstractEntity $entity): array /** * @dataProvider providerFormatValueAsString */ - public function testFormatValueAsStringWorks($value, $formatted): void + public function testFormatValueAsStringWorks(mixed $value, string $formatted): void { $formatter = new LogSerializer(); self::assertSame($formatted, $formatter->formatValueAsString($value)); @@ -129,7 +132,7 @@ public function testUnsupportedObjectThrowsAnException(): void { $formatter = new LogSerializer(); - $this->expectException(UnsupportObjectException::class); + $this->expectException(UnsupportedObjectException::class); $formatter->formatValueAsString(new ObjectWithoutId()); } diff --git a/tests/Subscriber/LoggerSubscriberTest.php b/tests/Subscriber/LoggerSubscriberTest.php index a64405b..7eaf833 100755 --- a/tests/Subscriber/LoggerSubscriberTest.php +++ b/tests/Subscriber/LoggerSubscriberTest.php @@ -18,6 +18,8 @@ use Doctrine\ORM\UnitOfWork; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use function Safe\realpath; + class LoggerSubscriberTest extends KernelTestCase { public function testEventSubscriptionAsAnAttribute(): void