Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
trigger error when property is removed
Browse files Browse the repository at this point in the history
smaine milianni committed Nov 3, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 8b9eb2d commit f3aa7b3
Showing 12 changed files with 167 additions and 97 deletions.
37 changes: 17 additions & 20 deletions src/Assert/ZddMessageAssert.php
Original file line number Diff line number Diff line change
@@ -2,19 +2,21 @@

namespace Yousign\ZddMessageBundle\Assert;

use Yousign\ZddMessageBundle\Factory\Property;
use Yousign\ZddMessageBundle\Factory\PropertyList;

/**
* @internal
*/
final class ZddMessageAssert
{
/**
* @param class-string<object> $messageFqcn
* @param array<string|string, string> $notNullableProperties
* @param class-string<object> $messageFqcn
*/
public static function assert(
string $messageFqcn,
string $serializedMessage,
array $notNullableProperties
PropertyList $propertyList
): void {
// ✅ Assert message is unserializable
$object = unserialize($serializedMessage);
@@ -24,40 +26,35 @@ public static function assert(
}

$reflection = new \ReflectionClass($messageFqcn);
$properties = $reflection->getProperties();
$reflectionProperties = $reflection->getProperties();

// ✅ Assert property type hint has not changed and new property have a default value
foreach ($properties as $property) {
foreach ($reflectionProperties as $reflectionProperty) {
// ✅ Assert error "Typed property Message::$theProperty must not be accessed before initialization".
$property->getValue($object); // @phpstan-ignore-line ::: Call to method ReflectionProperty::getValue() on a separate line has no effect.
}
$reflectionProperty->getValue($object); // @phpstan-ignore-line ::: Call to method ReflectionProperty::getValue() on a separate line has no effect.

// ✅ Assert not nullable property has been removed
foreach ($properties as $property) {
if (\array_key_exists($property->getName(), $notNullableProperties)) {
self::assertProperty($property, $notNullableProperties);
unset($notNullableProperties[$property->getName()]);
// ✅ Assert property
if ($propertyList->has($reflectionProperty->getName())) {
self::assertProperty($reflectionProperty, $propertyList->get($reflectionProperty->getName()), $messageFqcn);
$propertyList->remove($reflectionProperty->getName());
}
}

if (0 !== \count($notNullableProperties)) {
throw new \LogicException(\sprintf('The properties "%s" in class "%s" seems to have been removed, make it nullable first, deploy it and then remove it 🔥', implode(', ', \array_flip($notNullableProperties)), $messageFqcn));
if (0 !== $propertyList->count()) {
throw new \LogicException(\sprintf('⚠️ The properties "%s" in class "%s" seems to have been removed', implode(', ', $propertyList->getPropertiesName()), $messageFqcn));
}
}

/**
* @param array<string, string> $notNullableProperties
*/
private static function assertProperty(\ReflectionProperty $reflectionProperty, array $notNullableProperties): void
private static function assertProperty(\ReflectionProperty $reflectionProperty, Property $property, string $messageFqcn): void
{
if (null === $reflectionProperty->getType()) {
throw new \LogicException(\sprintf('$reflectionProperty::getType cannot be null'));
}
if (!$reflectionProperty->getType() instanceof \ReflectionNamedType) {
throw new \LogicException(\sprintf('$reflectionProperty::getType must be an instance of ReflectionNamedType'));
}
if ($reflectionProperty->getType()->getName() !== $notNullableProperties[$reflectionProperty->getName()]) {
throw new \LogicException(\sprintf('Property type mismatch between properties from $messageFqcn class and $notNullableProperties. Please verify your integration.'));
if ($reflectionProperty->getType()->getName() !== $property->type) {
throw new \LogicException(\sprintf('Error for property "%s" in class "%s", the type mismatch between the old and the new version of class. Please verify your integration.', $reflectionProperty->getName(), $messageFqcn));
}
}
}
2 changes: 1 addition & 1 deletion src/Command/ValidateZddMessageCommand.php
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ public function execute(InputInterface $input, OutputInterface $output): int
$messageToAssert = $this->zddMessageFilesystem->read($messageFqcn);

try {
ZddMessageAssert::assert($messageFqcn, $messageToAssert->serializedMessage(), $messageToAssert->notNullableProperties());
ZddMessageAssert::assert($messageFqcn, $messageToAssert->serializedMessage(), $messageToAssert->propertyList());

$table->addRow([$key + 1, $messageFqcn, 'Yes ✅']);
} catch (\Throwable $e) {
10 changes: 10 additions & 0 deletions src/Factory/Property.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Yousign\ZddMessageBundle\Factory;

final class Property
{
public function __construct(public readonly string $name, public readonly string $type, public readonly mixed $value)
{
}
}
84 changes: 70 additions & 14 deletions src/Factory/PropertyList.php
Original file line number Diff line number Diff line change
@@ -8,39 +8,95 @@
final class PropertyList
{
/**
* @var array<string, mixed>
* @var Property[]
*/
private array $properties = [];

/**
* @var array<string, string>
* @param Property[] $properties
*/
private array $notNullableProperties = [];
public function __construct(array $properties = [])
{
foreach ($properties as $property) {
$this->properties[$property->name] = $property;
}
}

public function addNullableProperty(string $propertyName): void
public function addProperty(Property $property): void
{
$this->properties[$propertyName] = null;
$this->properties[$property->name] = $property;
}

public function addProperty(string $propertyName, string $type, mixed $value): void
public static function fromJson(string $data): self
{
$this->properties[$propertyName] = $value;
$this->notNullableProperties[$propertyName] = $type;
/** @var array<array<string,string>> $decodedProperties */
$decodedProperties = \json_decode($data, true);
$properties = [];
foreach ($decodedProperties as $decodedProperty) {
$name = $decodedProperty['name'] ?? null;
$type = $decodedProperty['type'] ?? null;

if (null === $name || null === $type) {
throw new \LogicException(sprintf('Missing keys name and/or type in decoded properties from data: "%s"', $data));
}
$properties[] = new Property($decodedProperty['name'], $decodedProperty['type'], null);
}

return new self($properties);
}

/**
* @return array<string, mixed>
* @return array<string>
*/
public function getProperties(): array
public function getPropertiesName(): array
{
return $this->properties;
return array_keys($this->properties);
}

/**
* @return array<string, string>
* @return Property[]
*/
public function getNotNullableProperties(): array
public function getProperties(): array
{
return $this->properties;
}

public function has(string $name): bool
{
return array_key_exists($name, $this->properties);
}

public function get(string $name): Property
{
return $this->notNullableProperties;
$property = $this->properties[$name] ?? null;

if (null === $property) {
throw new \LogicException(sprintf('No property "%s" found in the properties list', $name));
}

return $property;
}

public function remove(string $name): void
{
unset($this->properties[$name]);
}

public function count(): int
{
return count($this->properties);
}

public function toJson(): string
{
$data = [];
foreach ($this->properties as $property) {
$data[] = [
'name' => $property->name,
'type' => $property->type,
];
}

return json_encode($data, JSON_THROW_ON_ERROR);
}
}
12 changes: 3 additions & 9 deletions src/Factory/ZddMessage.php
Original file line number Diff line number Diff line change
@@ -7,13 +7,10 @@
*/
final class ZddMessage
{
/**
* @param array<string, string> $notNullableProperties
*/
public function __construct(
private readonly string $messageFqcn,
private readonly string $serializedMessage,
private readonly array $notNullableProperties,
private readonly PropertyList $propertyList,
private readonly ?object $message = null,
) {
}
@@ -28,12 +25,9 @@ public function serializedMessage(): string
return $this->serializedMessage;
}

/**
* @return array<string, string>
*/
public function notNullableProperties(): array
public function propertyList(): PropertyList
{
return $this->notNullableProperties;
return $this->propertyList;
}

public function messageFqcn(): string
6 changes: 3 additions & 3 deletions src/Factory/ZddMessageFactory.php
Original file line number Diff line number Diff line change
@@ -26,11 +26,11 @@ public function create(string $className): ZddMessage
$propertyList = $this->propertyExtractor->extractPropertiesFromClass($className);

$message = (new \ReflectionClass($className))->newInstanceWithoutConstructor();
foreach ($propertyList->getProperties() as $property => $value) {
$this->forcePropertyValue($message, $property, $value);
foreach ($propertyList->getProperties() as $property) {
$this->forcePropertyValue($message, $property->name, $property->value);
}

return new ZddMessage($className, serialize($message), $propertyList->getNotNullableProperties(), $message);
return new ZddMessage($className, serialize($message), $propertyList, $message);
}

private function forcePropertyValue(object $object, string $property, mixed $value): void
9 changes: 2 additions & 7 deletions src/Factory/ZddPropertyExtractor.php
Original file line number Diff line number Diff line change
@@ -36,18 +36,13 @@ public function extractPropertiesFromClass(string $className): PropertyList
throw InvalidTypeException::typeMissing($propertyName, $className);
}

if ($propertyType->allowsNull()) {
$propertyList->addNullableProperty($propertyName);

continue;
}

if (!$propertyType instanceof \ReflectionNamedType) {
throw InvalidTypeException::typeNotSupported();
}

$typeHint = $propertyType->getName();
$propertyList->addProperty($propertyName, $typeHint, $this->generateFakeValueFromType($typeHint));
$value = $propertyType->allowsNull() ? null : $this->generateFakeValueFromType($typeHint);
$propertyList->addProperty(new Property($propertyName, $typeHint, $value));
}

return $propertyList;
23 changes: 12 additions & 11 deletions src/Filesystem/ZddMessageFilesystem.php
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

namespace Yousign\ZddMessageBundle\Filesystem;

use Yousign\ZddMessageBundle\Factory\PropertyList;
use Yousign\ZddMessageBundle\Factory\ZddMessage;

/**
@@ -30,10 +31,10 @@ public function write(ZddMessage $zddMessage): void
throw new \RuntimeException(\sprintf('Unable to write file "%s"', $serializedMessagePath));
}

$notNullablePropertiesPath = $this->getPathToNotNullableProperties($zddMessage->messageFqcn());
$byteWrittenInJson = \file_put_contents($notNullablePropertiesPath, \json_encode($zddMessage->notNullableProperties()));
$propertiesPath = $this->getPathToProperties($zddMessage->messageFqcn());
$byteWrittenInJson = \file_put_contents($propertiesPath, $zddMessage->propertyList()->toJson());
if (false === $byteWrittenInJson || 0 === $byteWrittenInJson) {
throw new \RuntimeException(\sprintf('Unable to write file "%s"', $notNullablePropertiesPath));
throw new \RuntimeException(\sprintf('Unable to write file "%s"', $propertiesPath));
}
}

@@ -44,14 +45,14 @@ public function read(string $messageFqcn): ZddMessage
throw new \RuntimeException(\sprintf('Unable to read file "%s"', $serializedMessagePath));
}

$notNullablePropertiesPath = $this->getPathToNotNullableProperties($messageFqcn);
if (false === $notNullableProperties = \file_get_contents($notNullablePropertiesPath)) {
throw new \RuntimeException(\sprintf('Unable to read file "%s"', $notNullablePropertiesPath));
$propertiesPath = $this->getPathToProperties($messageFqcn);
if (false === $properties = \file_get_contents($propertiesPath)) {
throw new \RuntimeException(\sprintf('Unable to read file "%s"', $propertiesPath));
}
$notNullableProperties = \json_decode($notNullableProperties, true);

/* @phpstan-ignore-next-line as $notNullableProperties comes from system */
return new ZddMessage($messageFqcn, $serializedMessage, $notNullableProperties);
$propertyList = PropertyList::fromJson($properties);

return new ZddMessage($messageFqcn, $serializedMessage, $propertyList);
}

public function exists(string $messageFqcn): bool
@@ -80,11 +81,11 @@ private function getPathToSerializedMessage(string $messageFqcn): string
return $this->zddPath.'/'.$directory.'/'.$shortName.'.txt';
}

private function getPathToNotNullableProperties(string $messageFqcn): string
private function getPathToProperties(string $messageFqcn): string
{
[$directory, $shortName] = $this->getDirectoryAndShortname($messageFqcn);

return $this->zddPath.'/'.$directory.'/'.$shortName.'.not_nullable_properties.json';
return $this->zddPath.'/'.$directory.'/'.$shortName.'.properties.json';
}

private function getBasePath(string $messageFqcn): string
2 changes: 1 addition & 1 deletion tests/Func/GenerateZddMessageCommandTest.php
Original file line number Diff line number Diff line change
@@ -62,7 +62,7 @@ private function assertSerializedFilesExist(string $baseDirectory): void
foreach ($messageConfig->getMessageToAssert() as $message) {
$shortName = (new \ReflectionClass($message))->getShortName();
$this->assertFileExists($baseDirectory.'/'.$shortName.'.txt');
$this->assertFileExists($baseDirectory.'/'.$shortName.'.not_nullable_properties.json');
$this->assertFileExists($baseDirectory.'/'.$shortName.'.properties.json');
}
}
}
Loading

0 comments on commit f3aa7b3

Please sign in to comment.