From e061b424a58e2a4ef8c8b2ffd5e8f2cd58de429c Mon Sep 17 00:00:00 2001 From: Jacques Marcotte Date: Wed, 20 Nov 2019 13:26:28 -0600 Subject: [PATCH 1/7] BUPH-21 | add more Annotation Processor documentation --- README.md | 77 +-------------- docs/AnnotationProcessors.md | 175 +++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 75 deletions(-) create mode 100644 docs/AnnotationProcessors.md diff --git a/README.md b/README.md index ccaa73b..21d70ad 100644 --- a/README.md +++ b/README.md @@ -225,82 +225,9 @@ Neighborhoods_Buphalo_V1_TemplateTree_Map_Builder_FactoryInterface__TemplateTree * In order to be efficient, Buphalo will only fabricate files that do not exist in `src` since anything in `src` will override what exists in `fab`. ### Annotation Processors -* Annotation Processors allow user space to define dynamic template content before tokenization or compilation of the template. -* Annotation Processors are optional. -* Providing static context to the Annotation Processor is optional. -* If the `static_context_record` key is provided, it MUST resolve to a PHP `array`. -* Default annotation replacement is accomplished by using the contents of the annotation. -* Annotation Processors MUST implement `\Neighborhoods\Buphalo\V1\AnnotationProcessorInterface`. -* Annotation Processors are not shared services. -```php -namespace Neighborhoods\Buphalo\V1; +Annotation Processors (APs) are optional configuration tools that allow user space to define dynamic template content. -use Neighborhoods\Buphalo\V1\AnnotationProcessor\ContextInterface; - -interface AnnotationProcessorInterface -{ - public function setAnnotationProcessorContext(ContextInterface $Context); - - public function getAnnotationProcessorContext(): ContextInterface; - - public function getReplacement(): string; -} -``` -* Currently, annotation processors have access to the static context, the annotation contents, and the Fabrication File by accessing the injected `\Neighborhoods\Buphalo\V1\AnnotationProcessor\ContextInterface` object. - -### Example Annotation Processors -* Annotation Processor Tag: `@neighborhoods-buphalo:annotation-processor` -* Annotation Processor Keys: - * `Neighborhoods\BuphaloTemplateTree\PrimaryActorName\Builder.build1` - * `Neighborhoods\BuphaloTemplateTree\PrimaryActorName\Builder.build2` -* The keys above are named according to a collision avoidance convention. However, there is no requirement on the key name except for uniqueness. -```php -// template-tree/V1/PrimaryActorName/Builder.php - public function build(): PrimaryActorNameInterface - { - $PrimaryActorName = $this->getPrimaryActorNameFactory()->create(); - /** @neighborhoods-buphalo:annotation-processor Neighborhoods\BuphaloTemplateTree\PrimaryActorName\Builder.build1 - */ - /** @neighborhoods-buphalo:annotation-processor Neighborhoods\BuphaloTemplateTree\PrimaryActorName\Builder.build2 - // @TODO - build the object. - throw new \LogicException('Unimplemented build method.'); - */ - - return $PrimaryActorName; - } -``` -```yml -# src/V2/Toe.fabrication.yml -actors: -# ... - /Builder.php: - template: PrimaryActorName/Builder.php - annotation_processors: - Neighborhoods\BuphaloTemplateTree\PrimaryActorName\Builder.build1: - processor_fqcn: \VENDOR\PRODUCT\AnAnnotationProcessor - Neighborhoods\BuphaloTemplateTree\PrimaryActorName\Builder.build2: - processor_fqcn: \VENDOR\PRODUCT\AnotherAnnotationProcessor - static_context_record: - head: 'shoulders' - knees: 'toes' - /Builder.service.yml: - template: PrimaryActorName/Builder.service.yml -# ... -``` -* If no annotation processors are defined then `\Neighborhoods\Buphalo\V1\AnnotationProcessor` is used and the above compiles as -```php -// src/V2/Toe/Builder.php - public function build(): ToeInterface - { - $Toe = $this->getToeFactory()->create(); - - - // @TODO - build the object. - throw new \LogicException('Unimplemented build method.'); - - return $Toe; - } -``` +See [AnnotationProcessors](docs/AnnotationProcessors.md) for more information. ## References * [Symfony Finder Component](https://symfony.com/doc/current/components/finder.html) diff --git a/docs/AnnotationProcessors.md b/docs/AnnotationProcessors.md new file mode 100644 index 0000000..a38c789 --- /dev/null +++ b/docs/AnnotationProcessors.md @@ -0,0 +1,175 @@ +# Annotation Processors +Annotation Processors (APs) are optional configuration tools that allow user space to define dynamic template content. + +## How Annotation Processors Work +When a template includes an appropriate [annotation][Annotations] (a special comment with a particular tag), +Buphalo will replace that comment with the results of the specified Annotation Processor. +If no Annotation Processor is specified, Buphalo uses a default AP that replaces the annotation with the contents of +the comment (sans annotation tag) + +### Template Annotations +The annotations in a template use the following form with two variables: +```php +/** @neighborhoods-buphalo:annotation-processor AnnotationProcessorKey +Default Contents +*/ +``` +- `AnnotationProcessorKey`: Also used in the fabrication file to tie the annotation to the specific AP. +- `Default Contents`: If no AnnotationProcessor is specified in the fabrication file, this is used instead + +#### Example Template +```php +/Builder.php: + template: PrimaryActorName/Builder.php + annotation_processors: + Builder.setters: # The AnnotationProcessorKey + processor_fqcn: \Neighborhoods\Buphalo\V1\AnnotationProcessors\Actor\Builder + static_context_record: + properties: + - record_key: 'bar' + set_method: 'setBar' + - record_Key: 'baz' + set_method: 'setBaz' +``` + + +### Annotation Processors +Each Annotation Processor MUST `implement \Neighborhoods\Buphalo\V1\AnnotationProcessorInterface`. +This will require implementing the following methods: +```php +public function setAnnotationProcessorContext(ContextInterface $Context); +public function getAnnotationProcessorContext(): ContextInterface; +public function getReplacement(): string; +``` +- `setAnnotationProcessorContext`: Used to inform the Annotation Processor of the context that it can use. + - The `ContextInterface` includes a `getStaticContextRecord()` method +- `getAnnotationProcessorContext`: Currently required to set the Default Contents +- `getReplacement`: Returns the text that will replace the annotation in the template + + +#### Example Annotation Processor +```php +getAnnotationProcessorContext()->getStaticContextRecord(); + $calls = []; + + foreach ($context['properties'] as $property) { + $calls[] = sprintf( + ' $PrimaryActorName->%s($this->record[\'%s\']);', + $property['set_method'], + $property['record_key'] + ); + } + + return implode(PHP_EOL, $calls); + } +} +``` + +### Putting it all together +If the above examples are used in `Foo.buphalo.v1.fabrication.yml`, +Buphalo will generate the following to `Foo/Builder.php`: +```php +setBar($this->record['bar']); + $Foo->setBaz($this->record['baz']); + + return $Foo; + } +} +``` + +If The `Builder.setters` Annotation Processor is not specified in `Foo.buphalo.v1.fabrication.yml`, _e.g._ +```yaml +actors: + /Builder.php: + template: PrimaryActorName/Builder.php +``` + +Buphalo will generate the following instead (Note that the annotation was replaced with its default contents) +```php + Date: Wed, 20 Nov 2019 14:12:17 -0600 Subject: [PATCH 2/7] BUPH-21 | add page for General Purpose Annotation Processors --- docs/AnnotationProcessors.md | 10 +++- docs/GeneralPurposeAnnotationProcessors.md | 60 ++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 docs/GeneralPurposeAnnotationProcessors.md diff --git a/docs/AnnotationProcessors.md b/docs/AnnotationProcessors.md index a38c789..6961fe0 100644 --- a/docs/AnnotationProcessors.md +++ b/docs/AnnotationProcessors.md @@ -164,12 +164,18 @@ class Builder { } ``` +### General Purpose Annotation Processors +For more exmaples of Annotation Processors and for processors you can use out-of-the-box, +see [General Purpose Annotation Processors][GPAP] + ### Known Issues - Each Annotation Processor can only be used once. - `getAnnotationProcessorContext` is required to be public ## References -[PHP Annotations][Annotations] -[RFC 2119: Keywords](https://tools.ietf.org/html/rfc2119) +- [General Purpose Annotation Processors][GPAP] +- [PHP Annotations][Annotations] +- [RFC 2119: Keywords](https://tools.ietf.org/html/rfc2119) [Annotations]: https://php-annotations.readthedocs.io/en/latest/UsingAnnotations.html +[GPAP]: GeneralPurposeAnnotationProcessors.md diff --git a/docs/GeneralPurposeAnnotationProcessors.md b/docs/GeneralPurposeAnnotationProcessors.md new file mode 100644 index 0000000..572d1d9 --- /dev/null +++ b/docs/GeneralPurposeAnnotationProcessors.md @@ -0,0 +1,60 @@ +# General Purpose Annotation Processors +Buphalo provides a number of out-of-the box General Purpose Annotation Processors. + +Each General Purpose Annotation Processor listed below has a number of similar qualities: +- Description: A brief description of the Annotation Processor's behavior +- FQCN: The fully qualified class name to use in the `processor_fqcn` field in the [fabrication file][Fabrication File] +- Contract: The expected format of the `static_context_record` field in the [fabrication file][Fabrication File] +- Example: An example template with annotation, fabrication file snippet, and the value after replacement + +## Simple String Replacement + +**Description:** Replaces the annotation with a specified `string` + +**FQCN:** `\Neighborhoods\Buphalo\V1\AnnotationProcessors\SimpleString` + +**Contract:** A single object with a `string` key. + +**Example:** +```php +/** @neighborhoods-buphalo:annotation-processor annotation1 */ +``` +```yaml +annotation1: + processor_fqcn: \Neighborhoods\Buphalo\V1\AnnotationProcessors\SimpleString + static_context_record: + string: 'this is a string' +``` +``` +this is a string +``` + +## Empty String Replacement + +**Description:** Removes the annotation. Useful for when you do not want to include the Default Contents + +**FQCN:** `\Neighborhoods\Buphalo\V1\AnnotationProcessors\EmptyString` + +**Example:** +```php +Before +/** @neighborhoods-buphalo:annotation-processor annotation1 +Default Content +*/ +After +``` +```yaml +annotation1: + processor_fqcn: \Neighborhoods\Buphalo\V1\AnnotationProcessors\EmptyString +``` +```php +Before + +After +``` + +## References +- [Annotation Processors][Annotation Processors] + +[Annotation Processors]: AnnotationProcessors.md +[Fabrication File]: AnnotationProcessors.md#fabrication-file-definitions From a0236113f7696a30a15fc9eb70364360efc92159 Mon Sep 17 00:00:00 2001 From: Jacques Marcotte Date: Wed, 20 Nov 2019 14:20:26 -0600 Subject: [PATCH 3/7] BUPH-21 | clarify newline requirements in Annotation tags --- docs/AnnotationProcessors.md | 1 + docs/GeneralPurposeAnnotationProcessors.md | 3 ++- tests/v1/SimpleStringAP/control/SimpleString.php | 4 +--- tests/v1/SimpleStringAP/templates/PrimaryActorName.php | 7 ++----- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/AnnotationProcessors.md b/docs/AnnotationProcessors.md index 6961fe0..811b626 100644 --- a/docs/AnnotationProcessors.md +++ b/docs/AnnotationProcessors.md @@ -16,6 +16,7 @@ Default Contents ``` - `AnnotationProcessorKey`: Also used in the fabrication file to tie the annotation to the specific AP. - `Default Contents`: If no AnnotationProcessor is specified in the fabrication file, this is used instead +- _N.B._ There MUST be a newline after the `AnnotationProcessorKey` #### Example Template ```php diff --git a/docs/GeneralPurposeAnnotationProcessors.md b/docs/GeneralPurposeAnnotationProcessors.md index 572d1d9..495cabb 100644 --- a/docs/GeneralPurposeAnnotationProcessors.md +++ b/docs/GeneralPurposeAnnotationProcessors.md @@ -17,7 +17,8 @@ Each General Purpose Annotation Processor listed below has a number of similar q **Example:** ```php -/** @neighborhoods-buphalo:annotation-processor annotation1 */ +/** @neighborhoods-buphalo:annotation-processor annotation1 +*/ ``` ```yaml annotation1: diff --git a/tests/v1/SimpleStringAP/control/SimpleString.php b/tests/v1/SimpleStringAP/control/SimpleString.php index 31b3d9f..71588e5 100644 --- a/tests/v1/SimpleStringAP/control/SimpleString.php +++ b/tests/v1/SimpleStringAP/control/SimpleString.php @@ -5,7 +5,5 @@ class SimpleString { - public const TEST_STRING =<< Date: Wed, 20 Nov 2019 14:23:47 -0600 Subject: [PATCH 4/7] BUPH-21 | clarify newline in annotations --- docs/AnnotationProcessors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/AnnotationProcessors.md b/docs/AnnotationProcessors.md index 811b626..048b2fe 100644 --- a/docs/AnnotationProcessors.md +++ b/docs/AnnotationProcessors.md @@ -16,7 +16,7 @@ Default Contents ``` - `AnnotationProcessorKey`: Also used in the fabrication file to tie the annotation to the specific AP. - `Default Contents`: If no AnnotationProcessor is specified in the fabrication file, this is used instead -- _N.B._ There MUST be a newline after the `AnnotationProcessorKey` +- There MUST be a newline between the `AnnotationProcessorKey` and `Default Contents` #### Example Template ```php From 2da3f6255324e22f0bb41d07db9847d6b9a07c7b Mon Sep 17 00:00:00 2001 From: Jacques Marcotte Date: Wed, 20 Nov 2019 16:27:39 -0600 Subject: [PATCH 5/7] BUPH-83 | add Symfony Expression AP --- docs/GeneralPurposeAnnotationProcessors.md | 30 ++++++++++++++++ .../SymfonyExpression.php | 29 +++++++++++++++ .../templates/PrimaryActorName.php | 3 +- tests/v1/SymfonyExpressionAP/.gitignore | 1 + .../control/Expression.php | 9 +++++ tests/v1/SymfonyExpressionAP/run_test | 36 +++++++++++++++++++ .../src/Expression.buphalo.v1.fabrication.yml | 9 +++++ .../templates/PrimaryActorName.php | 9 +++++ 8 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 src/V1/AnnotationProcessors/SymfonyExpression.php create mode 100644 tests/v1/SymfonyExpressionAP/.gitignore create mode 100644 tests/v1/SymfonyExpressionAP/control/Expression.php create mode 100755 tests/v1/SymfonyExpressionAP/run_test create mode 100644 tests/v1/SymfonyExpressionAP/src/Expression.buphalo.v1.fabrication.yml create mode 100644 tests/v1/SymfonyExpressionAP/templates/PrimaryActorName.php diff --git a/docs/GeneralPurposeAnnotationProcessors.md b/docs/GeneralPurposeAnnotationProcessors.md index 495cabb..d365d5d 100644 --- a/docs/GeneralPurposeAnnotationProcessors.md +++ b/docs/GeneralPurposeAnnotationProcessors.md @@ -36,6 +36,8 @@ this is a string **FQCN:** `\Neighborhoods\Buphalo\V1\AnnotationProcessors\EmptyString` +**Contract:** None + **Example:** ```php Before @@ -54,8 +56,36 @@ Before After ``` +## Symfony Expression Language +**Description:** Uses [Symfony Expression Language][Symfony EL] to generate the replacement. +Includes access to the `AnnotationProcessorContext` under the `context` alias. + +**FQCN:** `\Neighborhoods\Buphalo\V1\AnnotationProcessors\SymfonyExpression` + +**Contract:** A single object with an `expression` key. +May include other keys used by the expression. +Other keys can be accessed via `context.getStaticContextRecord()["key"]` + +**Example:** +```php +/** @neighborhoods-buphalo:annotation-processor annotation1 +*/ +``` +```yaml +# Expression.buphalo.v1.fabrication.yml +annotation1: + processor_fqcn: \Neighborhoods\Buphalo\V1\AnnotationProcessors\SymfonyExpression + static_context_record: + expression: 'context.getFabricationFile().getFileName() ~ " " ~ context.getStaticContextRecord()["word"]' + word: 'Language' +``` +```php +Expression Language +``` + ## References - [Annotation Processors][Annotation Processors] [Annotation Processors]: AnnotationProcessors.md [Fabrication File]: AnnotationProcessors.md#fabrication-file-definitions +[Symfony EL]: https://symfony.com/doc/current/components/expression_language.html diff --git a/src/V1/AnnotationProcessors/SymfonyExpression.php b/src/V1/AnnotationProcessors/SymfonyExpression.php new file mode 100644 index 0000000..53e8f9d --- /dev/null +++ b/src/V1/AnnotationProcessors/SymfonyExpression.php @@ -0,0 +1,29 @@ +getAnnotationProcessorContext()->getStaticContextRecord(); + + + $expressionLanguage = new ExpressionLanguage(); + return (string) $expressionLanguage->evaluate( + $context['expression'], + [ + 'context' => $this->getAnnotationProcessorContext() + ] + ); + } +} diff --git a/tests/v1/SimpleStringAP/templates/PrimaryActorName.php b/tests/v1/SimpleStringAP/templates/PrimaryActorName.php index 1aa7522..3b2b2c3 100644 --- a/tests/v1/SimpleStringAP/templates/PrimaryActorName.php +++ b/tests/v1/SimpleStringAP/templates/PrimaryActorName.php @@ -5,6 +5,5 @@ class PrimaryActorName { - public const TEST_STRING = "/** @neighborhoods-buphalo:annotation-processor TestSimpleStringAnnotationProcessor - This should be replaced by something else */"; + public const TEST_STRING = "/** @neighborhoods-buphalo:annotation-processor TestSimpleStringAnnotationProcessor */"; } diff --git a/tests/v1/SymfonyExpressionAP/.gitignore b/tests/v1/SymfonyExpressionAP/.gitignore new file mode 100644 index 0000000..44f14e1 --- /dev/null +++ b/tests/v1/SymfonyExpressionAP/.gitignore @@ -0,0 +1 @@ +fab/* diff --git a/tests/v1/SymfonyExpressionAP/control/Expression.php b/tests/v1/SymfonyExpressionAP/control/Expression.php new file mode 100644 index 0000000..7dc84a8 --- /dev/null +++ b/tests/v1/SymfonyExpressionAP/control/Expression.php @@ -0,0 +1,9 @@ +.php: + template: PrimaryActorName.php + annotation_processors: + TestSymfonyExpressionAnnotationProcessor: + processor_fqcn: \Neighborhoods\Buphalo\V1\AnnotationProcessors\SymfonyExpression + static_context_record: + expression: 'context.getFabricationFile().getFileName() ~ " " ~ context.getStaticContextRecord()["word"]' + word: 'Language' diff --git a/tests/v1/SymfonyExpressionAP/templates/PrimaryActorName.php b/tests/v1/SymfonyExpressionAP/templates/PrimaryActorName.php new file mode 100644 index 0000000..94da6c8 --- /dev/null +++ b/tests/v1/SymfonyExpressionAP/templates/PrimaryActorName.php @@ -0,0 +1,9 @@ + Date: Mon, 9 Dec 2019 10:41:45 -0600 Subject: [PATCH 6/7] BUPH-21 | add test for multiple annotations in a file --- tests/v1/MultipleAPs/.gitignore | 1 + .../control/MultipleAnnotations.php | 11 ++++++ tests/v1/MultipleAPs/run_test | 37 +++++++++++++++++++ ...ipleAnnotations.buphalo.v1.fabrication.yml | 10 +++++ .../templates/PrimaryActorName.php | 13 +++++++ 5 files changed, 72 insertions(+) create mode 100644 tests/v1/MultipleAPs/.gitignore create mode 100644 tests/v1/MultipleAPs/control/MultipleAnnotations.php create mode 100755 tests/v1/MultipleAPs/run_test create mode 100644 tests/v1/MultipleAPs/src/MultipleAnnotations.buphalo.v1.fabrication.yml create mode 100644 tests/v1/MultipleAPs/templates/PrimaryActorName.php diff --git a/tests/v1/MultipleAPs/.gitignore b/tests/v1/MultipleAPs/.gitignore new file mode 100644 index 0000000..44f14e1 --- /dev/null +++ b/tests/v1/MultipleAPs/.gitignore @@ -0,0 +1 @@ +fab/* diff --git a/tests/v1/MultipleAPs/control/MultipleAnnotations.php b/tests/v1/MultipleAPs/control/MultipleAnnotations.php new file mode 100644 index 0000000..f43505e --- /dev/null +++ b/tests/v1/MultipleAPs/control/MultipleAnnotations.php @@ -0,0 +1,11 @@ +.php: + template: PrimaryActorName.php + annotation_processors: + FirstAnnotation: + processor_fqcn: \Neighborhoods\Buphalo\V1\AnnotationProcessors\EmptyString + SecondAnnotation: + processor_fqcn: \Neighborhoods\Buphalo\V1\AnnotationProcessors\SimpleString + static_context_record: + string: "public const SECOND = 'second';" diff --git a/tests/v1/MultipleAPs/templates/PrimaryActorName.php b/tests/v1/MultipleAPs/templates/PrimaryActorName.php new file mode 100644 index 0000000..8ce0870 --- /dev/null +++ b/tests/v1/MultipleAPs/templates/PrimaryActorName.php @@ -0,0 +1,13 @@ + Date: Mon, 9 Dec 2019 10:47:02 -0600 Subject: [PATCH 7/7] BUPH-21 | clarify newline requirements in Annotation Tags --- docs/AnnotationProcessors.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/AnnotationProcessors.md b/docs/AnnotationProcessors.md index 048b2fe..3a93550 100644 --- a/docs/AnnotationProcessors.md +++ b/docs/AnnotationProcessors.md @@ -17,6 +17,8 @@ Default Contents - `AnnotationProcessorKey`: Also used in the fabrication file to tie the annotation to the specific AP. - `Default Contents`: If no AnnotationProcessor is specified in the fabrication file, this is used instead - There MUST be a newline between the `AnnotationProcessorKey` and `Default Contents` + - If no Default Contents exist, there SHOULD be a newline after the `AnnotationProcessorKey`. + - If more than one annotation is used in a template, there MUST be a newline after the `AnnotationProcessorKey` #### Example Template ```php @@ -172,6 +174,7 @@ see [General Purpose Annotation Processors][GPAP] ### Known Issues - Each Annotation Processor can only be used once. - `getAnnotationProcessorContext` is required to be public +- Newlines MUST be present after the `AnnotationProcessorKey` even with no `Default Contents` ## References - [General Purpose Annotation Processors][GPAP]