diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 3aa6af7..7ddb923 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -35,13 +35,19 @@ return (new PhpCsFixer\Config()) ->setRules([ '@Symfony' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, // [Symfony] defaults to `camelCase`, we set it to `snake_case` (phpspec style) 'php_unit_method_casing' => ['case' => 'snake_case'], + // [Symfony] defaults to `['arrays']`, we add `arguments` and `parameters` (PHP 8.0) + 'trailing_comma_in_multiline' => ['elements' => ['arrays', 'arguments', 'parameters']], + // [Symfony] defaults to `true`, we set it to `false` for phpspec 'visibility_required' => false, ]) + ->setRiskyAllowed(true) ->setUsingCache(true) ->setFinder($finder) ; diff --git a/CHANGELOG.md b/CHANGELOG.md index ccfab59..1ca5ab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,26 @@ # CHANGELOG +## 4.0.0: PHP 8.0 requirement + +Dropped support for PHP <8.0 + +New features: + +* added `Attribute` model (PHP 8.0 attributes) +* added attributes support to `Objekt`, `Contract`, `Method`, `Property`, `Argument` and `Constant` +* added union type support in `Type` (e.g. `string|int`, `DateTime|null`) +* added `mixed` type hint support in `Type` +* added support for class property types +* added constructor property promotion support (visibility on `Argument`) +* added support for nullable typehints + ## 3.0.2: Dockerised dev environment * setup Github Actions * changed tooling from scripts to Makefile -* installed phpstan as a dev depdendency -* installed swiss-knife as a dev depdendency -* installed rector as a dev depdendency +* installed phpstan as a dev dependency +* installed swiss-knife as a dev dependency +* installed rector as a dev dependency * upgraded PHP CS fixer to v2.19.3 * dockerized for local development @@ -41,7 +55,7 @@ Normalization from float to double, thanks to @ItsKelsBoys Added support for PHP 7.2, thanks to @roukmoute -BC break: Object has be renamed to Objekt, has it is a reserved keyword. +BC break: Object has been renamed to Objekt, has it is a reserved keyword. ## 2.0.0: PHP 7 and Return type hints diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f400a0c..ab266ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ make lib-init ## Standard code -Use [PHP CS fixer](http://cs.sensiolabs.org/) to make your code compliant with +Use [PHP CS Fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer) to make your code compliant with Memio's coding standards: ```console @@ -87,7 +87,7 @@ To keep your fork up-to-date, you should track the upstream (original) one using the following command: ```console -$ git remote add upstream https://github.com/memio/model.git +git remote add upstream https://github.com/memio/model.git ``` Then get the upstream changes: @@ -103,7 +103,7 @@ git rebase main Finally, publish your changes: ```console -$ git push -f origin +git push -f origin ``` Your pull request will be automatically updated. diff --git a/README.md b/README.md index 0472105..0a9b60e 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,21 @@ method arguments and even PHPdoc) by constructing "Model" objects. Install it using [Composer](https://getcomposer.org/download): - composer require memio/model:^3.0 +```console +composer require memio/model:^4.0 +``` ## Example -Let's say we want to describe the following method: +Let's say we want to describe the following constructor: ```php - /** - * @api - */ - public function doSomething(ValueObject $valueObject, int $type = self::TYPE_ONE, bool $option = true); + public function __construct( + private ValueObject $valueObject, + private string|int $type = self::TYPE_ONE, + private ?bool $option = true, + ) { + } ``` In order to do so, we'd need to write the following: @@ -32,56 +36,53 @@ require __DIR__.'/vendor/autoload.php'; use Memio\Model\Argument; use Memio\Model\Method; -use Memio\Model\Phpdoc\ApiTag; -use Memio\Model\Phpdoc\MethodPhpdoc; -use Memio\Model\Phpdoc\ParameterTag; - -$method = (new Method('doSomething')) - ->setPhpdoc((new MethodPhpdoc()) - ->addParameterTag(new ParameterTag('Vendor\Project\ValueObject', 'valueObject')) - ->addParameterTag(new ParameterTag('int', 'type')) - ->addParameterTag(new ParameterTag('bool', 'option')) - ->addApiTag(new ApiTag()) +$method = (new Method('__construct')) + ->addArgument((new Argument('Vendor\Project\ValueObject', 'valueObject')) + ->makePrivate() ) - ->addArgument(new Argument('Vendor\Project\ValueObject', 'valueObject')) - ->addArgument((new Argument('int', 'type')) + ->addArgument((new Argument('string|int', 'type')) + ->makePrivate() ->setDefaultValue('self::TYPE_ONE') ) - ->addArgument((new Argument('bool', 'option')) + ->addArgument((new Argument('?bool', 'option')) + ->makePrivate() ->setDefaultValue('true') ) ; ``` +This example showcases constructor property promotion, union types and nullable types. + Usually models aren't described manually like this, they would be built dynamically: ```php -// Let's say we've received the following two parameters: -$methodName = 'doSomething'; -$arguments = [new \Vendor\Project\ValueObject(), ValueObject::TYPE_ONE, true]; - -$method = new Method($methodName); -$phpdoc = (new MethodPhpdoc())->setApiTag(new ApiTag()); -$index = 1; -foreach ($arguments as $rawArgument) { - $type = is_object($rawArgument) ? get_class($argument) : gettype($rawArgument); - $name = 'argument'.$index++; - $argument = new Argument($type, $name); - +// Let's say we've received the following parameters: +$parameters = [ + ['type' => 'Vendor\Project\ValueObject', 'name' => 'valueObject'], + ['type' => 'string|int', 'name' => 'type', 'default' => 'self::TYPE_ONE'], + ['type' => '?bool', 'name' => 'option', 'default' => 'true'], +]; + +$method = new Method('__construct'); +foreach ($parameters as $parameter) { + $argument = (new Argument($parameter['type'], $parameter['name'])) + ->makePrivate() + ; + if (isset($parameter['default'])) { + $argument->setDefaultValue($parameter['default']); + } $method->addArgument($argument); - $phpdoc->addParameterTag(new ParameterTag($type, $name)); } -$method->setPhpdoc($phpdoc); ``` We can build dynamically the models using a configuration file, user input, existing -source code... Possibilities are endless! +source code, etc. Possibilities are endless! -Once built these models can be further tweaked, and converted to another format: -an array, source code, etc... Again, the possibilities are endless! +Once built, these models can be further tweaked and converted to another format: +an array, source code, etc. -Have a look at [the main respository](http://github.com/memio/memio) to discover the full power of Medio. +Have a look at [the main repository](http://github.com/memio/memio) to discover the full power of Memio. ## Want to know more? @@ -98,7 +99,7 @@ make phpspec arg='--format pretty' # Run the specifications You can see the current and past versions using one of the following: * the `git tag` command -* the [releases page on Github](https://github.com/memio/memio/releases) +* the [releases page on Github](https://github.com/memio/model/releases) * the file listing the [changes between versions](CHANGELOG.md) And finally some meta documentation: @@ -113,4 +114,3 @@ And finally some meta documentation: * extract `Import` (use statement) from `FullyQualifiedName` * get rid of `FullyQualifiedName` * support more PHPdoc stuff -* support annotations diff --git a/composer.json b/composer.json index 5965ac5..dff7247 100644 --- a/composer.json +++ b/composer.json @@ -17,11 +17,11 @@ "Memio\\Model\\": "src/Memio/Model" }}, "require": { - "php": "^7.2 || ^8.0" + "php": "^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.19.3", - "phpspec/phpspec": "^6.1 || ^7.0", + "phpspec/phpspec": "^7.0", "phpstan/phpstan": "1.12.32", "rector/rector": "^1.2.10", "rector/swiss-knife": "^2.3" diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 7319f18..365106a 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -3,6 +3,6 @@ includes: parameters: level: 0 - phpVersion: 70200 + phpVersion: 80000 paths: - src/ diff --git a/rector.php b/rector.php index f2db8c0..5649d8f 100644 --- a/rector.php +++ b/rector.php @@ -9,7 +9,7 @@ return RectorConfig::configure() ->withCache( '/tmp/rector', - FileCacheStorage::class + FileCacheStorage::class, ) ->withPaths([ __DIR__, @@ -23,7 +23,7 @@ ]) ->withSets([ // —— PHP —————————————————————————————————————————————————————————————— - SetList::PHP_72, + SetList::PHP_80, ]) ->withRules([ ]); diff --git a/spec/Memio/Model/ArgumentSpec.php b/spec/Memio/Model/ArgumentSpec.php index 918e545..5476054 100644 --- a/spec/Memio/Model/ArgumentSpec.php +++ b/spec/Memio/Model/ArgumentSpec.php @@ -1,5 +1,7 @@ beConstructedWith('array', 'lines'); } - function it_has_a_type() + function it_has_a_type(): void { $this->type->name->shouldBe('array'); } - function it_has_a_name() + function it_has_a_name(): void { $this->name->shouldBe('lines'); } - function it_can_have_default_value() + function it_can_have_default_value(): void { $this->defaultValue->shouldBe(null); @@ -41,7 +44,7 @@ function it_can_have_default_value() $this->defaultValue->shouldBe(null); } - function it_can_be_variadic() + function it_can_be_variadic(): void { $this->isVariadic()->shouldBe(false); @@ -51,4 +54,30 @@ function it_can_be_variadic() $this->removeVariadic(); $this->isVariadic()->shouldBe(false); } + + function it_can_have_attributes(Attribute $attribute): void + { + $this->attributes->shouldBe([]); + $this->addAttribute($attribute); + $this->attributes->shouldBe([$attribute]); + $this->removeAttributes(); + $this->attributes->shouldBe([]); + } + + function it_can_have_visibility(): void + { + $this->visibility->shouldBe(''); + + $this->makePublic(); + $this->visibility->shouldBe('public'); + + $this->makeProtected(); + $this->visibility->shouldBe('protected'); + + $this->makePrivate(); + $this->visibility->shouldBe('private'); + + $this->removeVisibility(); + $this->visibility->shouldBe(''); + } } diff --git a/spec/Memio/Model/AttributeSpec.php b/spec/Memio/Model/AttributeSpec.php new file mode 100644 index 0000000..e6adead --- /dev/null +++ b/spec/Memio/Model/AttributeSpec.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace spec\Memio\Model; + +use PhpSpec\ObjectBehavior; + +class AttributeSpec extends ObjectBehavior +{ + const NAME = 'Route'; + + function let(): void + { + $this->beConstructedWith(self::NAME); + } + + function it_has_a_name(): void + { + $this->name->shouldBe(self::NAME); + } + + function it_can_have_arguments(): void + { + $this->arguments->shouldBe(null); + + $this->setArguments("'/api'"); + $this->arguments->shouldBe("'/api'"); + } +} diff --git a/spec/Memio/Model/ConstantSpec.php b/spec/Memio/Model/ConstantSpec.php index fc81af4..d03f2d0 100644 --- a/spec/Memio/Model/ConstantSpec.php +++ b/spec/Memio/Model/ConstantSpec.php @@ -1,5 +1,7 @@ beConstructedWith(self::NAME, self::VALUE); } - function it_has_a_name() + function it_has_a_name(): void { $this->name->shouldBe(self::NAME); } - function it_has_a_value() + function it_has_a_value(): void { $this->value->shouldBe(self::VALUE); } + + function it_can_have_attributes(Attribute $attribute): void + { + $this->attributes->shouldBe([]); + $this->addAttribute($attribute); + $this->attributes->shouldBe([$attribute]); + $this->removeAttributes(); + $this->attributes->shouldBe([]); + } } diff --git a/spec/Memio/Model/ContractSpec.php b/spec/Memio/Model/ContractSpec.php index 85f8b96..277549a 100644 --- a/spec/Memio/Model/ContractSpec.php +++ b/spec/Memio/Model/ContractSpec.php @@ -1,5 +1,7 @@ beConstructedWith(self::FULLY_QUALIFIED_NAME); } - function it_is_a_structure() + function it_is_a_structure(): void { $this->shouldImplement('Memio\Model\Structure'); } - function it_has_a_fully_qualified_name() + function it_has_a_fully_qualified_name(): void { $this->fullyQualifiedName->fullyQualifiedName->shouldBe(self::FULLY_QUALIFIED_NAME); } - function it_has_a_name() + function it_has_a_name(): void { $this->getName()->shouldBe(self::NAME); } - function it_has_a_namespace() + function it_has_a_namespace(): void { $this->getNamespace()->shouldBe(self::NAMESPACE_); } - function it_can_have_phpdoc(StructurePhpdoc $phpdoc) + function it_can_have_phpdoc(StructurePhpdoc $phpdoc): void { $this->structurePhpdoc->shouldBe(null); $this->setPhpdoc($phpdoc); $this->structurePhpdoc->shouldBe($phpdoc); } - function it_can_extend_contracts(Contract $contract) + function it_can_extend_contracts(Contract $contract): void { $this->contracts->shouldBe([]); $this->extend($contract); $this->contracts->shouldBe([$contract]); } - function it_can_have_constants(Constant $constant) + function it_can_have_constants(Constant $constant): void { $this->constants->shouldBe([]); $this->addConstant($constant); $this->constants->shouldBe([$constant]); } - function it_can_have_methods(Method $method) + function it_can_have_methods(Method $method): void { $this->methods->shouldBe([]); $this->addMethod($method); $this->methods->shouldBe([$method]); } + + function it_can_have_attributes(Attribute $attribute): void + { + $this->attributes->shouldBe([]); + $this->addAttribute($attribute); + $this->attributes->shouldBe([$attribute]); + $this->removeAttributes(); + $this->attributes->shouldBe([]); + } } diff --git a/spec/Memio/Model/FileSpec.php b/spec/Memio/Model/FileSpec.php index 3f8fc00..006031b 100644 --- a/spec/Memio/Model/FileSpec.php +++ b/spec/Memio/Model/FileSpec.php @@ -1,5 +1,7 @@ beConstructedWith(self::FILENAME); } - function it_has_a_filename() + function it_has_a_filename(): void { $this->filename->shouldBe(self::FILENAME); } - function it_can_have_license_phpdoc(LicensePhpdoc $licensePhpdoc) + function it_can_have_license_phpdoc(LicensePhpdoc $licensePhpdoc): void { $this->licensePhpdoc->shouldBe(null); @@ -43,7 +45,7 @@ function it_can_have_license_phpdoc(LicensePhpdoc $licensePhpdoc) $this->licensePhpdoc->shouldBe(null); } - function it_has_a_namespace(Structure $structure) + function it_has_a_namespace(Structure $structure): void { $structure->getNamespace()->willReturn(self::NAMESPACE_); @@ -52,14 +54,14 @@ function it_has_a_namespace(Structure $structure) $this->structure->getNamespace()->shouldBe(self::NAMESPACE_); } - function it_can_have_fully_qualified_names(FullyQualifiedName $fullyQualifiedName) + function it_can_have_fully_qualified_names(FullyQualifiedName $fullyQualifiedName): void { $this->fullyQualifiedNames->shouldBe([]); $this->addFullyQualifiedName($fullyQualifiedName); $this->fullyQualifiedNames->shouldBe([$fullyQualifiedName]); } - function it_has_a_structure(Structure $structure) + function it_has_a_structure(Structure $structure): void { $this->setStructure($structure); diff --git a/spec/Memio/Model/FullyQualifiedNameSpec.php b/spec/Memio/Model/FullyQualifiedNameSpec.php index 4950f79..9201855 100644 --- a/spec/Memio/Model/FullyQualifiedNameSpec.php +++ b/spec/Memio/Model/FullyQualifiedNameSpec.php @@ -1,5 +1,7 @@ beConstructedWith('Vendor\Project\MyClass'); } - function it_has_fully_qualified_classname() + function it_has_fully_qualified_classname(): void { $this->fullyQualifiedName->shouldBe('Vendor\Project\MyClass'); } - function it_has_name() + function it_has_name(): void { $this->getName()->shouldBe('MyClass'); } - function it_has_namespace() + function it_has_namespace(): void { $this->namespace->shouldBe('Vendor\Project'); } - function it_can_have_an_alias() + function it_can_have_an_alias(): void { $this->hasAlias()->shouldBe(false); $this->getName()->shouldBe('MyClass'); @@ -49,7 +51,7 @@ function it_can_have_an_alias() $this->getName()->shouldBe('MyClass'); } - function it_normalizes_float_name() + function it_normalizes_float_name(): void { $this->beConstructedWith('float'); diff --git a/spec/Memio/Model/MethodSpec.php b/spec/Memio/Model/MethodSpec.php index 6b26fc2..a60e7a8 100644 --- a/spec/Memio/Model/MethodSpec.php +++ b/spec/Memio/Model/MethodSpec.php @@ -1,5 +1,7 @@ beConstructedWith(self::NAME); } - function it_has_a_name() + function it_has_a_name(): void { $this->name->shouldBe(self::NAME); } - function it_can_have_phpdoc(MethodPhpdoc $phpdoc) + function it_can_have_phpdoc(MethodPhpdoc $phpdoc): void { $this->methodPhpdoc->shouldBe(null); $this->setPhpdoc($phpdoc); $this->methodPhpdoc->shouldBe($phpdoc); } - function it_can_be_abstract() + function it_can_be_abstract(): void { $this->isAbstract->shouldBe(false); @@ -47,7 +50,7 @@ function it_can_be_abstract() $this->isAbstract->shouldBe(false); } - function it_can_be_final() + function it_can_be_final(): void { $this->isFinal->shouldBe(false); @@ -58,7 +61,7 @@ function it_can_be_final() $this->isFinal->shouldBe(false); } - function it_can_have_visibility() + function it_can_have_visibility(): void { $this->visibility->shouldBe('public'); @@ -75,7 +78,7 @@ function it_can_have_visibility() $this->visibility->shouldBe('public'); } - function it_can_have_staticness() + function it_can_have_staticness(): void { $this->isStatic->shouldBe(false); @@ -86,26 +89,35 @@ function it_can_have_staticness() $this->isStatic->shouldBe(false); } - function it_can_have_arguments(Argument $argument) + function it_can_have_arguments(Argument $argument): void { $this->arguments->shouldBe([]); $this->addArgument($argument); $this->arguments->shouldBe([$argument]); } - function it_can_have_a_return_type() + function it_can_have_a_return_type(): void { $this->returnType->shouldBe(null); $this->setReturnType('array'); $this->returnType->shouldBe('array'); } - function it_can_have_a_body() + function it_can_have_a_body(): void { $body = <<<'EOT' - $length = strlen('Nobody expects the spanish inquisition'); -EOT; + $length = strlen('Nobody expects the spanish inquisition'); + EOT; $this->setBody($body); $this->body->shouldBe($body); } + + function it_can_have_attributes(Attribute $attribute): void + { + $this->attributes->shouldBe([]); + $this->addAttribute($attribute); + $this->attributes->shouldBe([$attribute]); + $this->removeAttributes(); + $this->attributes->shouldBe([]); + } } diff --git a/spec/Memio/Model/ObjektSpec.php b/spec/Memio/Model/ObjektSpec.php index 42a8b32..17aa5c5 100644 --- a/spec/Memio/Model/ObjektSpec.php +++ b/spec/Memio/Model/ObjektSpec.php @@ -1,5 +1,7 @@ beConstructedWith(self::FULLY_QUALIFIED_NAME); } - function it_is_a_structure() + function it_is_a_structure(): void { $this->shouldImplement('Memio\Model\Structure'); } - function it_has_a_fully_qualified_name() + function it_has_a_fully_qualified_name(): void { $this->getFullyQualifiedName()->fullyQualifiedName->shouldBe(self::FULLY_QUALIFIED_NAME); } - function it_has_a_name() + function it_has_a_name(): void { $this->getName()->shouldBe(self::NAME); } - function it_has_a_namespace() + function it_has_a_namespace(): void { $this->getNamespace()->shouldBe(self::NAMESPACE_); } - function it_can_have_phpdoc(StructurePhpdoc $phpdoc) + function it_can_have_phpdoc(StructurePhpdoc $phpdoc): void { $this->structurePhpdoc->shouldBe(null); $this->setPhpdoc($phpdoc); $this->structurePhpdoc->shouldBe($phpdoc); } - function it_can_be_abstract() + function it_can_be_abstract(): void { $this->isAbstract->shouldBe(false); $this->makeAbstract(); @@ -66,7 +69,7 @@ function it_can_be_abstract() $this->isAbstract->shouldBe(false); } - function it_can_be_final() + function it_can_be_final(): void { $this->isFinal->shouldBe(false); $this->makeFinal(); @@ -75,7 +78,7 @@ function it_can_be_final() $this->isFinal->shouldBe(false); } - function it_can_have_a_parent(Objekt $parent) + function it_can_have_a_parent(Objekt $parent): void { $this->hasParent()->shouldBe(false); $this->parent->shouldBe(null); @@ -89,31 +92,40 @@ function it_can_have_a_parent(Objekt $parent) $this->parent->shouldBe(null); } - function it_can_implement_contracts(Contract $contract) + function it_can_implement_contracts(Contract $contract): void { $this->contracts->shouldBe([]); $this->implement($contract); $this->contracts->shouldBe([$contract]); } - function it_can_have_constants(Constant $constant) + function it_can_have_constants(Constant $constant): void { $this->constants->shouldBe([]); $this->addConstant($constant); $this->constants->shouldBe([$constant]); } - function it_can_have_properties(Property $property) + function it_can_have_properties(Property $property): void { $this->properties->shouldBe([]); $this->addProperty($property); $this->properties->shouldBe([$property]); } - function it_can_have_methods(Method $method) + function it_can_have_methods(Method $method): void { $this->methods->shouldBe([]); $this->addMethod($method); $this->methods->shouldBe([$method]); } + + function it_can_have_attributes(Attribute $attribute): void + { + $this->attributes->shouldBe([]); + $this->addAttribute($attribute); + $this->attributes->shouldBe([$attribute]); + $this->removeAttributes(); + $this->attributes->shouldBe([]); + } } diff --git a/spec/Memio/Model/Phpdoc/ApiTagSpec.php b/spec/Memio/Model/Phpdoc/ApiTagSpec.php index f41143a..c7948c3 100644 --- a/spec/Memio/Model/Phpdoc/ApiTagSpec.php +++ b/spec/Memio/Model/Phpdoc/ApiTagSpec.php @@ -1,5 +1,7 @@ beConstructedWith('v2.1'); diff --git a/spec/Memio/Model/Phpdoc/DeprecationTagSpec.php b/spec/Memio/Model/Phpdoc/DeprecationTagSpec.php index e5975b1..b12544f 100644 --- a/spec/Memio/Model/Phpdoc/DeprecationTagSpec.php +++ b/spec/Memio/Model/Phpdoc/DeprecationTagSpec.php @@ -1,5 +1,7 @@ version->shouldBe(null); $this->description->shouldBe(null); } - function it_can_have_a_version() + function it_can_have_a_version(): void { $this->beConstructedWith('v2.1'); @@ -29,7 +31,7 @@ function it_can_have_a_version() $this->description->shouldBe(null); } - function it_can_have_a_description() + function it_can_have_a_description(): void { $this->beConstructedWith('v2.1', 'Use Objekt#myMethod instead'); diff --git a/spec/Memio/Model/Phpdoc/DescriptionSpec.php b/spec/Memio/Model/Phpdoc/DescriptionSpec.php index b9c73e0..ee7dd00 100644 --- a/spec/Memio/Model/Phpdoc/DescriptionSpec.php +++ b/spec/Memio/Model/Phpdoc/DescriptionSpec.php @@ -1,5 +1,7 @@ beConstructedWith(self::SHORT_DESCRIPTION); } - function it_has_a_short_description() + function it_has_a_short_description(): void { $this->lines->shouldBe([self::SHORT_DESCRIPTION]); } - function it_can_have_empty_lines() + function it_can_have_empty_lines(): void { $this->addEmptyLine(); $this->lines->shouldBe([self::SHORT_DESCRIPTION, '']); } - function it_can_have_long_description() + function it_can_have_long_description(): void { $longDescription = [ 'Long descriptions can span on many lines', @@ -48,7 +50,7 @@ function it_can_have_long_description() $this->lines->shouldBe(array_merge( [self::SHORT_DESCRIPTION], - $longDescription + $longDescription, )); } } diff --git a/spec/Memio/Model/Phpdoc/LicensePhpdocSpec.php b/spec/Memio/Model/Phpdoc/LicensePhpdocSpec.php index 74b298e..7fdab77 100644 --- a/spec/Memio/Model/Phpdoc/LicensePhpdocSpec.php +++ b/spec/Memio/Model/Phpdoc/LicensePhpdocSpec.php @@ -1,5 +1,7 @@ beConstructedWith(self::PROJECT_NAME, self::AUTHOR_NAME, self::AUTHOR_EMAIL); } - function it_has_project_name() + function it_has_project_name(): void { $this->projectName->shouldBe(self::PROJECT_NAME); } - function it_has_author_name() + function it_has_author_name(): void { $this->authorName->shouldBe(self::AUTHOR_NAME); } - function it_has_author_email() + function it_has_author_email(): void { $this->authorEmail->shouldBe(self::AUTHOR_EMAIL); } diff --git a/spec/Memio/Model/Phpdoc/MethodPhpdocSpec.php b/spec/Memio/Model/Phpdoc/MethodPhpdocSpec.php index eb3ad43..e13cc32 100644 --- a/spec/Memio/Model/Phpdoc/MethodPhpdocSpec.php +++ b/spec/Memio/Model/Phpdoc/MethodPhpdocSpec.php @@ -1,5 +1,7 @@ isEmpty()->shouldBe(true); } - function it_can_have_description(Description $description) + function it_can_have_description(Description $description): void { $this->setDescription($description); $this->description->shouldBe($description); $this->isEmpty(false); } - function it_can_have_parameters(ParameterTag $parameterTag) + function it_can_have_parameters(ParameterTag $parameterTag): void { $this->addParameterTag($parameterTag); $this->parameterTags->shouldBe([$parameterTag]); $this->isEmpty(false); } - function it_can_be_tagged_as_api(ApiTag $apiTag) + function it_can_be_tagged_as_api(ApiTag $apiTag): void { $this->setApiTag($apiTag); $this->apiTag->shouldBe($apiTag); $this->isEmpty()->shouldBe(false); } - function it_can_be_tagged_as_deprecated(DeprecationTag $deprecationTag) + function it_can_be_tagged_as_deprecated(DeprecationTag $deprecationTag): void { $this->setDeprecationTag($deprecationTag); $this->deprecationTag->shouldBe($deprecationTag); diff --git a/spec/Memio/Model/Phpdoc/ParameterTagSpec.php b/spec/Memio/Model/Phpdoc/ParameterTagSpec.php index 3957f55..0c69c30 100644 --- a/spec/Memio/Model/Phpdoc/ParameterTagSpec.php +++ b/spec/Memio/Model/Phpdoc/ParameterTagSpec.php @@ -1,5 +1,7 @@ beConstructedWith('Vendor\Project\MyClass', 'myClass'); @@ -23,7 +25,7 @@ function it_has_a_type_and_a_name() $this->name->shouldBe('myClass'); } - function it_can_have_a_description() + function it_can_have_a_description(): void { $this->beConstructedWith('Vendor\Project\MyClass', 'myClass', 'description'); diff --git a/spec/Memio/Model/Phpdoc/PropertyPhpdocSpec.php b/spec/Memio/Model/Phpdoc/PropertyPhpdocSpec.php index 3c47dd1..0c9d960 100644 --- a/spec/Memio/Model/Phpdoc/PropertyPhpdocSpec.php +++ b/spec/Memio/Model/Phpdoc/PropertyPhpdocSpec.php @@ -1,5 +1,7 @@ isEmpty()->shouldBe(true); } - function it_can_have_a_property_tag(VariableTag $variableTag) + function it_can_have_a_property_tag(VariableTag $variableTag): void { $this->setVariableTag($variableTag); $this->variableTag->shouldBe($variableTag); diff --git a/spec/Memio/Model/Phpdoc/StructurePhpdocSpec.php b/spec/Memio/Model/Phpdoc/StructurePhpdocSpec.php index c37f4b3..d430c5f 100644 --- a/spec/Memio/Model/Phpdoc/StructurePhpdocSpec.php +++ b/spec/Memio/Model/Phpdoc/StructurePhpdocSpec.php @@ -1,5 +1,7 @@ isEmpty()->shouldBe(true); } - function it_can_have_description(Description $description) + function it_can_have_description(Description $description): void { $this->setDescription($description); $this->description->shouldBe($description); $this->isEmpty(false); } - function it_can_be_tagged_as_api(ApiTag $apiTag) + function it_can_be_tagged_as_api(ApiTag $apiTag): void { $this->setApiTag($apiTag); $this->apiTag->shouldBe($apiTag); $this->isEmpty()->shouldBe(false); } - function it_can_be_tagged_as_deprecated(DeprecationTag $deprecationTag) + function it_can_be_tagged_as_deprecated(DeprecationTag $deprecationTag): void { $this->setDeprecationTag($deprecationTag); $this->deprecationTag->shouldBe($deprecationTag); diff --git a/spec/Memio/Model/Phpdoc/VariableTagSpec.php b/spec/Memio/Model/Phpdoc/VariableTagSpec.php index 00862eb..0282af7 100644 --- a/spec/Memio/Model/Phpdoc/VariableTagSpec.php +++ b/spec/Memio/Model/Phpdoc/VariableTagSpec.php @@ -1,5 +1,7 @@ beConstructedWith('string'); $this->type->name->shouldBe('string'); } - function it_can_have_a_fully_qualified_name() + function it_can_have_a_fully_qualified_name(): void { $this->beConstructedWith('Vendor\Project\MyClass'); diff --git a/spec/Memio/Model/PropertySpec.php b/spec/Memio/Model/PropertySpec.php index be4dbbd..9763752 100644 --- a/spec/Memio/Model/PropertySpec.php +++ b/spec/Memio/Model/PropertySpec.php @@ -1,5 +1,7 @@ beConstructedWith(self::NAME); } - function it_has_a_name() + function it_has_a_name(): void { $this->name->shouldBe(self::NAME); } - function it_can_have_phpdoc(PropertyPhpdoc $phpdoc) + function it_can_have_phpdoc(PropertyPhpdoc $phpdoc): void { $this->propertyPhpdoc->shouldBe(null); $this->setPhpdoc($phpdoc); $this->propertyPhpdoc->shouldBe($phpdoc); } - function it_has_visibility() + function it_has_visibility(): void { $this->visibility->shouldBe('private'); @@ -49,7 +52,7 @@ function it_has_visibility() $this->visibility->shouldBe('private'); } - function it_can_have_staticness() + function it_can_have_staticness(): void { $this->isStatic->shouldBe(false); @@ -60,10 +63,30 @@ function it_can_have_staticness() $this->isStatic->shouldBe(false); } - function it_can_have_a_default_value() + function it_can_have_a_type(): void + { + $this->type->shouldBe(null); + + $this->setType('int'); + $this->type->name->shouldBe('int'); + + $this->removeType(); + $this->type->shouldBe(null); + } + + function it_can_have_a_default_value(): void { $this->defaultValue->shouldBe(null); $this->setDefaultValue('null'); $this->defaultValue->shouldBe('null'); } + + function it_can_have_attributes(Attribute $attribute): void + { + $this->attributes->shouldBe([]); + $this->addAttribute($attribute); + $this->attributes->shouldBe([$attribute]); + $this->removeAttributes(); + $this->attributes->shouldBe([]); + } } diff --git a/spec/Memio/Model/TypeSpec.php b/spec/Memio/Model/TypeSpec.php index 36aa16a..4fa858c 100644 --- a/spec/Memio/Model/TypeSpec.php +++ b/spec/Memio/Model/TypeSpec.php @@ -1,5 +1,7 @@ beConstructedWith('Vendor\Project\MyClass'); $this->getName()->shouldBe('Vendor\Project\MyClass'); $this->isObject()->shouldBe(true); + $this->isNullable()->shouldBe(false); } - function it_can_have_a_type_hint_if_it_is_an_object() + function it_can_have_a_type_hint_if_it_is_an_object(): void { $this->beConstructedWith('DateTime'); $this->hasTypeHint()->shouldBe(true); } - function it_can_have_a_type_hint_if_it_is_an_array() + function it_can_have_a_type_hint_if_it_is_an_array(): void { $this->beConstructedWith('array'); $this->hasTypeHint()->shouldBe(true); } - function it_can_have_a_type_hint_if_it_is_a_callable() + function it_can_have_a_type_hint_if_it_is_a_callable(): void { $this->beConstructedWith('callable'); $this->hasTypeHint()->shouldBe(true); } - function it_can_have_a_type_hint_if_it_is_a_string() + function it_can_have_a_type_hint_if_it_is_a_string(): void { $this->beConstructedWith('string'); $this->hasTypeHint()->shouldBe(true); } - function it_can_have_a_type_hint_if_it_is_an_integer() + function it_can_have_a_type_hint_if_it_is_an_integer(): void { $this->beConstructedWith('int'); $this->hasTypeHint()->shouldBe(true); } - function it_can_have_a_type_hint_if_it_is_a_float() + function it_can_have_a_type_hint_if_it_is_a_float(): void { $this->beconstructedwith('float'); $this->hastypehint()->shouldbe(true); } - function it_can_have_a_type_hint_if_it_is_a_boolean() + function it_can_have_a_type_hint_if_it_is_a_boolean(): void { $this->beConstructedWith('bool'); $this->hasTypeHint()->shouldBe(true); } - function it_can_have_a_type_hint_if_it_is_a_string_from_php_7_0() - { - $this->beConstructedWith('string'); - - $this->hasTypeHint()->shouldBe(version_compare(PHP_VERSION, '7.0.0') >= 0); - } - - function it_can_have_a_type_hint_if_it_is_an_integer_from_php_7_0() - { - $this->beConstructedWith('int'); - - $this->hasTypeHint()->shouldBe(version_compare(PHP_VERSION, '7.0.0') >= 0); - } - - function it_can_have_a_type_hint_if_it_is_a_float_from_php_7_0() - { - $this->beConstructedWith('float'); - - $this->hasTypeHint()->shouldBe(version_compare(PHP_VERSION, '7.0.0') >= 0); - } - - function it_can_have_a_type_hint_if_it_is_a_boolean_from_php_7_0() - { - $this->beConstructedWith('bool'); - - $this->hasTypeHint()->shouldBe(version_compare(PHP_VERSION, '7.0.0') >= 0); - } - - function it_can_be_an_array() + function it_can_be_an_array(): void { $this->beConstructedWith('array'); @@ -108,7 +83,7 @@ function it_can_be_an_array() $this->isObject()->shouldBe(false); } - function it_can_be_a_callable() + function it_can_be_a_callable(): void { $this->beConstructedWith('callable'); @@ -116,7 +91,7 @@ function it_can_be_a_callable() $this->isObject()->shouldBe(false); } - function it_can_be_a_string() + function it_can_be_a_string(): void { $this->beConstructedWith('string'); @@ -124,7 +99,7 @@ function it_can_be_a_string() $this->isObject()->shouldBe(false); } - function it_can_be_a_boolean() + function it_can_be_a_boolean(): void { $this->beConstructedWith('bool'); @@ -132,7 +107,7 @@ function it_can_be_a_boolean() $this->isObject()->shouldBe(false); } - function it_normalizes_boolean_name() + function it_normalizes_boolean_name(): void { $this->beConstructedWith('boolean'); @@ -140,7 +115,7 @@ function it_normalizes_boolean_name() $this->isObject()->shouldBe(false); } - function it_can_be_a_resource() + function it_can_be_a_resource(): void { $this->beConstructedWith('resource'); @@ -148,7 +123,7 @@ function it_can_be_a_resource() $this->isObject()->shouldBe(false); } - function it_can_be_an_integer() + function it_can_be_an_integer(): void { $this->beConstructedWith('int'); @@ -156,7 +131,7 @@ function it_can_be_an_integer() $this->isObject()->shouldBe(false); } - function it_normalizes_integer_name() + function it_normalizes_integer_name(): void { $this->beConstructedWith('integer'); @@ -164,7 +139,7 @@ function it_normalizes_integer_name() $this->isObject()->shouldBe(false); } - function it_can_be_a_float() + function it_can_be_a_float(): void { $this->beConstructedWith('float'); @@ -172,7 +147,7 @@ function it_can_be_a_float() $this->isObject()->shouldBe(false); } - function it_normalizes_float_name() + function it_normalizes_float_name(): void { $this->beConstructedWith('double'); @@ -180,7 +155,7 @@ function it_normalizes_float_name() $this->isObject()->shouldBe(false); } - function it_can_be_null() + function it_can_be_null(): void { $this->beConstructedWith('null'); @@ -188,7 +163,7 @@ function it_can_be_null() $this->isObject()->shouldBe(false); } - function it_normalizes_null_name() + function it_normalizes_null_name(): void { $this->beConstructedWith('NULL'); @@ -196,11 +171,87 @@ function it_normalizes_null_name() $this->isObject()->shouldBe(false); } - function it_can_be_unknown() + function it_can_be_unknown(): void { $this->beConstructedWith('mixed'); $this->getName()->shouldBe('mixed'); $this->isObject()->shouldBe(false); + $this->hasTypeHint()->shouldBe(true); + } + + function it_can_be_a_nullable_object(): void + { + $this->beConstructedWith('?DateTime'); + + $this->getName()->shouldBe('DateTime'); + $this->isObject()->shouldBe(true); + $this->hasTypeHint()->shouldBe(true); + $this->isNullable()->shouldBe(true); + } + + function it_can_be_a_nullable_scalar(): void + { + $this->beConstructedWith('?string'); + + $this->getName()->shouldBe('string'); + $this->isObject()->shouldBe(false); + $this->hasTypeHint()->shouldBe(true); + $this->isNullable()->shouldBe(true); + } + + function it_can_be_a_nullable_array(): void + { + $this->beConstructedWith('?array'); + + $this->getName()->shouldBe('array'); + $this->isObject()->shouldBe(false); + $this->hasTypeHint()->shouldBe(true); + $this->isNullable()->shouldBe(true); + } + + function it_normalizes_nullable_names(): void + { + $this->beConstructedWith('?boolean'); + + $this->getName()->shouldBe('bool'); + $this->isObject()->shouldBe(false); + $this->isNullable()->shouldBe(true); + } + + function it_can_be_a_union_type(): void + { + $this->beConstructedWith('string|int'); + + $this->getName()->shouldBe('string|int'); + $this->isUnionType->shouldBe(true); + $this->hasTypeHint()->shouldBe(true); + $this->types->shouldHaveCount(2); + } + + function it_can_be_a_nullable_union_type(): void + { + $this->beConstructedWith('string|null'); + + $this->getName()->shouldBe('string|null'); + $this->isUnionType->shouldBe(true); + $this->isNullable()->shouldBe(true); + } + + function it_normalizes_union_type_names(): void + { + $this->beConstructedWith('boolean|integer'); + + $this->getName()->shouldBe('bool|int'); + $this->isUnionType->shouldBe(true); + } + + function it_can_be_a_union_type_with_object(): void + { + $this->beConstructedWith('DateTime|string'); + + $this->getName()->shouldBe('DateTime|string'); + $this->isUnionType->shouldBe(true); + $this->types[0]->isObject->shouldBe(true); } } diff --git a/src/Memio/Model/Argument.php b/src/Memio/Model/Argument.php index 290dcbc..3622a8f 100644 --- a/src/Memio/Model/Argument.php +++ b/src/Memio/Model/Argument.php @@ -1,5 +1,7 @@ type = new Type($type); - $this->name = $name; + } + + /** + * @api + */ + public function addAttribute(Attribute $attribute): self + { + $this->attributes[] = $attribute; + + return $this; + } + + /** + * @api + */ + public function removeAttributes(): void + { + $this->attributes = []; } /** @@ -77,4 +97,44 @@ public function removeVariadic(): self return $this; } + + /** + * @api + */ + public function makePublic(): self + { + $this->visibility = 'public'; + + return $this; + } + + /** + * @api + */ + public function makeProtected(): self + { + $this->visibility = 'protected'; + + return $this; + } + + /** + * @api + */ + public function makePrivate(): self + { + $this->visibility = 'private'; + + return $this; + } + + /** + * @api + */ + public function removeVisibility(): self + { + $this->visibility = ''; + + return $this; + } } diff --git a/src/Memio/Model/Attribute.php b/src/Memio/Model/Attribute.php new file mode 100644 index 0000000..42d5623 --- /dev/null +++ b/src/Memio/Model/Attribute.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Memio\Model; + +/** + * @api + */ +class Attribute +{ + public ?string $arguments = null; + + /** + * @api + */ + public function __construct( + public string $name, + ) { + } + + /** + * @api + */ + public function setArguments(string $arguments): self + { + $this->arguments = $arguments; + + return $this; + } +} diff --git a/src/Memio/Model/Constant.php b/src/Memio/Model/Constant.php index bb75605..c612504 100644 --- a/src/Memio/Model/Constant.php +++ b/src/Memio/Model/Constant.php @@ -1,5 +1,7 @@ attributes[] = $attribute; + + return $this; + } /** * @api */ - public function __construct(string $name, string $value) + public function removeAttributes(): void { - $this->name = $name; - $this->value = $value; + $this->attributes = []; } } diff --git a/src/Memio/Model/Contract.php b/src/Memio/Model/Contract.php index 890f68b..1cb4d4f 100644 --- a/src/Memio/Model/Contract.php +++ b/src/Memio/Model/Contract.php @@ -1,5 +1,7 @@ attributes[] = $attribute; + + return $this; + } + + /** + * @api + */ + public function removeAttributes(): void + { + $this->attributes = []; + } + /** * @api */ diff --git a/src/Memio/Model/File.php b/src/Memio/Model/File.php index 2d1d486..2b78cfa 100644 --- a/src/Memio/Model/File.php +++ b/src/Memio/Model/File.php @@ -1,5 +1,7 @@ filename = $filename; } /** diff --git a/src/Memio/Model/FullyQualifiedName.php b/src/Memio/Model/FullyQualifiedName.php index 1562d82..e649cc5 100644 --- a/src/Memio/Model/FullyQualifiedName.php +++ b/src/Memio/Model/FullyQualifiedName.php @@ -1,5 +1,7 @@ 'double', ]; - public $fullyQualifiedName; - public $name; - public $namespace; - public $alias; + public string $fullyQualifiedName; + public string $name; + public string $namespace; + public ?string $alias = null; /** * @api diff --git a/src/Memio/Model/Method.php b/src/Memio/Model/Method.php index 1e5a787..87d9252 100644 --- a/src/Memio/Model/Method.php +++ b/src/Memio/Model/Method.php @@ -1,5 +1,7 @@ attributes[] = $attribute; + + return $this; + } /** * @api */ - public function __construct(string $name) + public function removeAttributes(): void { - $this->name = $name; + $this->attributes = []; } /** diff --git a/src/Memio/Model/Objekt.php b/src/Memio/Model/Objekt.php index 19f091a..86c5077 100644 --- a/src/Memio/Model/Objekt.php +++ b/src/Memio/Model/Objekt.php @@ -1,5 +1,7 @@ attributes[] = $attribute; + + return $this; + } + + /** + * @api + */ + public function removeAttributes(): void + { + $this->attributes = []; + } + /** * @api */ diff --git a/src/Memio/Model/Phpdoc/ApiTag.php b/src/Memio/Model/Phpdoc/ApiTag.php index ca6a6eb..eb5794e 100644 --- a/src/Memio/Model/Phpdoc/ApiTag.php +++ b/src/Memio/Model/Phpdoc/ApiTag.php @@ -1,5 +1,7 @@ since = $since; } } diff --git a/src/Memio/Model/Phpdoc/DeprecationTag.php b/src/Memio/Model/Phpdoc/DeprecationTag.php index d1e5839..a914f9b 100644 --- a/src/Memio/Model/Phpdoc/DeprecationTag.php +++ b/src/Memio/Model/Phpdoc/DeprecationTag.php @@ -1,5 +1,7 @@ version = $version; - $this->description = $description; } } diff --git a/src/Memio/Model/Phpdoc/Description.php b/src/Memio/Model/Phpdoc/Description.php index a3cee58..0349921 100644 --- a/src/Memio/Model/Phpdoc/Description.php +++ b/src/Memio/Model/Phpdoc/Description.php @@ -1,5 +1,7 @@ projectName = $projectName; - $this->authorName = $authorName; - $this->authorEmail = $authorEmail; } } diff --git a/src/Memio/Model/Phpdoc/MethodPhpdoc.php b/src/Memio/Model/Phpdoc/MethodPhpdoc.php index a869f8f..45b194e 100644 --- a/src/Memio/Model/Phpdoc/MethodPhpdoc.php +++ b/src/Memio/Model/Phpdoc/MethodPhpdoc.php @@ -1,5 +1,7 @@ description = $description; diff --git a/src/Memio/Model/Phpdoc/ParameterTag.php b/src/Memio/Model/Phpdoc/ParameterTag.php index 51c3a28..e9578f6 100644 --- a/src/Memio/Model/Phpdoc/ParameterTag.php +++ b/src/Memio/Model/Phpdoc/ParameterTag.php @@ -1,5 +1,7 @@ type = new Type($type); - $this->name = $name; - $this->description = $description; } } diff --git a/src/Memio/Model/Phpdoc/PropertyPhpdoc.php b/src/Memio/Model/Phpdoc/PropertyPhpdoc.php index e1f4175..6d4494c 100644 --- a/src/Memio/Model/Phpdoc/PropertyPhpdoc.php +++ b/src/Memio/Model/Phpdoc/PropertyPhpdoc.php @@ -1,5 +1,7 @@ type = $type; } } diff --git a/src/Memio/Model/Phpdoc/StructurePhpdoc.php b/src/Memio/Model/Phpdoc/StructurePhpdoc.php index 158b2d0..259e1d5 100644 --- a/src/Memio/Model/Phpdoc/StructurePhpdoc.php +++ b/src/Memio/Model/Phpdoc/StructurePhpdoc.php @@ -1,5 +1,7 @@ exception = $exception; } } diff --git a/src/Memio/Model/Phpdoc/VariableTag.php b/src/Memio/Model/Phpdoc/VariableTag.php index 8348072..5d55a59 100644 --- a/src/Memio/Model/Phpdoc/VariableTag.php +++ b/src/Memio/Model/Phpdoc/VariableTag.php @@ -1,5 +1,7 @@ attributes[] = $attribute; + + return $this; + } /** * @api */ - public function __construct(string $name) + public function removeAttributes(): void { - $this->name = $name; + $this->attributes = []; } /** @@ -55,7 +75,7 @@ public function makeStatic(): self /** * @api */ - public function removeStatic() + public function removeStatic(): void { $this->isStatic = false; } @@ -90,6 +110,24 @@ public function makePublic(): self return $this; } + /** + * @api + */ + public function setType(string $type): self + { + $this->type = new Type($type); + + return $this; + } + + /** + * @api + */ + public function removeType(): void + { + $this->type = null; + } + /** * @api */ diff --git a/src/Memio/Model/Structure.php b/src/Memio/Model/Structure.php index 595e6c4..fa53f1e 100644 --- a/src/Memio/Model/Structure.php +++ b/src/Memio/Model/Structure.php @@ -1,5 +1,7 @@ isNullable = str_starts_with($name, '?'); + if ($this->isNullable) { + $name = substr($name, 1); + } + if (str_contains($name, '|')) { + $this->isUnionType = true; + $this->hasTypeHint = true; + $this->isObject = false; + $parts = explode('|', $name); + $normalizedParts = []; + foreach ($parts as $part) { + $type = new self($part); + $this->types[] = $type; + $normalizedParts[] = $type->name; + if ('null' === $type->name) { + $this->isNullable = true; + } + } + $this->name = implode('|', $normalizedParts); + + return; + } if (isset(self::NORMALIZATIONS[$name])) { $name = self::NORMALIZATIONS[$name]; } @@ -85,4 +114,12 @@ public function hasTypeHint(): bool { return $this->hasTypeHint; } + + /** + * @api + */ + public function isNullable(): bool + { + return $this->isNullable; + } }