Skip to content
This repository has been archived by the owner on Feb 4, 2021. It is now read-only.

Commit

Permalink
algolia#283: Add support for custom serialization groups
Browse files Browse the repository at this point in the history
  • Loading branch information
kiler129 committed Feb 19, 2019
1 parent 5e9889b commit 805bd55
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 10 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ CHANGELOG
UNRELEASED
----------

* Feature: Custom Serialization Groups & Access To Root Entity Class On Normalization - PR [#287](https://github.com/algolia/search-bundle/pull/287)

The feature adds new `rootEntity` to normalization context, which allows distinguishing which index is processed
in a custom normalizer. In addition now it is possible to specify custom normalization groups (by default only
`searchable` group is used).
In order to use custom serialization groups the `enable_serializer_groups` must be enabled.


3.4.0
----------

Expand Down
21 changes: 20 additions & 1 deletion src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Algolia\SearchBundle\DependencyInjection;

use Algolia\SearchBundle\Searchable;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use function method_exists;
Expand Down Expand Up @@ -50,15 +51,33 @@ public function getConfigTreeBuilder()
->arrayNode('indices')
->useAttributeAsKey('name')
->arrayPrototype()
->beforeNormalization()
->ifTrue(function($v) {
return !empty($v['serializer_groups']) &&
(!isset($v['enable_serializer_groups']) || !$v['enable_serializer_groups']);
})
->thenInvalid('In order to specify "serializer_groups" you need to enable "enable_serializer_groups"')
->end()
->children()
->scalarNode('class')
->isRequired()
->cannotBeEmpty()
->end()
->booleanNode('enable_serializer_groups')
->info('When set to true, it will call normalize method with an extra groups parameter "groups" => [Searchable::NORMALIZATION_GROUP]')
->info(
'When set to true, it will call normalize method with an extra groups ' .
'defined in "serializer_groups" (Searchable::NORMALIZATION_GROUP by default)'
)
->defaultFalse()
->end()
->arrayNode('serializer_groups')
->info('List of serializer groups to use while serializing. This option requires "enable_serializer_groups" set to true.')
->beforeNormalization()
->castToArray()
->end()
->scalarPrototype()->end()
->defaultValue([Searchable::NORMALIZATION_GROUP])
->end()
->scalarNode('index_if')
->info('Property accessor path (like method or property name) used to decide if an entry should be indexed.')
->defaultNull()
Expand Down
24 changes: 21 additions & 3 deletions src/IndexManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class IndexManager implements IndexManagerInterface
private $aggregators;
private $entitiesAggregators;
private $classToIndexMapping;

/**
* @var array<string, string[]> Maps indexed classes to their groups (if configured for the index)
*/
private $classToSerializerGroupMapping;
private $indexIfMapping;
private $normalizer;
Expand Down Expand Up @@ -176,8 +180,17 @@ public function shouldBeIndexed($entity)
return true;
}

private function canUseSerializerGroup($className)
/**
* @param string $className
*
* @return string[]|null List of groups or null for entity with groups disabled
*/
private function getSerializerGroup($className)
{
if (!isset($this->classToSerializerGroupMapping[$className])) {
return null;
}

return $this->classToSerializerGroupMapping[$className];
}

