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 13, 2019
1 parent 53569ed commit b52e683
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 15 deletions.
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
21 changes: 18 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 @@ -277,7 +292,7 @@ private function forEachChunk(ObjectManager $objectManager, array $entities, $op
$entity,
$objectManager->getClassMetadata($entityClassName),
$this->normalizer,
['useSerializerGroup' => $this->canUseSerializerGroup($entityClassName)]
['serializerGroups' => $this->getSerializerGroup($entityClassName)]
);
}

Expand Down
40 changes: 32 additions & 8 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,19 +11,41 @@ class SearchableEntity implements SearchableEntityInterface
{
protected $indexName;
protected $entity;

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

/**
* @var string[]|null List of groups to use during serialization, or null if groups are disabled
*/
protected $serializerGroups = [];

private $id;
private $normalizer;

public function __construct($indexName, $entity, $entityMetadata, $normalizer, array $extra = [])
{
$this->indexName = $indexName;
$this->entity = $entity;
$this->entityMetadata = $entityMetadata;
$this->normalizer = $normalizer;
$this->useSerializerGroups = isset($extra['useSerializerGroup']) && $extra['useSerializerGroup'];
$this->indexName = $indexName;
$this->entity = $entity;
$this->entityMetadata = $entityMetadata;
$this->normalizer = $normalizer;
$this->serializerGroups = isset($extra['serializerGroups']) ? $extra['serializerGroups'] : null;

if (isset($extra['useSerializerGroup']) && $extra['useSerializerGroup']) {
@\trigger_error(
'Passing "useSerializerGroup" SearchableEntity is deprecated, pass "serializerGroups" with ' .
'list of groups, or null to disable',
\E_USER_DEPRECATED
);

if ($this->serializerGroups === null) {
$this->serializerGroups = [Searchable::NORMALIZATION_GROUP];
} elseif (!\in_array(Searchable::NORMALIZATION_GROUP, $this->serializerGroups)) {
$this->serializerGroups[] = Searchable::NORMALIZATION_GROUP;
}
}

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

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

if ($this->normalizer instanceof NormalizerInterface) {
Expand Down
11 changes: 10 additions & 1 deletion tests/Normalizer/CommentNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,19 @@ class CommentNormalizer implements NormalizerInterface
{
public function normalize($object, $format = null, array $context = array())
{
return [
$out = [
'content' => $object->getContent(),
'post_title' => $object->getPost()->getTitle(),
];


//This should ALWAYS exists, however it seems that aggregation looses context
// @see https://github.com/algolia/search-bundle/issues/286
if (isset($context['rootEntity'])) {
$out['originalClass'] = $context['rootEntity'];
}

return $out;
}

public function supportsNormalization($data, $format = null)
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
65 changes: 65 additions & 0 deletions 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,10 @@ public function testSimpleEntityToSearchableArray()
[
"content" => "a great comment",
"post_title" => "a simple post",
"originalClass" => Post::class
]
],
"secretProperty" => "secret"
];

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

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

[
['serializerGroups' => ['unknownGroup']],
[]
],
[
['serializerGroups' => ['searchableCustom']],
['customVirtualProperty' => 'here']
],
[
['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 +227,7 @@ public function testDedicatedNormalizer()
$expected = [
"content" => "hey, this is a comment",
"post_title" => "Another super post",
"originalClass" => Comment::class
];

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

0 comments on commit b52e683

Please sign in to comment.