diff --git a/README.md b/README.md index cf88e4c..04a6b13 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Transfer Object Generator ========================== -Would you like to build Transfer Objects (TO) easily? +Would you like to build lightweight Transfer Objects (TO) easily? You're in the right place! Build TOs Using an Array as Blueprint @@ -22,7 +22,7 @@ $data = [ ]; ``` -then facade method converts array into `YML` definition file: +TO facade method helps to convert array into `YML` definition file: ```yml Customer: firstName: @@ -31,7 +31,7 @@ Customer: type: string ``` -finally console command generates TO: +Generator console command builds TO based on definition file: ```php $customerTransfer = new CustomerTransfer(); $customerTransfer->firstName = 'Jan'; @@ -39,8 +39,19 @@ $customerTransfer->lastName = 'Kowalski'; ``` How it works in action can be found on Wiki: - - [Try Sample with Array](/doc/samples/try-definition-generator.php) - - [Try Sample with YML Definition](/doc/samples/try-transfer-generator.php) + - [Try Sample to generate Definition files](/doc/samples/try-definition-generator.php) + - [Try Sample to generate TOs](/doc/samples/try-transfer-generator.php) + - [Try Advanced Sample to generate TOs](/doc/samples/try-advanced-transfer-generator.php) + +Key Features +------------ + +* **Interface methods:** implements `fromArray()`, `toArray()` +* **Standard interfaces:** implements `IteratorAggregate`, `JsonSerializable`, and `Countable` +* **Lightweight:** TO includes only data without any business logic +* **Nullable:** supports both attribute types nullable and not nullable (`required:`) +* **BackedEnum:** supports `BackedEnum` +* **Adaptable:** compatible with custom Data Transfer Object (DTO) implementation Installation ------------ @@ -48,17 +59,15 @@ Installation Composer installation: ```shell -$ composer require-dev picamator/transfer-object +$ composer require picamator/transfer-object ``` Usage ----- -Transfer Object (TO) generator can be used in two ways: - -### I. Via Terminal +### Terminal -Run command bellow, specifying your configuration file: +Run command bellow to generate Transfer Objects: ```shell $ ./vendor/bin/generate-transfer [-c|--configuration CONFIGURATION] @@ -68,9 +77,10 @@ Please check Wiki for more details: - [Command Configuration](https://github.com/picamator/transfer-object/wiki/Command-Configuration) - [Definition File](https://github.com/picamator/transfer-object/wiki/Definition-File) -### II. Via Facade Interface +### Facade Interface -Directly call facade interfaces `TransferGeneratorFacadeInterface`, `DefinitionGeneratorFacadeInterface`. +Facade interface `DefinitionGeneratorFacadeInterface` is used to generate `YML` definition file +based on array. Please check Wiki for more details: - [Facade Interfaces](https://github.com/picamator/transfer-object/wiki/Facade-Interfaces) @@ -79,8 +89,7 @@ Please check Wiki for more details: Acknowledgment -------------- -Many thanks to everyone for you contribution, supports and feedback! -Have fun with using Transfer Object generator! +Many thanks for your contribution, supports, feedback and simply using Transfer Object Generator! Contribution ------------ diff --git a/SECURITY.md b/SECURITY.md index 078a2f6..56231de 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,11 +4,10 @@ Security Policy Reporting a Vulnerability ------------------------- -When you found an security vulnerability on Transfer Object Generator, -please report it by private message to one of project's maintainer using -any publicly available maintainer GitHub profile. +If you discover a security vulnerability in the Transfer Object Generator, please report it privately to one of the project's maintainers. +You can reach them through their GitHub profiles contact data. ### Important note -Please don't report security issues in publicly available way neither via GitHub issues -nor discussions or pull requests. +Please do not report security issues through public channels such as GitHub issues, discussions, or pull requests. +This ensures that security vulnerabilities are addressed quicky and do not pose a risk to users. diff --git a/composer.json b/composer.json index 7100042..b95a9f8 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "picamator/transfer-object", "description": "Transfer Object Generator based on property hooks and FixedArray.", - "keywords": ["generator", "code generator", "data transfer object", "dto", "dev"], + "keywords": ["generator", "code generator", "data transfer object", "dto"], "license": "MIT", "authors": [ { diff --git a/config/definition/transfer-generator.transfer.yml b/config/definition/transfer-generator.transfer.yml index 7b821e5..d6fb1bb 100644 --- a/config/definition/transfer-generator.transfer.yml +++ b/config/definition/transfer-generator.transfer.yml @@ -57,6 +57,8 @@ DefinitionProperty: isNullable: type: bool required: + namespace: + type: string DefinitionValidator: isValid: diff --git a/doc/samples/Advanced/CredentialsData.php b/doc/samples/Advanced/CredentialsData.php new file mode 100644 index 0000000..75a1968 --- /dev/null +++ b/doc/samples/Advanced/CredentialsData.php @@ -0,0 +1,23 @@ + $this->login; + set => $this->login = $value; + } + + public string $token { + get => $this->token; + set => $this->token = $value; + } +} diff --git a/doc/samples/Generated/AdvancedTransferGenerator/AdvancedCustomerTransfer.php b/doc/samples/Generated/AdvancedTransferGenerator/AdvancedCustomerTransfer.php new file mode 100644 index 0000000..c434cff --- /dev/null +++ b/doc/samples/Generated/AdvancedTransferGenerator/AdvancedCustomerTransfer.php @@ -0,0 +1,53 @@ + self::CREDENTIALS_DATA_NAME, + self::CUSTOMER => self::CUSTOMER_DATA_NAME, + ]; + + // credentials + #[PropertyTypeAttribute(CredentialsData::class)] + public const string CREDENTIALS = 'credentials'; + protected const string CREDENTIALS_DATA_NAME = 'CREDENTIALS'; + protected const int CREDENTIALS_DATA_INDEX = 0; + + public TransferInterface&CredentialsData $credentials { + get => $this->getData(self::CREDENTIALS_DATA_INDEX); + set => $this->setData(self::CREDENTIALS_DATA_INDEX, $value); + } + + // customer + #[PropertyTypeAttribute(CustomerTransfer::class)] + public const string CUSTOMER = 'customer'; + protected const string CUSTOMER_DATA_NAME = 'CUSTOMER'; + protected const int CUSTOMER_DATA_INDEX = 1; + + public TransferInterface&CustomerTransfer $customer { + get => $this->getData(self::CUSTOMER_DATA_INDEX); + set => $this->setData(self::CUSTOMER_DATA_INDEX, $value); + } +} diff --git a/doc/samples/config/advanced-transfer-generator/definition/advanced-customer.transfer.yml b/doc/samples/config/advanced-transfer-generator/definition/advanced-customer.transfer.yml new file mode 100644 index 0000000..eca2875 --- /dev/null +++ b/doc/samples/config/advanced-transfer-generator/definition/advanced-customer.transfer.yml @@ -0,0 +1,5 @@ +AdvancedCustomer: + customer: + type: "Picamator\\Doc\\Samples\\TransferObject\\Generated\\TransferGenerator\\CustomerTransfer" + credentials: + type: "Picamator\\Doc\\Samples\\TransferObject\\Advanced\\CredentialsData" diff --git a/doc/samples/config/advanced-transfer-generator/generator.config.yml b/doc/samples/config/advanced-transfer-generator/generator.config.yml new file mode 100644 index 0000000..812a8e1 --- /dev/null +++ b/doc/samples/config/advanced-transfer-generator/generator.config.yml @@ -0,0 +1,4 @@ +generator: + transferNamespace: "Picamator\\Doc\\Samples\\TransferObject\\Generated\\AdvancedTransferGenerator" + transferPath: "${PROJECT_ROOT}/doc/samples/Generated/AdvancedTransferGenerator" + definitionPath: "${PROJECT_ROOT}/doc/samples/config/advanced-transfer-generator/definition" diff --git a/doc/samples/try-advanced-transfer-generator.php b/doc/samples/try-advanced-transfer-generator.php new file mode 100644 index 0000000..6c53feb --- /dev/null +++ b/doc/samples/try-advanced-transfer-generator.php @@ -0,0 +1,110 @@ +firstName = 'Jan'; +$customerTransfer->lastName = 'Kowalski'; + +echo <<<'STORY' +============================================================================ + Lets take CredentialsData form Advanced + & + implements TransferInterface using DummyTransferAdapterTrait +============================================================================ + +STORY; +$credentialsData = new CredentialsData(); +$credentialsData->login = 'jan.kowalski'; +$credentialsData->token = 'some-random-token'; + +$encodedCredentialsData = json_encode($credentialsData); + +$serializedCredentialsData = serialize($credentialsData); +$unserializedCredentialsData = unserialize($serializedCredentialsData); + +$iteratedCredentialsData = implode(', ', iterator_to_array($credentialsData->getIterator())); + +echo <<count()} +JSON encode: $encodedCredentialsData +Serialized: $serializedCredentialsData +Iterated: [$iteratedCredentialsData] + +DEBUG; + +echo <<<'STORY' +=================================================================== + Create a Definition file combining both objects +=================================================================== + +AdvancedCustomer: + customer: + type: 'Picamator\\Doc\\Samples\\TransferObject\\Generated\\TransferGenerator\\CustomerTransfer' + credentials: + type: 'Picamator\\Doc\\Samples\\TransferObject\\Advanced\\CredentialsData' + + +STORY; + +echo <<<'STORY' +====================================================== + Generate Transfer Objects +====================================================== + +STORY; +$configPath = __DIR__ . '/config/advanced-transfer-generator/generator.config.yml'; +new TransferGeneratorFacade()->generateTransfersOrFail($configPath); + +echo <<<'STORY' +====================================================== + Try newly Generated Transfer Object + & + Debug +====================================================== + +STORY; + +$advancedCustomerTransfer = new AdvancedCustomerTransfer(); +$advancedCustomerTransfer->customer = $customerTransfer; +$advancedCustomerTransfer->credentials = $credentialsData; + +var_dump($advancedCustomerTransfer->toArray()); + +echo <<<'STORY' +====================================================== + Convert fromArray +====================================================== + +STORY; +$advancedCustomerTransfer = new AdvancedCustomerTransfer()->fromArray([ + AdvancedCustomerTransfer::CUSTOMER => [ + CustomerTransfer::FIRST_NAME => 'Max', + CustomerTransfer::LAST_NAME => 'Mustermann', + ], + AdvancedCustomerTransfer::CREDENTIALS => [], +]); + +var_dump($advancedCustomerTransfer->toArray()); diff --git a/src/Generated/DefinitionPropertyTransfer.php b/src/Generated/DefinitionPropertyTransfer.php index 8516bbf..d1de227 100644 --- a/src/Generated/DefinitionPropertyTransfer.php +++ b/src/Generated/DefinitionPropertyTransfer.php @@ -20,13 +20,14 @@ final class DefinitionPropertyTransfer extends AbstractTransfer { use TransferTrait; - protected const int META_DATA_SIZE = 6; + protected const int META_DATA_SIZE = 7; protected const array META_DATA = [ self::BUILD_IN_TYPE => self::BUILD_IN_TYPE_DATA_NAME, self::COLLECTION_TYPE => self::COLLECTION_TYPE_DATA_NAME, self::ENUM_TYPE => self::ENUM_TYPE_DATA_NAME, self::IS_NULLABLE => self::IS_NULLABLE_DATA_NAME, + self::NAMESPACE => self::NAMESPACE_DATA_NAME, self::PROPERTY_NAME => self::PROPERTY_NAME_DATA_NAME, self::TRANSFER_TYPE => self::TRANSFER_TYPE_DATA_NAME, ]; @@ -72,10 +73,20 @@ final class DefinitionPropertyTransfer extends AbstractTransfer set => $this->setData(self::IS_NULLABLE_DATA_INDEX, $value); } + // namespace + public const string NAMESPACE = 'namespace'; + protected const string NAMESPACE_DATA_NAME = 'NAMESPACE'; + protected const int NAMESPACE_DATA_INDEX = 4; + + public ?string $namespace { + get => $this->getData(self::NAMESPACE_DATA_INDEX); + set => $this->setData(self::NAMESPACE_DATA_INDEX, $value); + } + // propertyName public const string PROPERTY_NAME = 'propertyName'; protected const string PROPERTY_NAME_DATA_NAME = 'PROPERTY_NAME'; - protected const int PROPERTY_NAME_DATA_INDEX = 4; + protected const int PROPERTY_NAME_DATA_INDEX = 5; public string $propertyName { get => $this->getRequiredData(self::PROPERTY_NAME_DATA_INDEX); @@ -85,7 +96,7 @@ final class DefinitionPropertyTransfer extends AbstractTransfer // transferType public const string TRANSFER_TYPE = 'transferType'; protected const string TRANSFER_TYPE_DATA_NAME = 'TRANSFER_TYPE'; - protected const int TRANSFER_TYPE_DATA_INDEX = 5; + protected const int TRANSFER_TYPE_DATA_INDEX = 6; public ?string $transferType { get => $this->getData(self::TRANSFER_TYPE_DATA_INDEX); diff --git a/src/Transfer/AbstractTransfer.php b/src/Transfer/AbstractTransfer.php index 6a27aa8..5311675 100644 --- a/src/Transfer/AbstractTransfer.php +++ b/src/Transfer/AbstractTransfer.php @@ -9,7 +9,7 @@ abstract class AbstractTransfer implements TransferInterface { - use PropertyTypeTrait; + use AttributeTransferTrait; protected const int META_DATA_SIZE = 0; diff --git a/src/Transfer/PropertyTypeTrait.php b/src/Transfer/AttributeTransferTrait.php similarity index 97% rename from src/Transfer/PropertyTypeTrait.php rename to src/Transfer/AttributeTransferTrait.php index 025666c..55c19a7 100644 --- a/src/Transfer/PropertyTypeTrait.php +++ b/src/Transfer/AttributeTransferTrait.php @@ -8,7 +8,7 @@ use ReflectionAttribute; use ReflectionClassConstant; -trait PropertyTypeTrait +trait AttributeTransferTrait { final protected function getConstantAttribute(string $constantName): ?PropertyTypeAttributeInterface { diff --git a/src/Transfer/DummyTransferAdapterTrait.php b/src/Transfer/DummyTransferAdapterTrait.php new file mode 100644 index 0000000..0c9de7d --- /dev/null +++ b/src/Transfer/DummyTransferAdapterTrait.php @@ -0,0 +1,63 @@ + + */ + public function getIterator(): Traversable + { + return new EmptyIterator(); + } + + public function count(): int + { + return 0; + } + + /** + * @return array + */ + public function toArray(): array + { + return []; + } + + /** + * @param array $data + */ + public function fromArray(array $data): static + { + return $this; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return []; + } + + /** + * @return array + */ + final public function __serialize(): array + { + return []; + } + + /** + * @param array $data + */ + final public function __unserialize(array $data): void + { + } +} diff --git a/src/TransferGenerator/Config/Validator/Content/TransferNamespaceConfigContentValidator.php b/src/TransferGenerator/Config/Validator/Content/TransferNamespaceConfigContentValidator.php index 74be26a..65f88f9 100644 --- a/src/TransferGenerator/Config/Validator/Content/TransferNamespaceConfigContentValidator.php +++ b/src/TransferGenerator/Config/Validator/Content/TransferNamespaceConfigContentValidator.php @@ -6,26 +6,26 @@ use Picamator\TransferObject\Generated\ConfigContentTransfer; use Picamator\TransferObject\Generated\ValidatorMessageTransfer; +use Picamator\TransferObject\TransferGenerator\Validator\NamespaceValidatorTrait; use Picamator\TransferObject\TransferGenerator\Validator\ValidatorMessageTrait; readonly class TransferNamespaceConfigContentValidator implements ConfigContentValidatorInterface { use ValidatorMessageTrait; - - private const string NAMESPACE_REGEX = '#^(?:[A-Z][a-zA-Z0-9_]*\\\\)*[A-Z][a-zA-Z0-9_]*$#'; + use NamespaceValidatorTrait; private const string ERROR_MESSAGE_TEMPLATE = 'Invalid configuration namespace "%s".'; public function validate(ConfigContentTransfer $configContentTransfer): ValidatorMessageTransfer { $namespace = $configContentTransfer->transferNamespace; - if (preg_match(self::NAMESPACE_REGEX, $namespace) !== 1) { - $errorMessage = $this->getErrorMessage($namespace); - - return $this->createErrorMessageTransfer($errorMessage); + if ($this->isValidNamespace($namespace)) { + return $this->createSuccessMessageTransfer(); } - return $this->createSuccessMessageTransfer(); + $errorMessage = $this->getErrorMessage($namespace); + + return $this->createErrorMessageTransfer($errorMessage); } private function getErrorMessage(string $namespace): string diff --git a/src/TransferGenerator/Definition/DefinitionFactory.php b/src/TransferGenerator/Definition/DefinitionFactory.php index 173785c..b63088f 100644 --- a/src/TransferGenerator/Definition/DefinitionFactory.php +++ b/src/TransferGenerator/Definition/DefinitionFactory.php @@ -36,6 +36,7 @@ use Picamator\TransferObject\TransferGenerator\Definition\Validator\Property\CollectionTypePropertyValidator; use Picamator\TransferObject\TransferGenerator\Definition\Validator\Property\EnumTypePropertyValidator; use Picamator\TransferObject\TransferGenerator\Definition\Validator\Property\NamePropertyValidator; +use Picamator\TransferObject\TransferGenerator\Definition\Validator\Property\NamespacePropertyValidator; use Picamator\TransferObject\TransferGenerator\Definition\Validator\Property\PropertyValidatorInterface; use Picamator\TransferObject\TransferGenerator\Definition\Validator\Property\RequiredTypePropertyValidator; use Picamator\TransferObject\TransferGenerator\Definition\Validator\Property\TransferTypePropertyValidator; @@ -90,9 +91,15 @@ protected function createPropertyValidators(): ArrayObject $this->createTransferTypePropertyValidator(), $this->createCollectionTypePropertyValidator(), $this->createEnumTypePropertyValidator(), + $this->createNamespacePropertyValidator(), ]); } + protected function createNamespacePropertyValidator(): PropertyValidatorInterface + { + return new NamespacePropertyValidator(); + } + protected function createEnumTypePropertyValidator(): PropertyValidatorInterface { return new EnumTypePropertyValidator(); diff --git a/src/TransferGenerator/Definition/Enum/TypePrefixEnum.php b/src/TransferGenerator/Definition/Enum/TypePrefixEnum.php new file mode 100644 index 0000000..e7c7037 --- /dev/null +++ b/src/TransferGenerator/Definition/Enum/TypePrefixEnum.php @@ -0,0 +1,10 @@ +className = $className; + $contentTransfer->className = $className . TypePrefixEnum::TRANSFER->value; foreach ($properties as $propertyName => $propertyType) { $propertyType = is_array($propertyType) ? $propertyType : []; diff --git a/src/TransferGenerator/Definition/Parser/Expander/CollectionTypePropertyExpander.php b/src/TransferGenerator/Definition/Parser/Expander/CollectionTypePropertyExpander.php index 7033689..8ae38f5 100644 --- a/src/TransferGenerator/Definition/Parser/Expander/CollectionTypePropertyExpander.php +++ b/src/TransferGenerator/Definition/Parser/Expander/CollectionTypePropertyExpander.php @@ -5,9 +5,12 @@ namespace Picamator\TransferObject\TransferGenerator\Definition\Parser\Expander; use Picamator\TransferObject\Generated\DefinitionPropertyTransfer; +use Picamator\TransferObject\TransferGenerator\Definition\Enum\TypePrefixEnum; readonly class CollectionTypePropertyExpander implements PropertyExpanderInterface { + use NamespacePropertyExpanderTrait; + private const string COLLECTION_TYPE_KEY = 'collectionType'; public function isApplicable(array $propertyType): bool @@ -22,7 +25,15 @@ public function isNextAllowed(): false public function expandPropertyTransfer(array $propertyType, DefinitionPropertyTransfer $propertyTransfer): void { - $propertyTransfer->collectionType = $this->getCollectionType($propertyType); + $collectionType = $this->getCollectionType($propertyType) ?: ''; + if (!$this->isNamespace($collectionType)) { + $propertyTransfer->collectionType = $collectionType . TypePrefixEnum::TRANSFER->value; + + return; + } + + $propertyTransfer->namespace = $collectionType; + $propertyTransfer->collectionType = $this->getClassName($collectionType); } /** diff --git a/src/TransferGenerator/Definition/Parser/Expander/NamespacePropertyExpanderTrait.php b/src/TransferGenerator/Definition/Parser/Expander/NamespacePropertyExpanderTrait.php new file mode 100644 index 0000000..3388b94 --- /dev/null +++ b/src/TransferGenerator/Definition/Parser/Expander/NamespacePropertyExpanderTrait.php @@ -0,0 +1,40 @@ +getNamespaceAlias($namespace); + if ($alias !== null) { + return $alias; + } + + /** @var string $className */ + $className = strrchr($namespace, '\\'); + + return ltrim($className, '\\'); + } + + private function getNamespaceAlias(string $namespace): ?string + { + if (!str_contains($namespace, self::NAMESPACE_ALIAS_SEPARATOR)) { + return null; + } + + /** @var string $alias */ + $alias = strrchr($namespace, self::NAMESPACE_ALIAS_SEPARATOR); + + return trim($alias); + } +} diff --git a/src/TransferGenerator/Definition/Parser/Expander/TransferTypePropertyExpander.php b/src/TransferGenerator/Definition/Parser/Expander/TransferTypePropertyExpander.php index a94fc96..92a588f 100644 --- a/src/TransferGenerator/Definition/Parser/Expander/TransferTypePropertyExpander.php +++ b/src/TransferGenerator/Definition/Parser/Expander/TransferTypePropertyExpander.php @@ -5,9 +5,12 @@ namespace Picamator\TransferObject\TransferGenerator\Definition\Parser\Expander; use Picamator\TransferObject\Generated\DefinitionPropertyTransfer; +use Picamator\TransferObject\TransferGenerator\Definition\Enum\TypePrefixEnum; readonly class TransferTypePropertyExpander implements PropertyExpanderInterface { + use NamespacePropertyExpanderTrait; + private const string TYPE_KEY = 'type'; public function isApplicable(array $propertyType): bool @@ -22,7 +25,15 @@ public function isNextAllowed(): false public function expandPropertyTransfer(array $propertyType, DefinitionPropertyTransfer $propertyTransfer): void { - $propertyTransfer->transferType = $this->getTransferType($propertyType); + $transferType = $this->getTransferType($propertyType) ?: ''; + if (!$this->isNamespace($transferType)) { + $propertyTransfer->transferType = $transferType . TypePrefixEnum::TRANSFER->value; + + return; + } + + $propertyTransfer->namespace = $transferType; + $propertyTransfer->transferType = $this->getClassName($transferType); } /** diff --git a/src/TransferGenerator/Definition/Validator/ClassNameValidator.php b/src/TransferGenerator/Definition/Validator/ClassNameValidator.php index 9e381cb..b29cdac 100644 --- a/src/TransferGenerator/Definition/Validator/ClassNameValidator.php +++ b/src/TransferGenerator/Definition/Validator/ClassNameValidator.php @@ -6,6 +6,7 @@ use Picamator\TransferObject\Generated\ValidatorMessageTransfer; use Picamator\TransferObject\TransferGenerator\Validator\ValidatorMessageTrait; +use Picamator\TransferObject\TransferGenerator\Validator\VariableValidatorTrait; readonly class ClassNameValidator implements ClassNameValidatorInterface { diff --git a/src/TransferGenerator/Definition/Validator/Property/BuildInTypePropertyValidator.php b/src/TransferGenerator/Definition/Validator/Property/BuildInTypePropertyValidator.php index 6a51295..a4d2963 100644 --- a/src/TransferGenerator/Definition/Validator/Property/BuildInTypePropertyValidator.php +++ b/src/TransferGenerator/Definition/Validator/Property/BuildInTypePropertyValidator.php @@ -12,8 +12,7 @@ { use ValidatorMessageTrait; - private const string UNSUPPORTED_PROPERTY_TYPE_ERROR_MESSAGE_TEMPLATE - = 'Property "%s" with type "%s" is not supported.'; + private const string UNSUPPORTED_TYPE_ERROR_MESSAGE_TEMPLATE = 'Property "%s" with type "%s" is not supported.'; public function isApplicable(DefinitionPropertyTransfer $propertyTransfer): bool { @@ -34,7 +33,7 @@ public function validate(DefinitionPropertyTransfer $propertyTransfer): Validato private function getErrorMessage(DefinitionPropertyTransfer $propertyTransfer): string { return sprintf( - self::UNSUPPORTED_PROPERTY_TYPE_ERROR_MESSAGE_TEMPLATE, + self::UNSUPPORTED_TYPE_ERROR_MESSAGE_TEMPLATE, $propertyTransfer->propertyName, $propertyTransfer->buildInType->value ?? '', ); diff --git a/src/TransferGenerator/Definition/Validator/Property/NamePropertyValidator.php b/src/TransferGenerator/Definition/Validator/Property/NamePropertyValidator.php index 3f27e0b..99f2d20 100644 --- a/src/TransferGenerator/Definition/Validator/Property/NamePropertyValidator.php +++ b/src/TransferGenerator/Definition/Validator/Property/NamePropertyValidator.php @@ -6,15 +6,15 @@ use Picamator\TransferObject\Generated\DefinitionPropertyTransfer; use Picamator\TransferObject\Generated\ValidatorMessageTransfer; -use Picamator\TransferObject\TransferGenerator\Definition\Validator\VariableValidatorTrait; use Picamator\TransferObject\TransferGenerator\Validator\ValidatorMessageTrait; +use Picamator\TransferObject\TransferGenerator\Validator\VariableValidatorTrait; readonly class NamePropertyValidator implements PropertyValidatorInterface { use VariableValidatorTrait; use ValidatorMessageTrait; - private const string INVALID_PROPERTY_NAME_ERROR_MESSAGE_TEMPLATE = 'Invalid property name "%s".'; + private const string INVALID_NAME_ERROR_MESSAGE_TEMPLATE = 'Invalid property name "%s".'; public function isApplicable(DefinitionPropertyTransfer $propertyTransfer): true { @@ -35,7 +35,7 @@ public function validate(DefinitionPropertyTransfer $propertyTransfer): Validato private function getErrorMessage(DefinitionPropertyTransfer $propertyTransfer): string { return sprintf( - self::INVALID_PROPERTY_NAME_ERROR_MESSAGE_TEMPLATE, + self::INVALID_NAME_ERROR_MESSAGE_TEMPLATE, $propertyTransfer->propertyName, ); } diff --git a/src/TransferGenerator/Definition/Validator/Property/NamespacePropertyValidator.php b/src/TransferGenerator/Definition/Validator/Property/NamespacePropertyValidator.php new file mode 100644 index 0000000..2174b3a --- /dev/null +++ b/src/TransferGenerator/Definition/Validator/Property/NamespacePropertyValidator.php @@ -0,0 +1,58 @@ +namespace !== null; + } + + public function validate(DefinitionPropertyTransfer $propertyTransfer): ValidatorMessageTransfer + { + $namespace = $this->getNamespaceWithoutAlias($propertyTransfer); + if ($this->isValidNamespace($namespace)) { + return $this->createSuccessMessageTransfer(); + } + + $errorMessage = $this->getErrorMessage($propertyTransfer); + + return $this->createErrorMessageTransfer($errorMessage); + } + + private function getErrorMessage(DefinitionPropertyTransfer $propertyTransfer): string + { + return sprintf( + self::INVALID_NAMESPACE_ERROR_MESSAGE_TEMPLATE, + $propertyTransfer->namespace ?: '', + ); + } + + private function getNamespaceWithoutAlias(DefinitionPropertyTransfer $propertyTransfer): string + { + $namespace = $propertyTransfer->namespace ?: ''; + if (!str_contains($namespace, self::NAMESPACE_ALIAS_SEPARATOR)) { + return $namespace; + } + + /** @var string $namespace */ + $namespace = strstr($namespace, self::NAMESPACE_ALIAS_SEPARATOR, true); + + return trim($namespace); + } +} diff --git a/src/TransferGenerator/Generator/Enum/TransferEnum.php b/src/TransferGenerator/Generator/Enum/TransferEnum.php index 3e65480..e16e6f3 100644 --- a/src/TransferGenerator/Generator/Enum/TransferEnum.php +++ b/src/TransferGenerator/Generator/Enum/TransferEnum.php @@ -5,10 +5,12 @@ namespace Picamator\TransferObject\TransferGenerator\Generator\Enum; use Picamator\TransferObject\Transfer\AbstractTransfer; +use Picamator\TransferObject\Transfer\TransferInterface; use Picamator\TransferObject\Transfer\TransferTrait; enum TransferEnum: string { + case INTERFACE = TransferInterface::class; case ABSTRACT_CLASS = AbstractTransfer::class; case TRAIT = TransferTrait::class; } diff --git a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php index 26d9e97..55a580b 100644 --- a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php +++ b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php @@ -13,7 +13,7 @@ { private const string TEMPORARY_DIR = '_tmp'; - private const string FILE_NAME_TEMPLATE = '%sTransfer.php'; + private const string FILE_EXTENSION = '.php'; private const string FILE_NAME_PATTERN = '*Transfer.php'; public function __construct( @@ -51,9 +51,7 @@ public function rotateTempDir(): void public function writeFile(string $className, string $content): void { - $filePath = $this->getTemporaryPath() - . DIRECTORY_SEPARATOR - . sprintf(self::FILE_NAME_TEMPLATE, $className); + $filePath = $this->getTemporaryPath() . DIRECTORY_SEPARATOR . $className . self::FILE_EXTENSION; if ($this->filesystem->exists($filePath)) { throw new TransferGeneratorException( sprintf('Cannot save file "%s". A file with the same name already exists.', $filePath), diff --git a/src/TransferGenerator/Generator/Render/Expander/BuildInTypeTemplateExpander.php b/src/TransferGenerator/Generator/Render/Expander/BuildInTypeTemplateExpander.php index 0d13983..b1ed445 100644 --- a/src/TransferGenerator/Generator/Render/Expander/BuildInTypeTemplateExpander.php +++ b/src/TransferGenerator/Generator/Render/Expander/BuildInTypeTemplateExpander.php @@ -8,14 +8,11 @@ use Picamator\TransferObject\TransferGenerator\Generator\Enum\AttributeEnum; use Picamator\TransferObject\TransferGenerator\Generator\Enum\AttributeTemplateEnum; use Picamator\TransferObject\TransferGenerator\Generator\Enum\DockBlockTemplateEnum; -use Picamator\TransferObject\TransferGenerator\Generator\Render\TemplateRenderTrait; use Picamator\TransferObject\Generated\DefinitionPropertyTransfer; use Picamator\TransferObject\Generated\TemplateTransfer; readonly class BuildInTypeTemplateExpander implements TemplateExpanderInterface { - use TemplateRenderTrait; - public function isApplicable(DefinitionPropertyTransfer $propertyTransfer): bool { return $propertyTransfer->buildInType !== null; diff --git a/src/TransferGenerator/Generator/Render/Expander/CollectionTypeTemplateExpander.php b/src/TransferGenerator/Generator/Render/Expander/CollectionTypeTemplateExpander.php index 9cf9a1f..602b21e 100644 --- a/src/TransferGenerator/Generator/Render/Expander/CollectionTypeTemplateExpander.php +++ b/src/TransferGenerator/Generator/Render/Expander/CollectionTypeTemplateExpander.php @@ -29,16 +29,27 @@ public function expandTemplateTransfer( $templateTransfer->imports[AttributeEnum::COLLECTION_TYPE_ATTRIBUTE->value] ??= AttributeEnum::COLLECTION_TYPE_ATTRIBUTE->value; - $transferName = $this->getTransferName($propertyTransfer->collectionType ?: ''); - $propertyName = $propertyTransfer->propertyName; $templateTransfer->properties[$propertyName] = BuildInTypeEnum::ARRAY_OBJECT->value; - $templateTransfer->attributes[$propertyName] = sprintf( - AttributeTemplateEnum::COLLECTION_TYPE_ATTRIBUTE->value, - $transferName, - ); - $templateTransfer->dockBlocks[$propertyName] = sprintf(DockBlockTemplateEnum::COLLECTION->value, $transferName); - + $templateTransfer->attributes[$propertyName] = $this->getPropertyAttribute($propertyTransfer); + $templateTransfer->dockBlocks[$propertyName] = $this->getPropertyDockBlock($propertyTransfer); $templateTransfer->nullables[$propertyName] = false; } + + private function getPropertyDockBlock(DefinitionPropertyTransfer $propertyTransfer): string + { + $propertyType = $propertyTransfer->collectionType ?: ''; + if ($propertyTransfer->namespace !== null) { + $propertyType = $this->enforceTransferInterface($propertyType); + } + + return sprintf(DockBlockTemplateEnum::COLLECTION->value, $propertyType); + } + + private function getPropertyAttribute(DefinitionPropertyTransfer $propertyTransfer): string + { + $transferType = $propertyTransfer->collectionType ?: ''; + + return sprintf(AttributeTemplateEnum::COLLECTION_TYPE_ATTRIBUTE->value, $transferType); + } } diff --git a/src/TransferGenerator/Generator/Render/Expander/EnumTypeTemplateExpander.php b/src/TransferGenerator/Generator/Render/Expander/EnumTypeTemplateExpander.php index 2a927f3..89c3b10 100644 --- a/src/TransferGenerator/Generator/Render/Expander/EnumTypeTemplateExpander.php +++ b/src/TransferGenerator/Generator/Render/Expander/EnumTypeTemplateExpander.php @@ -30,14 +30,15 @@ public function expandTemplateTransfer( $enumClassName = $this->getEnumName($propertyTransfer); $templateTransfer->properties[$propertyName] = $enumClassName; - $templateTransfer->attributes[$propertyName] = sprintf( - AttributeTemplateEnum::ENUM_TYPE_ATTRIBUTE->value, - $enumClassName, - ); - + $templateTransfer->attributes[$propertyName] = $this->getPropertyAttribute($enumClassName); $templateTransfer->nullables[$propertyName] = $propertyTransfer->isNullable; } + private function getPropertyAttribute(string $enumClassName): string + { + return sprintf(AttributeTemplateEnum::ENUM_TYPE_ATTRIBUTE->value, $enumClassName); + } + private function getEnumName(DefinitionPropertyTransfer $propertyTransfer): string { $enumName = $propertyTransfer->enumType ?? ''; diff --git a/src/TransferGenerator/Generator/Render/Expander/NamespaceTemplateExpander.php b/src/TransferGenerator/Generator/Render/Expander/NamespaceTemplateExpander.php new file mode 100644 index 0000000..63dfbb6 --- /dev/null +++ b/src/TransferGenerator/Generator/Render/Expander/NamespaceTemplateExpander.php @@ -0,0 +1,28 @@ +namespace !== null; + } + + public function expandTemplateTransfer( + DefinitionPropertyTransfer $propertyTransfer, + TemplateTransfer $templateTransfer, + ): void { + $templateTransfer->imports[$propertyTransfer->namespace] ??= $propertyTransfer->namespace; + $templateTransfer->imports[TransferEnum::INTERFACE->value] ??= TransferEnum::INTERFACE->value; + } +} diff --git a/src/TransferGenerator/Generator/Render/Expander/TransferTypeTemplateExpander.php b/src/TransferGenerator/Generator/Render/Expander/TransferTypeTemplateExpander.php index 8bbdbec..4244da7 100644 --- a/src/TransferGenerator/Generator/Render/Expander/TransferTypeTemplateExpander.php +++ b/src/TransferGenerator/Generator/Render/Expander/TransferTypeTemplateExpander.php @@ -25,15 +25,26 @@ public function expandTemplateTransfer( ): void { $templateTransfer->imports[AttributeEnum::TYPE_ATTRIBUTE->value] ??= AttributeEnum::TYPE_ATTRIBUTE->value; - $transferName = $this->getTransferName($propertyTransfer->transferType ?: ''); $propertyName = $propertyTransfer->propertyName; + $templateTransfer->properties[$propertyName] = $this->getPropertyType($propertyTransfer); + $templateTransfer->attributes[$propertyName] = $this->getPropertyAttribute($propertyTransfer); + $templateTransfer->nullables[$propertyName] = $propertyTransfer->isNullable; + } - $templateTransfer->properties[$propertyName] = $transferName; - $templateTransfer->attributes[$propertyName] = sprintf( - AttributeTemplateEnum::TYPE_ATTRIBUTE->value, - $transferName, - ); + private function getPropertyAttribute(DefinitionPropertyTransfer $propertyTransfer): string + { + $transferType = $propertyTransfer->transferType ?: ''; - $templateTransfer->nullables[$propertyName] = $propertyTransfer->isNullable; + return sprintf(AttributeTemplateEnum::TYPE_ATTRIBUTE->value, $transferType); + } + + private function getPropertyType(DefinitionPropertyTransfer $propertyTransfer): string + { + $transferType = $propertyTransfer->transferType ?: ''; + if ($propertyTransfer->namespace === null) { + return $transferType; + } + + return $this->enforceTransferInterface($transferType); } } diff --git a/src/TransferGenerator/Generator/Render/Template/TemplateHelper.php b/src/TransferGenerator/Generator/Render/Template/TemplateHelper.php index 1847f61..c089315 100644 --- a/src/TransferGenerator/Generator/Render/Template/TemplateHelper.php +++ b/src/TransferGenerator/Generator/Render/Template/TemplateHelper.php @@ -16,7 +16,8 @@ private const string PADDING_LEFT = ' '; private const string EMPTY_STRING = ''; - private const string NULLABLE_SIGN = '?'; + private const string NULLABLE_TYPE = '?'; + private const string NULLABLE_UNION = 'null|'; private const string REQUIRED_METHOD_NAME = 'Required'; public function __construct(private TemplateTransfer $templateTransfer) @@ -72,7 +73,16 @@ public function getDockBlock(string $property): string public function getNullable(string $property): string { - return $this->templateTransfer->nullables[$property] ? self::NULLABLE_SIGN : self::EMPTY_STRING; + $propertyType = $this->templateTransfer->properties[$property]; + if (!$this->templateTransfer->nullables[$property] || str_contains($propertyType, '&')) { + return self::EMPTY_STRING; + } + + if (str_contains($propertyType, '|')) { + return self::NULLABLE_UNION; + } + + return self::NULLABLE_TYPE; } public function getRequired(string $property): string diff --git a/src/TransferGenerator/Generator/Render/TemplateBuilder.php b/src/TransferGenerator/Generator/Render/TemplateBuilder.php index 3eb8d8d..76d3472 100644 --- a/src/TransferGenerator/Generator/Render/TemplateBuilder.php +++ b/src/TransferGenerator/Generator/Render/TemplateBuilder.php @@ -29,7 +29,7 @@ public function createTemplateTransfer(DefinitionContentTransfer $contentTransfe { $templateTransfer = new TemplateTransfer(); $templateTransfer->classNamespace = $this->config->getTransferNamespace(); - $templateTransfer->className = $this->getTransferName($contentTransfer->className); + $templateTransfer->className = $contentTransfer->className; $templateTransfer->imports[TransferEnum::ABSTRACT_CLASS->value] = TransferEnum::ABSTRACT_CLASS->value; $templateTransfer->imports[TransferEnum::TRAIT->value] = TransferEnum::TRAIT->value; diff --git a/src/TransferGenerator/Generator/Render/TemplateRenderTrait.php b/src/TransferGenerator/Generator/Render/TemplateRenderTrait.php index 48a6a0e..4eaa80a 100644 --- a/src/TransferGenerator/Generator/Render/TemplateRenderTrait.php +++ b/src/TransferGenerator/Generator/Render/TemplateRenderTrait.php @@ -8,8 +8,6 @@ trait TemplateRenderTrait { - private const string FILE_NAME_SUFFIX = 'Transfer'; - private const string META_CONSTANT_REGEX = '#(?createTransferTypeTemplateExpander(), $this->createBuildInTypeTemplateExpander(), $this->createEnumTypeTemplateExpander(), + $this->createNamespaceTemplateExpander(), ]); } + protected function createNamespaceTemplateExpander(): TemplateExpanderInterface + { + return new NamespaceTemplateExpander(); + } + protected function createEnumTypeTemplateExpander(): TemplateExpanderInterface { return new EnumTypeTemplateExpander(); diff --git a/src/TransferGenerator/Validator/NamespaceValidatorTrait.php b/src/TransferGenerator/Validator/NamespaceValidatorTrait.php new file mode 100644 index 0000000..391a1b1 --- /dev/null +++ b/src/TransferGenerator/Validator/NamespaceValidatorTrait.php @@ -0,0 +1,15 @@ += 1; + return $variableName !== null && preg_match(self::VARIABLE_NAME_PATTERN, $variableName) === 1; } } diff --git a/tests/integration/DefinitionGenerator/data/REFERENCE.md b/tests/integration/DefinitionGenerator/data/REFERENCE.md index 9d7aac6..d4e7038 100644 --- a/tests/integration/DefinitionGenerator/data/REFERENCE.md +++ b/tests/integration/DefinitionGenerator/data/REFERENCE.md @@ -5,4 +5,4 @@ REFERENCE |--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| | [nasa-neo-rest-v1-neo-2465633.json](/json-samples/nasa-neo-rest-v1-neo-2465633.json) | [NASA Open Api](https://api.nasa.gov/neo/rest/v1/neo/2465633?api_key=DEMO_KEY) | | [open-weather.json](/json-samples/open-weather.json) | [OpenWeather](https://openweathermap.org/current#example_JSON) | -| [google-shopping-content-api.json](/json-samples/google-shopping-content-api.json) | [Content API for Shopping](https://developers.google.com/shopping-content/guides/products/products-api?hl=en) | +| [google-shopping-content-api.json](/json-samples/google-shopping-content-api.json) | [Content API for Shopping](https://developers.google.com/shopping-content/guides/products/products-api?hl=en) | diff --git a/tests/integration/Samples/SamplesTest.php b/tests/integration/Samples/SamplesTest.php index ec787cb..7fee8af 100644 --- a/tests/integration/Samples/SamplesTest.php +++ b/tests/integration/Samples/SamplesTest.php @@ -44,5 +44,9 @@ public static function samplesNameDataProvider(): Generator yield 'try-transfer-generator' => [ 'sampleName' => 'try-transfer-generator.php', ]; + + yield 'try-advanced-transfer-generator' => [ + 'sampleName' => 'try-advanced-transfer-generator.php', + ]; } } diff --git a/tests/integration/Transfer/Generated/NamespaceTransfer.php b/tests/integration/Transfer/Generated/NamespaceTransfer.php new file mode 100644 index 0000000..46a0b6d --- /dev/null +++ b/tests/integration/Transfer/Generated/NamespaceTransfer.php @@ -0,0 +1,56 @@ + self::ITEMS_DATA_NAME, + self::REQUIRED => self::REQUIRED_DATA_NAME, + ]; + + // items + #[CollectionPropertyTypeAttribute(ItemTransfer::class)] + public const string ITEMS = 'items'; + protected const string ITEMS_DATA_NAME = 'ITEMS'; + protected const int ITEMS_DATA_INDEX = 0; + + /** @var \ArrayObject */ + public ArrayObject $items { + get => $this->getRequiredData(self::ITEMS_DATA_INDEX); + set => $this->setData(self::ITEMS_DATA_INDEX, $value); + } + + // required + #[PropertyTypeAttribute(RequiredAlias::class)] + public const string REQUIRED = 'required'; + protected const string REQUIRED_DATA_NAME = 'REQUIRED'; + protected const int REQUIRED_DATA_INDEX = 1; + + public TransferInterface&RequiredAlias $required { + get => $this->getData(self::REQUIRED_DATA_INDEX); + set => $this->setData(self::REQUIRED_DATA_INDEX, $value); + } +} diff --git a/tests/integration/Transfer/TransferTest.php b/tests/integration/Transfer/TransferTest.php index 67a4c13..765f76c 100644 --- a/tests/integration/Transfer/TransferTest.php +++ b/tests/integration/Transfer/TransferTest.php @@ -15,6 +15,7 @@ use Picamator\Tests\Integration\TransferObject\Transfer\Enum\ImBasicEnum; use Picamator\Tests\Integration\TransferObject\Transfer\Generated\ItemCollectionTransfer; use Picamator\Tests\Integration\TransferObject\Transfer\Generated\ItemTransfer; +use Picamator\Tests\Integration\TransferObject\Transfer\Generated\NamespaceTransfer; use Picamator\Tests\Integration\TransferObject\Transfer\Generated\RequiredTransfer; use Picamator\TransferObject\Transfer\Exception\PropertyTypeTransferException; use TypeError; @@ -259,4 +260,16 @@ public function testAccessRequiredPropertyBeforeInitialisingShouldRiseException( // Act $requiredTransfer->toArray(); } + + #[Depends('testGenerateTransferShouldSucceed')] + public function testTypeWithNamespaceShouldSucceed(): void + { + // Arrange + $namespaceTransfer = new NamespaceTransfer(); + $namespaceTransfer->items[] = new ItemTransfer(); + $namespaceTransfer->required = new RequiredTransfer(); + + // Act + $this->assertCount(1, $namespaceTransfer->items); + } } diff --git a/tests/integration/Transfer/data/config/definition/item.transfer.yml b/tests/integration/Transfer/data/config/definition/item.transfer.yml index a6d27f9..742e941 100644 --- a/tests/integration/Transfer/data/config/definition/item.transfer.yml +++ b/tests/integration/Transfer/data/config/definition/item.transfer.yml @@ -17,5 +17,5 @@ Item: type: ArrayObject iAmEnum: enumType: '\Picamator\Tests\Integration\TransferObject\Transfer\Enum\ImBackedEnum' - data: + data: # property name on `AbstractTranfer`, testing collision with custom defined property type: array diff --git a/tests/integration/Transfer/data/config/definition/namespace.transfer.yml b/tests/integration/Transfer/data/config/definition/namespace.transfer.yml new file mode 100644 index 0000000..1c2fa00 --- /dev/null +++ b/tests/integration/Transfer/data/config/definition/namespace.transfer.yml @@ -0,0 +1,5 @@ +Namespace: + items: + collectionType: "Picamator\\Tests\\Integration\\TransferObject\\Transfer\\Generated\\ItemTransfer" + required: + type: "Picamator\\Tests\\Integration\\TransferObject\\Transfer\\Generated\\RequiredTransfer as RequiredAlias" diff --git a/tests/integration/TransferGenerator/TransferGeneratorFacadeErrorTest.php b/tests/integration/TransferGenerator/TransferGeneratorFacadeErrorTest.php index 5a62823..b1ccfa4 100644 --- a/tests/integration/TransferGenerator/TransferGeneratorFacadeErrorTest.php +++ b/tests/integration/TransferGenerator/TransferGeneratorFacadeErrorTest.php @@ -129,13 +129,23 @@ public static function invalidDefinitionDataProvider(): Generator yield 'definition file include class without properties should return error' => [ 'configCaseName' => 'empty-property-definition', - 'expectedMessage' => 'Class "AddressStatistics" properties were not defined.', + 'expectedMessage' => 'Class "AddressStatisticsTransfer" properties were not defined.', ]; yield 'definitions not found should return error' => [ 'configCaseName' => 'empty-definition-directory', 'expectedMessage' => 'Missed Transfer Object definitions.', ]; + + yield 'invalid type namespace' => [ + 'configCaseName' => 'invalid-type-namespace', + 'expectedMessage' => 'Invalid property namespace', + ]; + + yield 'invalid type namespace with alias' => [ + 'configCaseName' => 'invalid-type-namespace-with-alias', + 'expectedMessage' => 'Invalid property namespace', + ]; } public function testBulkTransferGeneratorShouldFailOnError(): void diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace-with-alias/definition/address-statistics.transfer.yml b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace-with-alias/definition/address-statistics.transfer.yml new file mode 100644 index 0000000..0741aa0 --- /dev/null +++ b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace-with-alias/definition/address-statistics.transfer.yml @@ -0,0 +1,5 @@ +AddressStatistics: + addressBookUuid: + type: "00Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Success\\AddressTransfer as AliasAddress" + addressUuid: + type: string diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace-with-alias/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace-with-alias/generator.config.yml new file mode 100644 index 0000000..f7777e5 --- /dev/null +++ b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace-with-alias/generator.config.yml @@ -0,0 +1,4 @@ +generator: + transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" + transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" + definitionPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace-with-alias/definition" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace/definition/address-statistics.transfer.yml b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace/definition/address-statistics.transfer.yml new file mode 100644 index 0000000..f14b47f --- /dev/null +++ b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace/definition/address-statistics.transfer.yml @@ -0,0 +1,5 @@ +AddressStatistics: + addressBookUuid: + type: "00Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Success\\AddressTransfer" + addressUuid: + type: string diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace/generator.config.yml new file mode 100644 index 0000000..c5dddb1 --- /dev/null +++ b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace/generator.config.yml @@ -0,0 +1,4 @@ +generator: + transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" + transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" + definitionPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace/definition" diff --git a/tests/unit/DefinitionGenerator/Builder/DefinitionBuilderTraitInterface.php b/tests/unit/DefinitionGenerator/Builder/DefinitionBuilderInterface.php similarity index 87% rename from tests/unit/DefinitionGenerator/Builder/DefinitionBuilderTraitInterface.php rename to tests/unit/DefinitionGenerator/Builder/DefinitionBuilderInterface.php index 6bee5ff..5e99896 100644 --- a/tests/unit/DefinitionGenerator/Builder/DefinitionBuilderTraitInterface.php +++ b/tests/unit/DefinitionGenerator/Builder/DefinitionBuilderInterface.php @@ -6,7 +6,7 @@ use Picamator\TransferObject\DefinitionGenerator\Builder\BuilderContentInterface; -interface DefinitionBuilderTraitInterface +interface DefinitionBuilderInterface { public function createBuilderContent(string $propertyName, mixed $propertyValue): BuilderContentInterface; } diff --git a/tests/unit/DefinitionGenerator/Builder/DefinitionBuilderTraitTest.php b/tests/unit/DefinitionGenerator/Builder/DefinitionBuilderTest.php similarity index 72% rename from tests/unit/DefinitionGenerator/Builder/DefinitionBuilderTraitTest.php rename to tests/unit/DefinitionGenerator/Builder/DefinitionBuilderTest.php index 59f9e08..cf84330 100644 --- a/tests/unit/DefinitionGenerator/Builder/DefinitionBuilderTraitTest.php +++ b/tests/unit/DefinitionGenerator/Builder/DefinitionBuilderTest.php @@ -8,13 +8,13 @@ use Picamator\TransferObject\DefinitionGenerator\Builder\DefinitionBuilderTrait; use Picamator\TransferObject\DefinitionGenerator\Exception\DefinitionGeneratorException; -class DefinitionBuilderTraitTest extends TestCase +class DefinitionBuilderTest extends TestCase { - private DefinitionBuilderTraitInterface $builderTrait; + private DefinitionBuilderInterface $builder; protected function setUp(): void { - $this->builderTrait = new class () implements DefinitionBuilderTraitInterface + $this->builder = new class () implements DefinitionBuilderInterface { use DefinitionBuilderTrait { createBuilderContent as public; @@ -31,6 +31,6 @@ public function testUnsupportedTypeShouldThrowException(): void $this->expectException(DefinitionGeneratorException::class); // Act - $this->builderTrait->createBuilderContent($propertyName, $propertyValue); + $this->builder->createBuilderContent($propertyName, $propertyValue); } } diff --git a/tests/unit/TransferGenerator/Generator/Generator/BulkTransferGeneratorTest.php b/tests/unit/TransferGenerator/Generator/Generator/ServiceTransferGeneratorTest.php similarity index 97% rename from tests/unit/TransferGenerator/Generator/Generator/BulkTransferGeneratorTest.php rename to tests/unit/TransferGenerator/Generator/Generator/ServiceTransferGeneratorTest.php index 4367f6a..4e8786e 100644 --- a/tests/unit/TransferGenerator/Generator/Generator/BulkTransferGeneratorTest.php +++ b/tests/unit/TransferGenerator/Generator/Generator/ServiceTransferGeneratorTest.php @@ -13,7 +13,7 @@ use Picamator\TransferObject\TransferGenerator\Generator\Generator\TransferGeneratorServiceInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\TransferGeneratorInterface; -class BulkTransferGeneratorTest extends TestCase +class ServiceTransferGeneratorTest extends TestCase { private TransferGeneratorServiceInterface $bulkGenerator; diff --git a/tests/unit/TransferGenerator/Generator/Render/Template/TemplateHelperTest.php b/tests/unit/TransferGenerator/Generator/Render/Template/TemplateHelperTest.php new file mode 100644 index 0000000..26b25e1 --- /dev/null +++ b/tests/unit/TransferGenerator/Generator/Render/Template/TemplateHelperTest.php @@ -0,0 +1,89 @@ + $templateData + */ + #[DataProvider('getNullableDataProvider')] + public function testGetNullable(array $templateData, string $property, string $expected): void + { + // Arrange + $templateTransfer = new TemplateTransfer()->fromArray($templateData); + $templateHelper = new TemplateHelper($templateTransfer); + + // Act + $actual = $templateHelper->getNullable($property); + + // Assert + $this->assertSame($expected, $actual); + } + + /** + * @return Generator + */ + public static function getNullableDataProvider(): Generator + { + yield 'property is not nullable should return empty string' => [ + 'templateData' => [ + TemplateTransfer::PROPERTIES => [ + 'test' => 'TestTransfer', + ], + TemplateTransfer::NULLABLES => [ + 'test' => false, + ], + ], + 'property' => 'test', + 'expected' => '', + ]; + + yield 'property is nullable but with intersection type should return empty string' => [ + 'templateData' => [ + TemplateTransfer::PROPERTIES => [ + 'test' => 'TestTransfer&TTransferInterface', + ], + TemplateTransfer::NULLABLES => [ + 'test' => true, + ], + ], + 'property' => 'test', + 'expected' => '', + ]; + + yield 'property is nullable with union should return union null' => [ + 'templateData' => [ + TemplateTransfer::PROPERTIES => [ + 'test' => 'TestTransfer|TransferInterface', + ], + TemplateTransfer::NULLABLES => [ + 'test' => true, + ], + ], + 'property' => 'test', + 'expected' => 'null|', + ]; + + yield 'property is nullable without intersection or union should return null type' => [ + 'templateData' => [ + TemplateTransfer::PROPERTIES => [ + 'test' => 'TestTransfer', + ], + TemplateTransfer::NULLABLES => [ + 'test' => true, + ], + ], + 'property' => 'test', + 'expected' => '?', + ]; + } +} diff --git a/tests/unit/TransferGenerator/Validator/NamespaceValidatorInterface.php b/tests/unit/TransferGenerator/Validator/NamespaceValidatorInterface.php new file mode 100644 index 0000000..a7d940d --- /dev/null +++ b/tests/unit/TransferGenerator/Validator/NamespaceValidatorInterface.php @@ -0,0 +1,10 @@ +validator = new class () implements NamespaceValidatorInterface + { + use NamespaceValidatorTrait { + isValidNamespace as public; + } + }; + } + + #[DataProvider('validNamespaceDataProvider')] + public function testValidNamespaceShouldReturnTrue(string $namespace): void + { + // Act + $actual = $this->validator->isValidNamespace($namespace); + + // Assert + $this->assertTrue($actual); + } + + /** + * @return Generator + */ + public static function validNamespaceDataProvider(): Generator + { + yield 'many sections namespace' => [ + 'Picamator\Tests\Unit\TransferObject\TransferGenerator\Validator', + ]; + + yield 'many sections namespace with first uppercase chars' => [ + 'PHPUnit\Framework\TestCase', + ]; + + yield 'two section namespace' => [ + 'Picamator\Tests', + ]; + + yield 'one section namespace' => [ + 'Picamator', + ]; + } +}