Expand Down Expand Up @@ -240,7 +253,9 @@ private function setClassToSerializerGroupMapping()
{
$mapping = [];
foreach ($this->configuration['indices'] as $indexDetails) {
$mapping[$indexDetails['class']] = $indexDetails['enable_serializer_groups'];
if ($indexDetails['enable_serializer_groups']) {
$mapping[$indexDetails['class']] = $indexDetails['serializer_groups'];
}
}

$this->classToSerializerGroupMapping = $mapping;
Expand Down Expand Up @@ -271,13 +286,16 @@ private function forEachChunk(ObjectManager $objectManager, array $entities, $op
$searchableEntitiesChunk = [];
foreach ($chunk as $entity) {
$entityClassName = ClassUtils::getClass($entity);
$groups = $this->getSerializerGroup($entityClassName);

$searchableEntitiesChunk[] = new SearchableEntity(
$this->getFullIndexName($entityClassName),
$entity,
$objectManager->getClassMetadata($entityClassName),
$this->normalizer,
['useSerializerGroup' => $this->canUseSerializerGroup($entityClassName)]
$groups === null
? ['useSerializerGroup' => false]
: ['useSerializerGroup' => true, 'serializerGroups' => $groups]
);
}

Expand Down
28 changes: 25 additions & 3 deletions src/SearchableEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Algolia\SearchBundle;

use Doctrine\ORM\Mapping\ClassMetadata;
use JMS\Serializer\ArrayTransformerInterface;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
Expand All @@ -10,8 +11,21 @@ class SearchableEntity implements SearchableEntityInterface
{
protected $indexName;
protected $entity;

/**
* @var ClassMetadata
*/
protected $entityMetadata;
protected $useSerializerGroups;

/**
* @var string[] List of groups to use during serialization
*/
protected $serializerGroups = [Searchable::NORMALIZATION_GROUP];

/**
* @var bool
*/
protected $useSerializerGroups = false;

private $id;
private $normalizer;
Expand All @@ -22,7 +36,14 @@ public function __construct($indexName, $entity, $entityMetadata, $normalizer, a
$this->entity = $entity;
$this->entityMetadata = $entityMetadata;
$this->normalizer = $normalizer;
$this->useSerializerGroups = isset($extra['useSerializerGroup']) && $extra['useSerializerGroup'];

if (isset($extra['useSerializerGroup']) && $extra['useSerializerGroup']) {
$this->useSerializerGroups = true;
}

if (isset($extra['serializerGroups']) && \is_array($extra['serializerGroups'])) {
$this->serializerGroups = $extra['serializerGroups'];
}

$this->setId();
}
Expand All @@ -35,11 +56,12 @@ public function getIndexName()
public function getSearchableArray()
{
$context = [
'rootEntity' => $this->entityMetadata->name,
'fieldsMapping' => $this->entityMetadata->fieldMappings,
];

if ($this->useSerializerGroups) {
$context['groups'] = [Searchable::NORMALIZATION_GROUP];
$context['groups'] = $this->serializerGroups;
}

if ($this->normalizer instanceof NormalizerInterface) {
Expand Down
1 change: 1 addition & 0 deletions tests/Normalizer/CommentNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class CommentNormalizer implements NormalizerInterface
public function normalize($object, $format = null, array $context = array())
{
return [
'original_class' => \md5($context['rootEntity']), //prevent skewing results with "TestApp"
'content' => $object->getContent(),
'post_title' => $object->getPost()->getTitle(),
];
Expand Down
14 changes: 13 additions & 1 deletion tests/TestApp/Entity/Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Algolia\SearchBundle\TestApp\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

/**
* @ORM\Entity
Expand All @@ -15,7 +16,7 @@ class Image
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*
* @Groups({"searchable"})
*/
private $id;

Expand All @@ -30,6 +31,9 @@ public function __construct(array $attributes = [])
$this->url = isset($attributes['url']) ? $attributes['url'] : '/wp-content/uploads/flamingo.jpg';
}

/**
* @Groups({"searchable"})
*/
public function getId()
{
return $this->id;
Expand All @@ -49,4 +53,12 @@ public function setUrl($url)
{
$this->url = $url;
}

/**
* @Groups({"searchableCustom"})
*/
public function getCustomVirtualProperty()
{
return 'here';
}
}
22 changes: 21 additions & 1 deletion tests/TestCase/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,19 @@ public function dataTestConfigurationTree()
"prefix" => "sf_",
"indices" => [
['name' => 'posts', 'class' => 'App\Entity\Post', 'index_if' => null],
['name' => 'tags', 'class' => 'App\Entity\Tag', 'enable_serializer_groups' => true, 'index_if' => null],
[
'name' => 'tags',
'class' => 'App\Entity\Tag',
'enable_serializer_groups' => true,
'index_if' => null,
],
[
'name' => 'comments',
'class' => 'App\Entity\Comment',
'enable_serializer_groups' => true,
'serializer_groups' => ['foo', 'bar'],
'index_if' => null,
],
],
],[
"prefix" => "sf_",
Expand All @@ -73,11 +85,19 @@ public function dataTestConfigurationTree()
'posts' => [
'class' => 'App\Entity\Post',
'enable_serializer_groups' => false,
'serializer_groups' => ['searchable'],
'index_if' => null,
],
'tags' => [
'class' => 'App\Entity\Tag',
'enable_serializer_groups' => true,
'serializer_groups' => ['searchable'],
'index_if' => null,
],
'comments' => [
'class' => 'App\Entity\Comment',
'enable_serializer_groups' => true,
'serializer_groups' => ['foo', 'bar'],
'index_if' => null,
],
],
Expand Down
68 changes: 67 additions & 1 deletion tests/TestCase/SerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Algolia\SearchBundle\Searchable;
use Algolia\SearchBundle\SearchableEntity;
use Algolia\SearchBundle\TestApp\Entity\Comment;
use Algolia\SearchBundle\TestApp\Entity\Image;
use Algolia\SearchBundle\TestApp\Entity\Post;
use Algolia\SearchBundle\TestApp\Entity\Tag;
use Algolia\SearchBundle\Normalizer\CommentNormalizer;
Expand Down Expand Up @@ -70,8 +71,9 @@ public function testSimpleEntityToSearchableArray()
[
"content" => "a great comment",
"post_title" => "a simple post",
"original_class" => \md5(Post::class)
]
],
]
];

$this->assertEquals($expected, $searchablePost->getSearchableArray());
Expand Down Expand Up @@ -114,6 +116,69 @@ public function testEntityWithAnnotationsToSearchableArray()
$this->assertEquals($expected, $searchablePost->getSearchableArray());
}

