diff --git a/README.md b/README.md index ea5019c4..587aa1fb 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The rewrite of this library is heavily inspired by it and reuses some of its bas ## Docs -Coming soon... +[See the docs here](https://github.com/prolic/fpp/tree/master/docs/Home.md) ## Install diff --git a/docs/Conditions.md b/docs/Conditions.md deleted file mode 100644 index 186441a0..00000000 --- a/docs/Conditions.md +++ /dev/null @@ -1,152 +0,0 @@ -Please read [Simple examples](Simple%20Examples.md) first. - -Conditions are useful to add further restrictions to your objects. - -Conditions start using the keyword `where` and are bound to a specific constructor, so in case you have multiple constructors, -you can have specfic rules for each constructor. You can also use the default constructor, which is `_`. - -Conditions are defined by: -``` -| [some rule in pure PHP] => [some error message] -``` - -But it's way easier to understand from simple examples: - -``` -namespace Foo; -data FirstName = String deriving (FromString, ToString); -data LastName = String deriving (FromString, ToString); -data Age = Int deriving (FromScalar, ToScalar); -data Person = Person { FirstName $firstName, LastName $lastName, Age $age } | Boss { FirstName $firstName, LastName $lastName } where - Person: - | strlen($firstName->toString()) === 0 => 'first name too short' - | strlen($lastName->toString()) === 0 => 'last name too short' - | $age->toScalar() < 18 => "you\'re too young, sorry" - Boss: - | strlen($firstName->toString()) < 5 => 'first name too short' - | strlen($lastName->toString()) < 5 => 'last name too short'; -``` - -And this will generate the following: - -```php -class Person -{ - private $firstName; - private $lastName; - private $age; - - public function __construct(FirstName $firstName, LastName $lastName, Age $age) - { - if (strlen($firstName->toString()) === 0) { - throw new \InvalidArgumentException('first name too short'); - } - - if (strlen($lastName->toString()) === 0) { - throw new \InvalidArgumentException('last name too short'); - } - - if ($age->toScalar() < 18) { - throw new \InvalidArgumentException('you\'re too young, sorry'); - } - - $this->firstName = $firstName; - $this->lastName = $lastName; - $this->age = $age; - } - - // rest of code -} - -final class Boss extends Person -{ - private $firstName; - private $lastName; - - public function __construct(FirstName $firstName, LastName $lastName) - { - if (strlen($firstName->toString()) < 5) { - throw new \InvalidArgumentException('first name too short'); - } - - if (strlen($lastName->toString()) < 5) { - throw new \InvalidArgumentException('last name too short'); - } - - $this->firstName = $firstName; - $this->lastName = $lastName; - } -``` - -Now let's have a short look on the default constructor. -With the default constructor, you can have rules that apply to all of the constructors, without typing the rule every time. - -``` -// other definitions first -data Person = Person { FirstName $firstName, LastName $lastName, Age $age } | Boss { FirstName $firstName, LastName $lastName } where - Person: - | strlen($lastName->toString()) === 0 => 'last name too short' - | $age->toScalar() < 18 => "you\'re too young, sorry" - Boss: - | strlen($lastName->toString()) < 5 => 'last name too short' - _: - | strlen($firstName->toString()) === 0 => 'first name too short'; -``` - -This will generate the following: - -```php -class Person -{ - private $firstName; - private $lastName; - private $age; - - public function __construct(FirstName $firstName, LastName $lastName, Age $age) - { - if (strlen($lastName->toString()) === 0) { - throw new \InvalidArgumentException('last name too short'); - } - - if ($age->toScalar() < 18) { - throw new \InvalidArgumentException('you\'re too young, sorry'); - } - - if (strlen($firstName->toString()) === 0) { - throw new \InvalidArgumentException('first name too short'); - } - - $this->firstName = $firstName; - $this->lastName = $lastName; - $this->age = $age; - } - - // rest of code -} - -final class Boss extends Person -{ - private $firstName; - private $lastName; - - public function __construct(FirstName $firstName, LastName $lastName) - { - if (strlen($lastName->toString()) < 5) { - throw new \InvalidArgumentException('last name too short'); - } - - if (strlen($firstName->toString()) === 0) { - throw new \InvalidArgumentException('first name too short'); - } - - $this->firstName = $firstName; - $this->lastName = $lastName; - } - - // rest of code -} -``` - -Wanna see more? check [Markers](Markers.md) - -Wanna have [prooph components](http://getprooph.org/) integration? See the [prooph integration](prooph.md) diff --git a/docs/Configuration.md b/docs/Configuration.md new file mode 100644 index 00000000..638c358c --- /dev/null +++ b/docs/Configuration.md @@ -0,0 +1,94 @@ +# Functional PHP Preprocessor - Immutable data type generator + +## Configuration + +So far we used FPP without any custom configuration, that's because +FPP ships with a default build-in configuration. If you need to configure +FPP, first you need to generate the default config file, so you can than +adjust it to your needs: + +```bash +./vendor/bin/fpp --gen-config +``` + +This will create a file `fpp-config.php` in your project's root folder. + +The first few options are pretty self-explanatory, but we'll go over it anyway: + +- `use_strict_types` whether or not to use strict types in the generated PHP code +- `printer` a callback that creates a Printer for FPP to use. Usually the PSR + Printer is exactly what you want, so you most likely won't change this ever. + In case you are working with Nette Framework (we use their PhpGenerator component), + you might want to use the `Nette\PhpGenerator\Printer` class instead. As FPP + doesn't have any type hints on the printer used, you could also use your very + own printer implementation here. +- `file_parser` defines the function that will parse files for you. Unless you know + the internals of FPP and want to mess around a little, you would never touch that + at all. +- `comment` defines the comment that is added on top of every generated file. + You can put any string here or set to `null` to disable comments. +- `types` is the list of available types in FPP as well as the `DateTimeImmutable` + class. That's because FPP can use already `DateTimeImmutable`, as long as you + import that class. + +## Custom type configuration + +Maybe the easiest way to learn how to configure FPP is by copy & paste the example +of `DateTimeImmutable`, add to the configuration and adjust. However, let's go +through the various steps quickly. + +In the `types` section of the configuration, you need to provide a class name in +the key of the config and a `TypeConfiguration` as value. The `TypeConfiguration` +class has the following constructor: + +``` +public function __construct( + ?callable $parse, + ?callable $build, + ?callable $fromPhpValue, + ?callable $toPhpValue, + ?callable $validator, + ?callable $validationErrorMessage, + ?callable $equals +) +``` + +As you can see, it expects a bunch of callable but all of them are optional. + +- `parse` defines the parse function to use in order to parse the given type. + When importing already existing classes, you would provide `null` here. + In case you want to add your very own FPP type, you need to provide the parser + function here. +- `build` defines the function that builds the PHP code that is then printed by + the printer defined. Again, even when we are using the `Nette\PhpGenerator`, + there are no type hints at all. So if you replace all the default builders + shipped with FPP and provide a special printer, you can use FPP to even generate + code in any other language, for example JavaScript. +- `fromPhpValue` defines the function that will be used to transform a scalar or + array PHP value to an object. If there is no function provided, the object will + be required as is in the generated `fromArray` method. +- `toPhpValue` defines the function that will be used to transform your object to + a scalar or array value in PHP. If there is no function provided, the object + will be returned as is in the generated `toArray` method. +- `validator` defines the function that will be used to validate a given PHP scalar + or array value. This will be used in the generated `fromArray` method. If left to + `null` the value will be be validated at all. +- `validationErrorMessage` is used to display the error message in the `fromArray` + method, when the given `validator` fails. +- `equals` defines how to compare of two of those objects are equal. + +Let's have an example here real quick: + +``` +Role::class => new TypeConfiguration( + null, + null, + fn (string $type, string $paramName) => "$type::fromName($$paramName)", + fn (string $paramName) => $paramName . '->getName()', + fn (string $paramName) => "\is_string(\$$paramName)", + fn (string $paramName) => "Error on \"$paramName\", string expected", + fn (string $paramName, string $otherParamName) => "{$paramName}->equals($otherParamName)" +), +``` + +So far for the configuration, let's head to [Messages](Messages.md) next. diff --git a/docs/Data Types.md b/docs/Data Types.md new file mode 100644 index 00000000..6a0290c2 --- /dev/null +++ b/docs/Data Types.md @@ -0,0 +1,168 @@ +# Functional PHP Preprocessor - Immutable data type generator + +## Data Types + +### Basics + +Data types are the thing where FPP really shines. You've learned how to generate +simple wrappers around PHP scalar types like string, int, bool, float as well as +uuid, guid and enum. We can use those base types to generate more complex types. + +Let's say we need a class with the following constructor: + +``` +class Address +{ + // properties here + + public function __construct( + string $street, + string $number, + string $zipCode, + string $country + ) { + // more code here + } + + // a bunch of setters here + + // toArray-method + + // fromArray-constructor + + // equals method +} +``` + +As you can see, this would be a lot of code to type, so let's see how the same thing +can be generated by FPP without typing all this boilerplate. + +``` +namespace Foo; + +data Address = { string $street, string $number, string $zipCode, string $country }; +``` + +Of course, you can also add marker interfaces again: + +``` +namespace Foo; + +data Address : MyMarker = { string $street, string $number, string $zipCode, string $country }; +``` + + +So that's a lot less code to type! We can also use our base types, so let's do this +by example again and use an enum for our country parameter as well: + +``` +namespace Foo; + +string Street; +string Number; +string ZipCode; +enum CountryName = US | PY | CA | UK; + +data Address = { Street $street, Number $number, ZipCode $zipCode, Country $country }; +``` + +As you might notice, using base types, the parameter name and type are usually the same, +so we can also avoid it: + +``` +namespace Foo; + +data Address = { Street, Number, ZipCode, Country }; +``` + +But what happens if you need to use the same type twice in a constructor? +For example we can think of a user having a user id and a parent user id. +Let's have a look: + +``` +namespace Foo; + +uuid UserId; +string FirstName; +string LastName; +int Age; + +data User = { UserId, UserId, FirstName, LastName, Age }; +``` + +This would generate the following parameter names: + +`$userId, $userId2, $firstName, $lastName, $age` + +That's already pretty good, but most likely you want to name the second parameter +according to its purpose `$parentUserId`. That's no big deal at all! + +``` +namespace Foo; + +data User = { UserId, UserId $parentUserId, FirstName, LastName, Age }; +``` + +### Nullable types, array-bracket syntax and default values + +Array-bracket syntax is very importing if you are dealing with lists of things, +and nullable types when a property is optional. Of course, you can also add +default values: + +``` +namespace Foo; + +data User = { string $name = 'Guest', ?int $parentUserId, string[] $hobbies }; +``` + +### Importing classes from another namespace + +You can use the same syntax (also using aliases) as you would use in plain PHP. +So the following would work and generate you usable PHP code: + +``` +namespace Foo { + + string FullName; + string Hobbie; +} + +namespace Foo\Bar { + use Foo\FullName as Name; + use Foo\Hobbie; + + data Person = { Name, Hobbie[] }; +} +``` + +### Subclasses + +Let's assume you need to have an `Animal` abstract class and its implementations +`Cat`, `Dog` and `Bird`. Let's have a look: + +``` +namespace Foo; + +data Animal = + Cat { string $name, int $age } + | Dog { string $name, int $age, string $ownersName } + | Bird { string $name }; +``` + +### DateTimeImmutable + +You can reuse DateTimeImmutable class by default in FPP, just import it first: + +``` +namespace Foo; + +use DateTimeImmutable; + +uuid UserId; +string Name; +data User = { UserId, DateTimeImmutable $registeredAt, Name }; +``` + +Next, let's have look on how to reuse existing classes in FPP. In order to do that +you need to configure FPP a little and make it aware on how those classes, external +to FPP work. See [Configuration](Configuration.md). diff --git a/docs/Derivings.md b/docs/Derivings.md deleted file mode 100644 index 9bcfa774..00000000 --- a/docs/Derivings.md +++ /dev/null @@ -1,55 +0,0 @@ -Please read [Simple examples](Simple%20Examples.md) first. - -Derivings are kind of PHP's extends keyword, the following rules apply: - -- It's possible to derive multiple times -- Some derivings are not compatible to each other (f.e. Command and ToArray cannot be mixed) - -There are 14 deriving types for now: - -- `AggregateChanged` -- `Command` -- `DomainEvent` -- `Enum(useName)` (default) / `Enum(useValue)` -- `Equals` -- `FromArray` -- `FromScalar` -- `FromString` -- `Query` -- `MicroAggregateChanged` (not extending from prooph/eventsourcing, f.e. for prooph/micro) -- `ToArray` -- `ToScalar` -- `ToString` -- `Uuid` - -Okay, but as always, it's easier to see for yourself, so generate this code: - -``` -namespace Foo; -data Person = Person { string $name, ?int $age } deriving (ToArray, Equals); -``` - -Or create some nice enum: - -``` -namespace Foo; -data Color = Red | Blue | Green | Yellow deriving (Enum); -``` - -Even enums with value mapping: - -```fpp -namespace Foo; -data Color = Red | Blue | Yellow deriving (Enum) with (Red:'someThing', Blue: 13, Yellow:['key' => 'value']); -``` - -What about uuids? - -``` -namespace Foo; -data UserId = UserId deriving (Uuid); -``` - -Wanna see more? check [Conditions](Conditions.md) - -Wanna have [prooph components](http://getprooph.org/) integration? See the [prooph integration](prooph.md) diff --git a/docs/Exception.md b/docs/Exception.md deleted file mode 100644 index 61a74b08..00000000 --- a/docs/Exception.md +++ /dev/null @@ -1,304 +0,0 @@ -# Definition - -## Basic - -``` -namespace App; -data UserNotFound = UserNotFound deriving (Exception); -``` - -generates - -```php -id = $id; - - parent::__construct($message, $code, $previous); - } - - public function id(): UserId - { - return $this->id; - } -} -``` - - -## With named constructor -``` -namespace App; -data EmailAlreadyUsed = EmailAlreadyUsed deriving (Exception) with - | withEmail { string $email } => 'Email {{$email}} is already used'; -``` - -generates - -```php - - 'User is not old enough'; -``` - -generates - -```php - 'User must be {{$requiredAge}}'; -``` - -generates - -```php -age = $age; - - parent::__construct($message, $code, $previous); - } - - public static function withAge(int $age, int $requiredAge, int $code = 0, \Throwable $previous = null): self - { - return new self($age, sprintf('User must be %s', $requiredAge), $code, $previous); - } - - public function age(): int - { - return $this->age; - } -} -``` - -## Full featured definition -``` -namespace App; -data UserId = UserId deriving (Uuid); -marker UserLandException; -data InvalidUserException: UserLandException = InvalidUserException deriving (Exception: \RuntimeException); -data UserNotFound = UserNotFound { UserId $id } deriving (Exception: InvalidUserException) with - | withId { UserId $id } => 'User {{$id}} does not exist' - | withEmail { string $email, UserId $id } => 'User {{$email}} does not exist' - | _ => 'An user is no one'; -``` - -generates - -```php -id = $id; - - parent::__construct($message, $code, $previous); - } - - public static function withId(UserId $id, int $code = 0, \Throwable $previous = null): self - { - return new self($id, sprintf('User %s does not exist', $id), $code, $previous); - } - - public static function withEmail(string $email, UserId $id, int $code = 0, \Throwable $previous = null): self - { - return new self($id, sprintf('User %s does not exist', $email), $code, $previous); - } - - public function id(): UserId - { - return $this->id; - } -} -``` - -# Error cases - -## With non instance of \Throwable parent -``` -data UserNotFound = UserNotFound deriving (Foo); -``` - -will throw a \RuntimeException: "App\UserNotFound" cannot extend "Foo" because it is not an instance of "\Throwable" - -## With named constructor not defining exception properties -``` -namespace App; -data TooYoung = TooYoung { int $age } deriving (Exception) with - | withRequiredAge { int $requiredAge } => 'User must be {{$requiredAge}}'; -``` - -will throw a \RuntimeException: "App\UserNotFound::withRequiredAge" is missing required argument "$age" diff --git a/docs/Home.md b/docs/Home.md index 7ad38597..d1a76641 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,17 +1,185 @@ -## Functional PHP Preprocessor - Immutable data type generator +# Functional PHP Preprocessor - Immutable data type generator -### YouTube Video Tutorial +This library can generate immutable data types in PHP. The syntax is very precise. +You can type about 1/50 of the amount of code required in FPP compared to PHP. -[![YouTube Video Tutorial](https://i.ytimg.com/vi/MYh1_sydQ5U/hqdefault.jpg?sqp=-oaymwEXCNACELwBSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCtO68XORuK-gEGeTJSXdSHqY3PBQ)](https://youtu.be/MYh1_sydQ5U) +## Basics -### [PhpStorm integration](PhpStorm-Integration.md) +Every fpp-file needs to have only one namespace, declared exactly like in PHP: -### [Simple examples](Simple%20Examples.md) +``` +namespace Foo; +``` -### [Derivings](Derivings.md) +But you can also have multiple namespaces in a file, using curly bracket syntax: -### [Conditions](Conditions.md) +``` +namespace Foo { +} -### [Markers](Markers.md) +namespace Bar { +} +``` -### [prooph integration](prooph.md) +The namespaces you use, need to be available in your composer.json file, so FPP knows +where to store the generated files. PSR-4 and PSR-0 are supported. + +Let's start with something simple: All we need are wrappers around scalar types in PHP. + +Why? Consider this code in PHP: + +``` +class Person { + // maybe some properties here + + public function __construct( + string $firstName, + string $lastName, + int $age, + int $amountOfChildren, + float $longitude, + float $latitude + ) { + // more code here + } +} +``` + +Although we use PHP's type system, it's easy to break things. You can put first name +and last name in the wrong order, same with age and amount of children, and so on. + +What we want to have, are distinct types for each property. Just like this: + +``` +class Person { + // maybe some properties here + + public function __construct( + FirstName $firstName, + LastName $lastName, + Age $age, + AmountOfChildren $amountOfChildren, + Longitude $longitude, + Latitude $latitude + ) { + // more code here + } +} +``` + +Just to have the `FirstName` created, FPP will generate this PHP code for you: + +``` +value = $value; + } + + public function value(): string + { + return $this->value; + } + + public function equals(?self $other): bool + { + return null !== $other && $this->value === $other->value; + } +} +``` + +You can imagine the amount of boilerplate you have to write to create all those wrappers. + +FPP can generate you this boilerplate very quick without much typing. + +``` +namespace Foo; + +bool IsDeleted; +float Longitude; +int Age; +string FirstName +``` + +Run `./vendor/bin/fpp ` and this is generated! + +## Enums, Uuid's and more + +PHP doesn't have an enum type, however FPP can give you a precise syntax +that will generate PHP code for you, so you have something to work with as enums. + +``` +namespace Foo; + +enum Color = Red | Green | Blue | Yellow; +``` + +If you're working with ramsey/uuid a lot, you might want to have custom types for +your uuid's as well. So you can name them `UserId` instead of just Uuid. + +FPP can do that for you: + +``` +namespace Foo; + +uuid UserId; +``` + +In case you want Guid's, no problem: + +``` +namespace Foo; + +guid UserId; +``` + +## Adding interfaces + +In case you have an existing interface you want to add to your generated class, +you can do so by using the following syntax: + +``` +namespace Foo; + +string FirstName : MyMarker; +``` + +This will let the generated class `Foo\FirstName` have the `MyMarker` interface +implemented. Note that if the interface has methods defined in it, that would +not be generated as well by FPP, you'll get an invalid class you cannot use. +Therefor it's mostly useful for marker interfaces only. + +## Marker interfaces + +A marker interface is an interface without any defined methods or constants. +Usually it is used to `mark` a class with a given interface. A good example for that +would be the `Traversable` interface in PHP. + +You can define marker interfaces with the marker keyword: + +``` +namespace Foo; + +marker MyMarker; +``` + +And you can extend markers from other interfaces: + +``` +namespace Foo; + +marker MyMarker; +marker MyMarker2; +marker OtherMarker : MyMarker, MyMarker2; +``` + +So far for the basics, let's head to [Data Types](Data Types.md) next. diff --git a/docs/Markers.md b/docs/Markers.md index 52eb5428..eb420408 100644 --- a/docs/Markers.md +++ b/docs/Markers.md @@ -63,6 +63,6 @@ marker MyMarkerD; data MyData : MyMarkerC, MyMarkerD = MyData; ``` -Wanna see more? check [Derivings](Derivings.md) +Wanna see more? check [Derivings](Configuration.md) Wanna have [prooph components](http://getprooph.org/) integration? See the [prooph integration](prooph.md) diff --git a/docs/Messages.md b/docs/Messages.md new file mode 100644 index 00000000..47c3136e --- /dev/null +++ b/docs/Messages.md @@ -0,0 +1,73 @@ +# Functional PHP Preprocessor - Immutable data type generator + +## Messages + +The following message types are available: + +- `command` +- `event` + +Those are basically data types with some additional attributes. + +### Command + +Assuming we have all the basic types already defined: + +``` +namespace Foo; + +command UserCommand : Command (CommandId) = + RegisteredUser as registered-user { Name $name, ?Age $age} + | UpdateUserName as update-user-name { Name $name } + | DeleteUser; +``` + +A command can be defined using the `command` keyword, following by the abstract +class (here `UserCommand`) generated, and optionally also one or more marker +interfaces (here `Command`). A command can be distinguished from other commands +by it's ID (here `CommandId`), usually that's a UUID or GUID. + +After the equals sign we defined here the various constructors (sub classes) and +it's parameters (if any). + +The used alias `as` that you see here, f.e. `as registered-user` defines a command +type. Each generated command has a command type property and getter-method that +returns a string representation of the command type. If none provided it will +default to the fully qualified class name. + +### Event + +Events here are meant to be used for event sourcing, so they not only have an EventId +similar to the CommandId for commands, but they also have an aggregate ID to specify +to which aggregate root they belong. Therefor they have one argument more, but +other than that look very similar: + +``` +namespace Foo; + +event UserEvent : Event (EventId, UserId) = + UserRegistered as user-registered { Name $name, ?Age $age} + | UserNameUpdated as user-name-updated { Name $name } + | UserDeleted; +``` + +Actually the EventId and AggregateId (here called `UserId`) don't need to have +different types. You could reuse the same GUID implementation everywhere: + +``` +namespace Foo; + +guid Guid; + +command UserCommand : Command (Guid) = + RegisteredUser as registered-user { Name $name, ?Age $age} + | UpdateUserName as update-user-name { Name $name } + | DeleteUser; + +event UserEvent : Event (Guid, Guid) = + UserRegistered as user-registered { Name $name, ?Age $age} + | UserNameUpdated as user-name-updated { Name $name } + | UserDeleted; +``` + +That's it, last but not least, you can have full [PhpStorm Integration](PhpStorm-Integration.md). diff --git a/docs/PhpStorm-Integration.md b/docs/PhpStorm-Integration.md index 6ec6a695..d8d527a9 100644 --- a/docs/PhpStorm-Integration.md +++ b/docs/PhpStorm-Integration.md @@ -1,45 +1,66 @@ +# Functional PHP Preprocessor - Immutable data type generator + +## PhpStorm Interation + Do the following after installing FPP into your project. -1) First you'll need to install the [File Watchers plugin](https://www.jetbrains.com/help/phpstorm/settings-tools-file-watchers.html), if you haven't already done so. -To install the plugin, go to the PhpStorm Settings/Preferences -> "Plugins" -> and search for "File Watchers". Check off the plugin to install it. You'll need to restart PhpStorm. +1) First you'll need to install the [File Watchers plugin](https://www.jetbrains.com/help/phpstorm/settings-tools-file-watchers.html), if you haven't already done so. +To install the plugin, go to the PhpStorm Settings/Preferences -> "Plugins" -> and search for "File Watchers". Check off the plugin to install it. You'll need to restart PhpStorm. ![Enable the Plugin](https://raw.githubusercontent.com/prolic/fpp/master/docs/img/phpstorm_1.png) -2) Now we need to create an FPP file type. -In the PhpStorm Settings/Preferences -> "Editor" -> "File Types". -Add a new file type, click at the "Recognized File Types" section on the "+" icon. -Put the name to "FPP" and description to "FPP files" (or whatever you'd like). -Add `//` as Line comment, `/*` as Block comment start and `*/` as Block comment end. -Set the keywords to: +2) Now we need to create an FPP file type. +In the PhpStorm Settings/Preferences -> "Editor" -> "File Types". +Add a new file type, click at the "Recognized File Types" section on the "+" icon. +Put the name to "FPP" and description to "FPP files" (or whatever you'd like). +Add `//` as Line comment, `/*` as Block comment start and `*/` as Block comment end. +Set the keywords to: ``` -=> -_ -data -deriving namespace -where -with +use +data +bool +string +float +int +enum +uuid +guid +event +command +{ +| +} += +: +``` + +If you want to have more colors, put the following to the Tab `2` of keywords: + +``` { | } += +: ``` -Then click on OK. -In the "Registered Patterns" section (with FPP selected above), click on the "+" icon and add `*.fpp`, then click OK. +Then click on OK. +In the "Registered Patterns" section (with FPP selected above), click on the "+" icon and add `*.fpp`, then click OK. ![Add FPP File Type](https://raw.githubusercontent.com/prolic/fpp/master/docs/img/phpstorm_2.png) -3) Configure the File Watcher. -In the PhpStorm Settings/Preferences -> "Tools" -> "File Watchers". -Click on the "+" icon and choose "" for the template. -Put "FPP" as name. -Select FPP as the File type. -Select your Scope, if you don't have one, create it. Set it to `src` for example. -Set "Programm" to `php`. -Set this as "Arguments": `$ProjectFileDir$/vendor/prolic/fpp/bin/fpp.php $FilePath$` +3) Configure the File Watcher. +In the PhpStorm Settings/Preferences -> "Tools" -> "File Watchers". +Click on the "+" icon and choose "" for the template. +Put "FPP" as name. +Select FPP as the File type. +Select your Scope, if you don't have one, create it. Set it to `src` for example. +Set "Programm" to `php`. +Set this as "Arguments": `$ProjectFileDir$/vendor/prolic/fpp/bin/fpp.php $FilePath$` Enter an output path, such as `$Sourcepath$` (PHPStorm will refresh this path to look for changes to the generated classes) -Disable the checkbox "Auto-save edited files to trigger the watcher" – in my experience this is really awkward. -Select "On error" for "Show Console". -Click OK. +Disable the checkbox "Auto-save edited files to trigger the watcher" – in my experience this is really awkward. +Select "On error" for "Show Console". +Click OK. ![Configure the File Watcher](https://raw.githubusercontent.com/prolic/fpp/master/docs/img/phpstorm_3.png) Try it out! Have fun! diff --git a/docs/Simple Examples.md b/docs/Simple Examples.md deleted file mode 100644 index a986e021..00000000 --- a/docs/Simple Examples.md +++ /dev/null @@ -1,65 +0,0 @@ -Generate the code for the examples here yourself. -Create a file `demo.fpp` and run `php bin/fpp.php demo.fpp`. -Make sure you have `Foo` in your `composer.json`'s autoload section. - -Let's start with something really simple: - -``` -namespace Foo; -data Person = Person { string $name, ?int $age }; -``` - -Or adding default values: - -``` -namespace Foo; -data Person = Person { string $name, int $value = 5 }; -``` - -But you can also reuse objects: - -``` -namespace Foo; -data Name = String; -data Person = Person { Name $name, ?int $age }; -``` - -Or reuse objects across namespaces: - -``` -namespace Foo\Bar { - data Name = String; -} -namespace Foo { - data Person = Person { Bar\Name $name, ?int $age }; -} -``` - -Use array notation: - -``` -namespace Foo\Bar { - data Name = String; - data EmailAddress = String; -} -namespace Foo { - data Person = Person { Bar\Name $name, ?int $age, EmailAddress[] $emailAddresses, string[] $nicknames }; -} -``` - -Put comments: - -``` -namespace Foo\Bar { - data Name = String; -} -// comment here -namespace Foo { - /* - comment there - */ - data Person = Person { Bar\Name $name, ?int $age }; -} -``` - -Wanna see more? check [Derivings](Derivings.md) and [Conditions](Conditions.md) diff --git a/docs/prooph.md b/docs/prooph.md deleted file mode 100644 index 6b50c08b..00000000 --- a/docs/prooph.md +++ /dev/null @@ -1,39 +0,0 @@ -Please read [Simple examples](Simple%20Examples.md) and [Derivings](Derivings.md) first. - -There are 5 deriving types for prooph messages: - -- AggregateChanged -- Command -- DomainEvent -- Query -- MicroAggregateChanged - -Command, DomainEvent and Query will generate what the name says, obviously. -An AggregateChanged is a special deriving for prooph/eventsourcing. -MicroAggregateChanged is another special deriving that is not extending from prooph/eventsourcing, f.e. for prooph/micro. - -Enough theory, let's generate some code: - -``` -namespace Foo { - data UserId = UserId deriving (Uuid); - data Name = String deriving (ToString, FromString); - data Age = Int deriving (ToScalar, FromScalar); - data Email = String deriving (ToString, FromString); - data Person = Person { Name $name, ?Age $age, Email[] $emails } deriving (ToArray, FromArray); -} - -namespace Foo\Command { - data CreateUser = CreateUser { \Foo\UserId $userId, \Foo\Person $person } deriving (Command); -} - -namespace Foo\Query { - data FindUser = FindUser { \Foo\UserId $userId } deriving (Query); -} - -namespace Foo\DomainEvent { - data UserCreated = UserCreated { \Foo\UserId $userId, \Foo\Person $person } deriving (DomainEvent); -} -``` - -Now try (Micro)AggregateChanged yourself if you're into event-sourcing. diff --git a/spec/FppParser/CommandSpec.php b/spec/FppParser/CommandSpec.php new file mode 100644 index 00000000..28d5fd0e --- /dev/null +++ b/spec/FppParser/CommandSpec.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace FppSpec\FppParser; + +use Fpp\Argument; +use Fpp\Type\Command\Command; +use Fpp\Type\Command\Constructor; +use function Fpp\Type\Command\parse; + +describe("Fpp\Parser", function () { + context('FPP parsers', function () { + describe('command', function () { + it('can parse command types', function () { + expect(parse()->run('command Cmd (Guid) = Go { string, int };')[0]->_1)->toEqual( + new Command( + 'Cmd', + [], + 'Guid', + [ + new Constructor( + 'Go', + '', + [ + new Argument( + 'string', + 'string', + false, + false, + null), + new Argument( + 'int', + 'int', + false, + false, + null + ), + ] + ), + ] + ) + ); + }); + + it('can parse command types with custom names', function () { + expect(parse()->run('command Cmd (Guid) = Go as goo { string, int };')[0]->_1)->toEqual( + new Command( + 'Cmd', + [], + 'Guid', + [ + new Constructor( + 'Go', + 'goo', + [ + new Argument( + 'string', + 'string', + false, + false, + null + ), + new Argument( + 'int', + 'int', + false, + false, + null + ), + ] + ), + ] + ) + ); + }); + + it('can parse command types with custom name, marker and multiple types', function () { + expect(parse()->run('command Cmd : Marker (Guid) = Go as goo { string, int } + | Go2 as goo2 { bool, float };')[0]->_1)->toEqual( + new Command( + 'Cmd', + ['Marker'], + 'Guid', + [ + new Constructor( + 'Go', + 'goo', + [ + new Argument( + 'string', + 'string', + false, + false, + null + ), + new Argument( + 'int', + 'int', + false, + false, + null + ), + ] + ), + new Constructor( + 'Go2', + 'goo2', + [ + new Argument( + 'bool', + 'bool', + false, + false, + null + ), + new Argument( + 'float', + 'float', + false, + false, + null + ), + ] + ), + ] + ) + ); + }); + }); + }); +}); diff --git a/spec/FppParser/EventSpec.php b/spec/FppParser/EventSpec.php new file mode 100644 index 00000000..60ea4cef --- /dev/null +++ b/spec/FppParser/EventSpec.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace FppSpec\FppParser; + +use Fpp\Argument; +use Fpp\Type\Event\Constructor; +use Fpp\Type\Event\Event; +use function Fpp\Type\Event\parse; + +describe("Fpp\Parser", function () { + context('FPP parsers', function () { + describe('event', function () { + it('can parse event types', function () { + expect(parse()->run('event Ev (Guid, int) = Go { string, int };')[0]->_1)->toEqual( + new Event( + 'Ev', + [], + 'Guid', + 'int', + [ + new Constructor( + 'Go', + '', + [ + new Argument( + 'string', + 'string', + false, + false, + null), + new Argument( + 'int', + 'int', + false, + false, + null + ), + ] + ), + ] + ) + ); + }); + + it('can parse event types with custom names', function () { + expect(parse()->run('event Ev (Guid, int) = Go as goo { string, int };')[0]->_1)->toEqual( + new Event( + 'Ev', + [], + 'Guid', + 'int', + [ + new Constructor( + 'Go', + 'goo', + [ + new Argument( + 'string', + 'string', + false, + false, + null + ), + new Argument( + 'int', + 'int', + false, + false, + null + ), + ] + ), + ] + ) + ); + }); + + it('can parse event types with custom name, marker and multiple types', function () { + expect(parse()->run('event Ev : Marker (Guid, int) = Go as goo { string, int } + | Go2 as goo2 { bool, float };')[0]->_1)->toEqual( + new Event( + 'Ev', + ['Marker'], + 'Guid', + 'int', + [ + new Constructor( + 'Go', + 'goo', + [ + new Argument( + 'string', + 'string', + false, + false, + null + ), + new Argument( + 'int', + 'int', + false, + false, + null + ), + ] + ), + new Constructor( + 'Go2', + 'goo2', + [ + new Argument( + 'bool', + 'bool', + false, + false, + null + ), + new Argument( + 'float', + 'float', + false, + false, + null + ), + ] + ), + ] + ) + ); + }); + }); + }); +}); diff --git a/spec/FppSpec.php b/spec/FppSpec.php index 5d00c307..b59af23b 100644 --- a/spec/FppSpec.php +++ b/spec/FppSpec.php @@ -12,9 +12,20 @@ namespace FppSpec; +use function describe; +use function expect; +use Fpp\Argument; +use function Fpp\buildDefaultPhpFile; +use function Fpp\calculateDefaultValue; +use Fpp\Configuration; +use Fpp\Definition; use function Fpp\flatMap; use function Fpp\isKeyword; use function Fpp\locatePsrPath; +use Fpp\Type\Enum\Constructor; +use Fpp\Type\Enum\Enum; +use Nette\PhpGenerator\PsrPrinter; +use function Pair; describe('Fpp', function () { context('Basic FPP helper functions', function () { @@ -71,5 +82,74 @@ expect(locatePsrPath([], $psr0, 'Foo\\Bar'))->toBe('src/Foo/Bar.php'); }); }); + + describe('buildDefaultPhpFile', function () { + it('can build default php file structure', function () { + $definition = new Definition( + 'Foo', + new Enum('Color', [], [ + new Constructor('Blue'), + new Constructor('Red'), + ]), + [ + Pair('LongName', 'LN'), + ] + ); + $config = new Configuration( + true, + fn () => new PsrPrinter(), + fn () => null, + null, + [] + ); + + $phpFile = buildDefaultPhpFile($definition, $config); + + expect($phpFile->hasStrictTypes())->toBe(true); + expect($phpFile->getNamespaces())->not->toBeEmpty(); + expect($phpFile->getNamespaces()['Foo']->getName())->toBe('Foo'); + expect($phpFile->getNamespaces()['Foo']->getUses())->toEqual(['LN' => 'LongName']); + }); + }); + + describe('calculateDefaultValue', function () { + it('can calculate default php values for a given argument', function () { + $arg1 = new Argument('foo', null, false, false, null); + + expect(calculateDefaultValue($arg1))->toBe(null); + + $arg2 = new Argument('foo', 'string', false, false, '\'\''); + + expect(calculateDefaultValue($arg2))->toBe(''); + + $arg3 = new Argument('foo', 'int', false, false, '12'); + + expect(calculateDefaultValue($arg3))->toBe(12); + + $arg4 = new Argument('foo', null, false, true, '[]'); + + expect(calculateDefaultValue($arg4))->toBe([]); + + $arg5 = new Argument('foo', 'float', false, false, '12.3'); + + expect(calculateDefaultValue($arg5))->toBe(12.3); + + $arg6 = new Argument('foo', 'string', false, false, '\'foo\''); + + expect(calculateDefaultValue($arg6))->toBe('foo'); + + $arg7 = new Argument('foo', null, false, false, null); + + expect(calculateDefaultValue($arg7))->toBe(null); + + $arg8 = new Argument('foo', 'array', false, true, '[]'); + + expect(calculateDefaultValue($arg8))->toBe([]); + + $arg9 = new Argument('foo', 'string', true, false, null); + + expect(calculateDefaultValue($arg9))->toBe(null); + }); + }); }); }); diff --git a/src/Type/Command.php b/src/Type/Command.php index b48b272f..86b0bd64 100644 --- a/src/Type/Command.php +++ b/src/Type/Command.php @@ -309,8 +309,6 @@ class Command implements FppType private string $commandIdType; - private string $aggregateIdType; - /** @param list $constructors */ public function __construct( string $classname,