Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Max depth strategy #4

Merged
merged 1 commit into from
Jun 1, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions doc/cookbook/exclusion_strategies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,44 @@ You can then tell the serializer which groups to serialize in your controller::
use JMS\Serializer\SerializationContext;

$serializer->serialize(new BlogPost(), 'json', SerializationContext::create()->setGroups(array('list')));

Limiting serialization depth of some properties
-----------------------------------------------
You can limit the depth of what will be serialized in a property with the
``@MaxDepth`` annotation.
This exclusion strategy is a bit different from the others, because it will
affect the serialized content of others classes than the one you apply the
annotation to.

.. code-block :: php

use JMS\Serializer\Annotation\MaxDepth;

class User
{
private $username;

/** @MaxDepth(1) */
private $friends;

/** @MaxDepth(2) */
private $posts;
}

class Post
{
private $title;

private $author;
}

In this example, serializing a user, because the max depth of the ``$friends``
property is 1, the user friends would be serialized, but not their friends;
and because the the max depth of the ``$posts`` property is 2, the posts would
be serialized, and their author would also be serialized.

You need to tell the serializer to take into account MaxDepth checks::

use JMS\Serializer\SerializationContext;

$serializer->serialize($data, 'json', SerializationContext::create()->enableMaxDepthChecks());
8 changes: 7 additions & 1 deletion doc/reference/annotations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ This annotation can be defined on a property to specifiy to if the property
should be serialized when only serializing specific groups (see
:doc:`../cookbook/exclusion_strategies`).

@MaxDepth
~~~~~~~~~
This annotation can be defined on a property to limit the depth to which the
content will be serialized. It is very useful when a property will contain a
large object graph.

@AccessType
~~~~~~~~~~~
This annotation can be defined on a property, or a class to specify in which way
Expand Down Expand Up @@ -474,4 +480,4 @@ Resulting XML:

.. code-block :: xml

<result name="firstname" value="Adrien"/>
<result name="firstname" value="Adrien"/>
1 change: 1 addition & 0 deletions doc/reference/xml_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ XML Reference
groups="foo,bar"
xml-key-value-pairs="true"
xml-attribute-map="true"
max-depth="2"
>
<!-- You can also specify the type as element which is necessary if
your type contains "<" or ">" characters. -->
Expand Down
1 change: 1 addition & 0 deletions doc/reference/yml_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ YAML Reference
key_attribute_name: foo
entry_name: bar
xml_attribute_map: true
max_depth: 2

handler_callbacks:
serialization:
Expand Down
32 changes: 32 additions & 0 deletions src/JMS/Serializer/Annotation/MaxDepth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* Copyright 2011 Johannes M. Schmitt <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace JMS\Serializer\Annotation;

/**
* @Annotation
* @Target({"PROPERTY","METHOD"})
*/
final class MaxDepth
{
/**
* @Required
* @var integer
*/
public $depth;
}
8 changes: 8 additions & 0 deletions src/JMS/Serializer/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
namespace JMS\Serializer;

use JMS\Serializer\Exception\RuntimeException;
use JMS\Serializer\Exclusion\DepthExclusionStrategy;
use JMS\Serializer\Exclusion\DisjunctExclusionStrategy;
use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
Expand Down Expand Up @@ -173,6 +174,13 @@ public function setGroups($groups)
return $this;
}

public function enableMaxDepthChecks()
{
$this->addExclusionStrategy(new DepthExclusionStrategy());

return $this;
}

public function setSerializeNull($bool)
{
$this->serializeNull = (boolean) $bool;
Expand Down
67 changes: 67 additions & 0 deletions src/JMS/Serializer/Exclusion/DepthExclusionStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

/*
* Copyright 2011 Johannes M. Schmitt <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace JMS\Serializer\Exclusion;

use JMS\Serializer\Context;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;

/**
* @author Adrien Brault <[email protected]>
*/
class DepthExclusionStrategy implements ExclusionStrategyInterface
{
/**
* {@inheritDoc}
*/
public function shouldSkipClass(ClassMetadata $metadata, Context $context)
{
return $this->isTooDeep($context);
}

/**
* {@inheritDoc}
*/
public function shouldSkipProperty(PropertyMetadata $property, Context $context)
{
return $this->isTooDeep($context);
}

private function isTooDeep(Context $context)
{
$depth = $context->getDepth();
$metadataStack = $context->getMetadataStack();

$nthProperty = 0;
// iterate from the first added items to the lasts
for ($i = $metadataStack->count() - 1; $i > 0; $i--) {
$metadata = $metadataStack[$i];
if ($metadata instanceof PropertyMetadata) {
$nthProperty++;
$relativeDepth = $depth - $nthProperty;

if (null !== $metadata->maxDepth && $relativeDepth > $metadata->maxDepth) {
return true;
}
}
}

return false;
}
}
3 changes: 3 additions & 0 deletions src/JMS/Serializer/Metadata/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
use JMS\Serializer\Exception\InvalidArgumentException;
use JMS\Serializer\Annotation\XmlAttributeMap;
use Metadata\Driver\DriverInterface;
use JMS\Serializer\Annotation\MaxDepth;

class AnnotationDriver implements DriverInterface
{
Expand Down Expand Up @@ -189,6 +190,8 @@ public function loadMetadataForClass(\ReflectionClass $class)
$propertyMetadata->inline = true;
} elseif ($annot instanceof XmlAttributeMap) {
$propertyMetadata->xmlAttributeMap = true;
} elseif ($annot instanceof MaxDepth) {
$propertyMetadata->maxDepth = $annot->depth;
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/JMS/Serializer/Metadata/Driver/XmlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ protected function loadMetadataFromFile(\ReflectionClass $class, $path)
$pMetadata->xmlKeyValuePairs = 'true' === (string) $pElem->attributes()->{'xml-key-value-pairs'};
}

if (isset($pElem->attributes()->{'max-depth'})) {
$pMetadata->maxDepth = (int) $pElem->attributes()->{'max-depth'};
}

//we need read-only before setter and getter set, because that method depends on flag being set
if (null !== $readOnly = $pElem->attributes()->{'read-only'}) {
$pMetadata->readOnly = 'true' === strtolower($readOnly);
Expand Down
3 changes: 3 additions & 0 deletions src/JMS/Serializer/Metadata/Driver/YamlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ protected function loadMetadataFromFile(\ReflectionClass $class, $file)
$pMetadata->inline = (Boolean) $pConfig['inline'];
}

if (isset($pConfig['max_depth'])) {
$pMetadata->maxDepth = (int) $pConfig['max_depth'];
}
}
if ((ExclusionPolicy::NONE === $exclusionPolicy && !$isExclude)
|| (ExclusionPolicy::ALL === $exclusionPolicy && $isExpose)) {
Expand Down
3 changes: 3 additions & 0 deletions src/JMS/Serializer/Metadata/PropertyMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class PropertyMetadata extends BasePropertyMetadata
public $inline = false;
public $readOnly = false;
public $xmlAttributeMap = false;
public $maxDepth = null;

private static $typeParser;

Expand Down Expand Up @@ -113,6 +114,7 @@ public function serialize()
$this->inline,
$this->readOnly,
$this->xmlAttributeMap,
$this->maxDepth,
parent::serialize(),
));
}
Expand All @@ -137,6 +139,7 @@ public function unserialize($str)
$this->inline,
$this->readOnly,
$this->xmlAttributeMap,
$this->maxDepth,
$parentStr
) = unserialize($str);

