Skip to content

Commit

Permalink
3.0 (#45)
Browse files Browse the repository at this point in the history
* ISSUE-38: Don't force get method when using type="datetime".

* ISSUE-41: Load Doctrine proxy earlier and correctly!

* ISSUE-42: Add option to sort all properties recursive when serializing. (#47)

* ISSUE-49: Cache annotation in FileSystemCache, split annotation extractor code.

* ISSUE-52: Add explicit exception when a property does not exist.

* 3.0: Remove unused encoder type annotation option.

* 3.0: Move documentation to the github wiki.
  • Loading branch information
BowlOfSoup authored Sep 16, 2020
1 parent 87182af commit c705b78
Show file tree
Hide file tree
Showing 35 changed files with 936 additions and 619 deletions.
48 changes: 33 additions & 15 deletions .php_cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ use PhpCsFixer\Console\ConfigurationResolver;
final class CustomFinder extends Finder
{
/** @var array */
private $excludes = array(
private $excludes = [
'vendor',
);
];

/** @var \PhpCsFixer\Console\ConfigurationResolver|null */
private $configurationResolver = null;
Expand All @@ -23,10 +23,10 @@ final class CustomFinder extends Finder
private $input = '';

/** @var array */
private $files = array();
private $files = [];

/** @var array */
private $directories = array();
private $directories = [];

/**
* Constructor.
Expand Down Expand Up @@ -90,7 +90,7 @@ final class CustomFinder extends Finder
private function initFilesFromStdin(): void
{
$this->input = 'STDIN';
$files = array();
$files = [];
$paths = explode(PHP_EOL, trim(stream_get_contents(STDIN)));
$paths = array_map(function ($path) {
return $this->findFiles($path);
Expand Down Expand Up @@ -124,7 +124,7 @@ final class CustomFinder extends Finder
*/
private function initDirectories(): void
{
$directories = array();
$directories = [];
foreach ($this->files as $file) {
$directory = dirname($file);
foreach ($this->excludes as $exclude) {
Expand Down Expand Up @@ -199,8 +199,10 @@ final class CustomFinder extends Finder

/**
* Execute an external program without broken pipes.
*
* @return string|mixed|null
*/
private function pipedExec(string $command, array &$output = null, int &$returnVar = null): string
private function pipedExec(string $command, array &$output = null, int &$returnVar = null)
{
$contents = '';
$handle = popen($command . '; echo $?', 'r');
Expand All @@ -217,27 +219,43 @@ final class CustomFinder extends Finder
}

/* Based on dev-master|^2.0 of php-cs-fixer */
return Config::create('bowlofsoup/normalizer-bundle', 'BowlOfSoup code style.')
return Config::create()
->setUsingCache(true)
->setRiskyAllowed(true)
->setRules(array(
->setRules([
// default
'@PSR2' => true,
'@Symfony' => true,
// additionally, @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/README.rst
'concat_space' => array('spacing' => 'one'),
'array_syntax' => array('syntax' => 'short'),
// see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/README.rst
'concat_space' => ['spacing' => 'one'],
'array_syntax' => ['syntax' => 'short'],
'blank_line_after_opening_tag' => true,
'no_blank_lines_before_namespace' => false,
'ordered_imports' => true,
'phpdoc_align' => false,
'phpdoc_inline_tag' => false,
'phpdoc_order' => true,
'simplified_null_return' => false,
'binary_operator_spaces' => array(
'binary_operator_spaces' => [
'align_double_arrow' => false,
'align_equals' => false
),
],
'no_unused_imports' => true,
))
'declare_strict_types' => true,
'final_internal_class' => false,
'general_phpdoc_annotation_remove' => ['author', 'copyright', 'category', 'version'],
'global_namespace_import' => ['import_classes' => null],
'list_syntax' => ['syntax' => 'short'],
'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'], // according to the documentation this is the default, but it ain't
'no_php4_constructor' => true,
'no_superfluous_elseif' => false,
'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'remove_inheritdoc' => true],
'php_unit_internal_class' => false,
'php_unit_test_case_static_method_calls' => ['call_type' => 'this'],
'php_unit_test_class_requires_covers' => false,
'phpdoc_no_empty_return' => false,
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
'ordered_class_elements' => ['order' => ['use_trait', 'constant', 'property', 'construct', 'destruct', 'phpunit', 'method']],
'ternary_to_null_coalescing' => true,
])
->setFinder(CustomFinder::create());
236 changes: 13 additions & 223 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,233 +31,23 @@ Quick feature overview

The main features are described in the corresponding annotations below.

Documentation
-----
Documentation on the usage and all supported options can be found [in the wiki](https://github.com/BowlOfSoup/NormalizerBundle/wiki).

1. [What is serialization and normalization?](https://github.com/BowlOfSoup/NormalizerBundle/wiki/What-is-serialization-and-normalization%3F)
2. [Installation](https://github.com/BowlOfSoup/NormalizerBundle/wiki/Installation)
3. [Serializing](https://github.com/BowlOfSoup/NormalizerBundle/wiki/Serializing)
1. [Serialize annotations](https://github.com/BowlOfSoup/NormalizerBundle/wiki/Serialize-annotations)
4. [Normalizing](https://github.com/BowlOfSoup/NormalizerBundle/wiki/Normalizing)
1. [Normalize annotations](https://github.com/BowlOfSoup/NormalizerBundle/wiki/Normalize-annotations)
5. [Translate a value](https://github.com/BowlOfSoup/NormalizerBundle/wiki/Translate-a-value)
1. [Translate annotations](https://github.com/BowlOfSoup/NormalizerBundle/wiki/Translate-annotations)

Why use this normalizer and not ...
-----
- The Bowl Of Soup Normalizer uses an opt-in mechanism by default. You have to indicate which properties must be normalized
- You can indicate a context group, how is the value to be normalized, in which context?
- It's designed with speed in mind. Not packed with features for which you don't use half of it
- It has proven itself in a complex application with 15.000+ daily end users


What is serialization/normalization?
-----

![visual serialization/normalization](https://symfony.com/doc/current/_images/components/serializer/serializer_workflow.svg)
(Source: [Symfony Serializer](https://symfony.com/doc/current/components/serializer.html))

Serialization of an object is visualized on the right side of the above visual. It consists of two steps,
normalization and encoding normalized data.

You can call each step separately (normalize, encode) or directly serialize an object.

# Serializer

Annotations in your model
-----
As we see in the visual the first step in serialization is normalizing. To indicate the way object properties and methods
need to be normalized the "Normalizer" annotations have to be used. See paragraph "Normalizer" for annotation usage.

For serialization two encodings are supported: **JSON** and **XML**.

### Use statement and alias
On top of the object you want to serialize:

use BowlOfSoup\NormalizerBundle\Annotation as Bos;

### Wrapper element
When outputting to a specific encoding you can indicate the wrapping element, this element will be the root node.
You can and should indicate a group (context), use property 'group' to separate context.

/**
* @Bos\Serialize(wrapElement="SomeWrapperElement", group={"default"})
*/
class ClassToBeSerialized
{

### Calling the serializer
The serializer can be injected, but also auto-wired.

<argument type="service" id="bos.serializer" />

You can input an object or an array. If you input an object, it will normalize first, and thus the "Normalize" annotations are used.

Calling the serializer with a group is optional, but certainly recommended.

$result = $this->serializer->serialize($someEntity, 'somegroup');

The result will be a string of data.

# Normalizer

Annotations in your model
-----
The normalizer uses annotations to indicate how the data should be represented. You can use the following annotations properties:

These properties can be used on class/object *properties* and *methods*.

### Use statement and alias
BowlOfSoup\NormalizerBundle\Annotation as Bos;

### Include class property in normalization
Use the following annotation to include the class property in the normalization functionality.

/**
* @Bos\Normalize
*/
private $propertyToBeNormalized

### Maximal depth
To normalize until a certain depth (type="object" / type="collection"). Property can only be set on class level.

/**
* @Bos\Normalize(maxDepth=2)
*/
class ClassToBeNormalized
{

### Skip empty properties
You can omit properties that are empty to be normalized. If the property contains no data (empty, or null) the normalized output will not contain this property.

/**
* @Bos\Normalize(skipEmpty=true)
*/
private $propertyToBeNormalized

This can also be used on class level. All properties which are empty, will now not be normalized.

/**
* @Bos\Normalize(skipEmpty=true)
*/
class ClassToBeNormalized
{

### Group or context support

Use property 'group' to separate context.

/**
* @Bos\Normalize(group={"default"})
* @Bos\Normalize(group={"customgroup", "customgroup2"}, name="something")
*/
private $propertyToBeNormalized

### Name
Use property 'name' to change the key (name) of the class property to be normalized.

/**
* @Bos\Normalize(name="automobile")
*/
private $propertyToBeNormalized

### Type
Indicate if the class property is a of 'special' type. Type can be 'collection', 'object', or 'datetime'.
If an object is empty, value 'null' will be returned.

/**
* @Bos\Normalize(type="DateTime")
* @Bos\Normalize(type="object")
* @Bos\Normalize(type="collection", callback="toListArray")
*/
private $propertyToBeNormalized

### Format
Format the 'DateTime' type. Can only be used for type="DateTime".

/**
* @Bos\Normalize(type="DateTime", format="Y-m-d")
*/
private $propertyToBeNormalized

### Callback
Sometimes you encouter an object for which you still want to use a legacy method, or just a custom method to normalize data for a property to normalize.
if used together with type="object", the callback is the method that is bound to the class property the annotation is set on, if used without type="", the callback relates to a method within the current class.

/**
* @Bos\Normalize(type="object", callback="toListArray")
* @Bos\Normalize(callback="getPropertyToBeNormalized")
*/
private $propertyToBeNormalized

*Note: callbacks can't be used on methods, since a method can surely function as callback.*

### Normalize callback output
It is possible to normalize output from a callback method.
E.g. if you return an array with objects or just a single object from a callback method it will also normalize those objects.

/**
* @Bos\Normalize(callback="legacyMethod", normalizeCallbackResult=true)
* @Bos\Normalize(type="object", callback="legacyMethod", normalizeCallbackResult=true)
* @Bos\Normalize(type="collection", callback="legacyMethod", normalizeCallbackResult=true)
*/
private $propertyToBeNormalized

*Note: callbacks can't be used on methods.*

### Normalize collections
If you have property which contains collection of other entities, you can use the type 'collection'. If you specify a callback, it will be applied to each item of the collection and placed to the result array.

_See paragraph: Type_

### Usage in multiple context
As you can see, per group you can specify different outcomes.

/**
* @Bos\Normalize(group={"somegroup"}, name="automobile")
* @Bos\Normalize(group={"someotherspecialgroup"})
*/
private $propertyToBeNormalized

/**
* @Bos\Normalize(type="DateTime", format="Y-m-d", group={"somegroup"}, name="creationDate")
* @Bos\Normalize(type="DateTime", format="Y", group={"someotherspecialgroup"})
*/
private $propertyToBeNormalized

### Calling the normalizer
The normalizer needs to be injected, but can also be auto-wired.

<argument type="service" id="bos.normalizer" />
Calling the normalizer with a group is optional, but certainly recommended. The result will be an *array*.

$result = $this->normalizer->normalize($someEntity, 'somegroup');
$result = $this->normalizer->normalize(array($someEntity, $anotherEntity), 'somegroup');

Using translations
-----
### Use statement and alias
BowlOfSoup\NormalizerBundle\Annotation as Bos;

### Add the annotation to your property/method

/**
* @Bos\Normalize(group={"somegroup"}, name="automobile")
* @Bos\Translate(group={"translation"})
*/
private $propertyToBeNormalized

This will try to translate the value in `$propertyToBeNormalized`.
By default, the translation must be in a `Resources/translations/messages.en.xliff`.

### Specify your domain (the .xliff file)

/**
* @Bos\Normalize(group={"somegroup"}, name="automobile")
* @Bos\Translate(group={"translation"}, domain="some_domain")
*/
private $propertyToBeNormalized

This tries to find the translation in `Resources/translations/some_domain.en.xliff`.

### Specify your locale (language)

You can specify your locale, if you did not set that globally in Symfony.

/**
* @Bos\Normalize(group={"somegroup"}, name="automobile")
* @Bos\Translate(group={"translation"}, domain="some_domain", locale="nl")
*/
private $propertyToBeNormalized

This tries to find the translation in `Resources/translations/some_domain.nl.xliff`.
Notice the `nl` in the file name (Dutch language).
Loading

0 comments on commit c705b78

Please sign in to comment.