public function annotatedEntityContextProvider()
{
return [
[
['useSerializerGroup' => false], //Grouping disabled -> all properties will be serialized
['id' => 42, 'url' => 'http://www.example.com', 'customVirtualProperty' => 'here']
],
[
//As in Symfony Serializer empty groups array will return no result
['useSerializerGroup' => true, 'serializerGroups' => []],
[]
],
[
['useSerializerGroup' => true], //Ensure legacy method still works
['id' => 42]
],
[
//This should work exactly like legacy above
['useSerializerGroup' => true, 'serializerGroups' => ['searchable']],
['id' => 42]
],

[
['useSerializerGroup' => true, 'serializerGroups' => ['unknownGroup']],
[]
],
[
['useSerializerGroup' => true, 'serializerGroups' => ['searchableCustom']],
['customVirtualProperty' => 'here']
],
[
['useSerializerGroup' => true, 'serializerGroups' => ['searchable', 'searchableCustom']],
['id' => 42, 'customVirtualProperty' => 'here']
],
];
}

/**
* @dataProvider annotatedEntityContextProvider
*/
public function testEntityWithCustomSerializationGroupsToSearchableArray($extra, $expectedOutput)
{
$image = new Image(
[
'id' => 42,
'url' => 'http://www.example.com',
]
);
$postMeta = $this->get('doctrine')
->getManager()
->getClassMetadata(Image::class);

$searchablePost = new SearchableEntity(
'images',
$image,
$postMeta,
$this->get('serializer'),
$extra
);

$this->assertEquals($expectedOutput, $searchablePost->getSearchableArray());
}

public function testNormalizableEntityToSearchableArray()
{
$datetime = new \DateTime();
Expand Down Expand Up @@ -163,6 +228,7 @@ public function testDedicatedNormalizer()
$expected = [
"content" => "hey, this is a comment",
"post_title" => "Another super post",
"original_class" => \md5(Comment::class)
];

$this->assertEquals($expected, $searchableComment->getSearchableArray());
Expand Down

0 comments on commit 805bd55

Please sign in to comment.