Expand Down
5 changes: 5 additions & 0 deletions tests/JMS/Serializer/Tests/Fixtures/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@

class Node
{
/**
* @Serializer\MaxDepth(2)
*/
public $children;

public $foo = 'bar';

public function __construct($children = array())
{
$this->children = $children;
Expand Down
34 changes: 34 additions & 0 deletions tests/JMS/Serializer/Tests/Fixtures/Tree.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/*
* Copyright 2013 Johannes M. Schmitt <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace JMS\Serializer\Tests\Fixtures;

use JMS\Serializer\Annotation as Serializer;

class Tree
{
/**
* @Serializer\MaxDepth(10)
*/
public $tree;

public function __construct($tree)
{
$this->tree = $tree;
}
}
7 changes: 7 additions & 0 deletions tests/JMS/Serializer/Tests/Metadata/Driver/BaseDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ public function testLoadDiscriminatorSubClass()
$this->assertEquals(array(), $m->discriminatorMap);
}

public function testMaxDepth()
{
$m = $this->getDriver()->loadMetadataForClass(new \ReflectionClass('JMS\Serializer\Tests\Fixtures\Node'));

$this->assertEquals(2, $m->propertyMetadata['children']->maxDepth);
}

/**
* @return DriverInterface
*/
Expand Down
12 changes: 12 additions & 0 deletions tests/JMS/Serializer/Tests/Metadata/Driver/php/Node.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;

$metadata = new ClassMetadata('JMS\Serializer\Tests\Fixtures\Node');

$pMetadata = new PropertyMetadata('JMS\Serializer\Tests\Fixtures\Node', 'children');
$pMetadata->maxDepth = 2;
$metadata->addPropertyMetadata($pMetadata);

return $metadata;
6 changes: 6 additions & 0 deletions tests/JMS/Serializer/Tests/Metadata/Driver/xml/Node.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<serializer>
<class name="JMS\Serializer\Tests\Fixtures\Node">
<property name="children" max-depth="2" />
</class>
</serializer>
4 changes: 4 additions & 0 deletions tests/JMS/Serializer/Tests/Metadata/Driver/yml/Node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
JMS\Serializer\Tests\Fixtures\Node:
properties:
children:
max_depth: 2
24 changes: 24 additions & 0 deletions tests/JMS/Serializer/Tests/Serializer/BaseSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use JMS\Serializer\Handler\PhpCollectionHandler;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\Tests\Fixtures\Discriminator\Car;
use JMS\Serializer\Tests\Fixtures\Tree;
use PhpCollection\Sequence;
use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\Translation\IdentityTranslator;
Expand Down Expand Up @@ -82,6 +83,8 @@
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use PhpCollection\Map;
use JMS\Serializer\Exclusion\DepthExclusionStrategy;
use JMS\Serializer\Tests\Fixtures\Node;

abstract class BaseSerializationTest extends \PHPUnit_Framework_TestCase
{
Expand Down Expand Up @@ -708,6 +711,27 @@ public function testPolymorphicObjectsInvalidDeserialization()
);
}

public function testDepthExclusionStrategy()
{
$context = SerializationContext::create()
->addExclusionStrategy(new DepthExclusionStrategy())
;

$data = new Tree(
new Node(array(
new Node(array(
new Node(array(
new Node(array(
new Node(),
)),
)),
)),
))
);

$this->assertEquals($this->getContent('tree'), $this->serializer->serialize($data, $this->getFormat(), $context));
}

abstract protected function getContent($key);
abstract protected function getFormat();

Expand Down
Loading