Skip to content

Commit

Permalink
Added support for multilevel embed via 'associated' option
Browse files Browse the repository at this point in the history
    Also supports parsing options through to embeds (for example 'accessibleFields')
  • Loading branch information
lilHermit committed Aug 24, 2018
1 parent 13af84c commit 642beb7
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 20 deletions.
48 changes: 28 additions & 20 deletions src/Marshaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,37 +59,24 @@ public function __construct(Index $index)
*/
public function one(array $data, array $options = [])
{
$options += ['associated' => []];

$entityClass = $this->index->entityClass();
$entity = $this->createAndHydrate($entityClass, $data, $options);
$entity->setSource($this->index->getRegistryAlias());

foreach ($this->index->embedded() as $embed) {
$property = $embed->property();
$alias = $embed->getAlias();
if (isset($data[$property])) {
if (isset($options['associated'][$alias])) {
$entity->set($property, $this->newNested($embed, $data[$property], $options['associated'][$alias]));
} elseif (in_array($alias, $options['associated'])) {
$entity->set($property, $this->newNested($embed, $data[$property]));
}
}
}

return $entity;
}

/**
* Creates and Hydrates Document whilst honouring accessibleFields etc
*
* @param string $class Class name of Document to create
* @param array $data The data to hydrate with
* @param array $options Options to control the hydration
* @param string $class Class name of Document to create
* @param array $data The data to hydrate with
* @param array $options Options to control the hydration
* @param string $indexClass Index class to get embeds from (for nesting)
*
* @return Document
*/
protected function createAndHydrate($class, array $data, array $options = [])
protected function createAndHydrate($class, array $data, array $options = [], $indexClass = null)
{
$entity = new $class();

Expand All @@ -108,6 +95,27 @@ protected function createAndHydrate($class, array $data, array $options = [])
unset($data[$badKey]);
}

if ($indexClass === null) {
$embeds = $this->index->embedded();
} else {
$index = IndexRegistry::get($indexClass);
$embeds = $index->embedded();
}

foreach ($embeds as $embed) {
$property = $embed->property();
$alias = $embed->getAlias();
if (isset($data[$property])) {
if (isset($options['associated'][$alias])) {
$entity->set($property, $this->newNested($embed, $data[$property], $options['associated'][$alias]));
unset($data[$property]);
} elseif (in_array($alias, $options['associated'])) {
$entity->set($property, $this->newNested($embed, $data[$property]));
unset($data[$property]);
}
}
}

if (!isset($options['fieldList'])) {
$entity->set($data);
} else {
Expand All @@ -134,14 +142,14 @@ protected function newNested(Embedded $embed, array $data, array $options = [])
{
$class = $embed->entityClass();
if ($embed->type() === Embedded::ONE_TO_ONE) {
return $this->createAndHydrate($class, $data, $options);
return $this->createAndHydrate($class, $data, $options, $embed->indexClass());
}

if ($embed->type() === Embedded::ONE_TO_MANY) {
$children = [];
foreach ($data as $row) {
if (is_array($row)) {
$children[] = $this->createAndHydrate($class, $row, $options);
$children[] = $this->createAndHydrate($class, $row, $options, $embed->indexClass());
}
}

Expand Down
173 changes: 173 additions & 0 deletions tests/TestCase/MarshallerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
*/
namespace Cake\ElasticSearch\Test;

use Cake\Core\Configure;
use Cake\Datasource\ConnectionManager;
use Cake\ElasticSearch\Document;
use Cake\ElasticSearch\Index;
use Cake\ElasticSearch\Marshaller;
use Cake\TestSuite\TestCase;
use TestApp\Model\Index\AccountsIndex;

/**
* Test entity for mass assignment.
Expand Down Expand Up @@ -804,4 +806,175 @@ public function testMergeManyBadEntityData()
$this->assertCount(1, $result);
$this->assertEquals($data[0], $result[0]->toArray());
}

/**
* test marshalling One with multi level embed
*
* @return void
*/
public function testMarshallOneMultiLevelEmbed()
{
Configure::write('App.namespace', 'TestApp');

$data = [
'address' => '123 West Street',
'users' => [
[
'first_name' => 'Mark',
'last_name' => 'Story',
'user_type' => [
'label' => 'Admin'
]
],
['first_name' => 'Clare', 'last_name' => 'Smith'],
]
];
$options = [
'associated' => [
'User' => [
'associated' => [
'UserType' => []
]
]
]
];

$index = new AccountsIndex();

$marshaller = new Marshaller($index);
$result = $marshaller->one($data, $options);

$this->assertCount(2, $result->users);
$this->assertSame('123 West Street', $result->address);
$this->assertInstanceOf('TestApp\Model\Document\User', $result->users[0]);
$this->assertSame('Mark', $result->users[0]->first_name);
$this->assertSame('Story', $result->users[0]->last_name);
$this->assertInstanceOf('TestApp\Model\Document\User', $result->users[1]);
$this->assertSame('Clare', $result->users[1]->first_name);
$this->assertSame('Smith', $result->users[1]->last_name);
$this->assertInstanceOf('Cake\ElasticSearch\Document', $result->users[0]->user_type);
$this->assertSame('Admin', $result->users[0]->user_type->label);
}

/**
* test marshalling One with multi level embed (with AccessibleFields)
*
* @return void
*/
public function testMarshallOneMultiLevelEmbedWithAccessibleFields()
{
Configure::write('App.namespace', 'TestApp');

$data = [
'address' => '123 West Street',
'remove_this' => 'something',
'users' => [
[
'first_name' => 'Mark',
'last_name' => 'Story',
'user_type' => [
'label' => 'Admin',
'level' => 21
]
],
['first_name' => 'Clare', 'last_name' => 'Smith'],
]
];
$options = [
'accessibleFields' => ['remove_this' => false],
'associated' => [
'User' => [
'accessibleFields' => ['last_name' => false],
'associated' => [
'UserType' => [
'accessibleFields' => ['level' => false]
]
]
]
]
];

$index = new AccountsIndex();

$marshaller = new Marshaller($index);
$result = $marshaller->one($data, $options);

$this->assertCount(2, $result->users);
$this->assertNull($result->remove_this);
$this->assertInstanceOf('TestApp\Model\Document\User', $result->users[0]);
$this->assertNull($result->users[0]->last_name);
$this->assertInstanceOf('TestApp\Model\Document\User', $result->users[1]);
$this->assertNull($result->users[1]->last_name);
$this->assertInstanceOf('Cake\ElasticSearch\Document', $result->users[0]->user_type);
$this->assertNull($result->users[0]->user_type->level);
}

/**
* test marshalling Many with multi level embed
*
* @return void
*/
public function testMarshallManyMultiLevelEmbed()
{
Configure::write('App.namespace', 'TestApp');

$data = [
[
'address' => '123 West Street',
'users' => [
[
'first_name' => 'Mark',
'last_name' => 'Story',
'user_type' => [
'label' => 'Admin'
]
],
['first_name' => 'Clare', 'last_name' => 'Smith']
]
],
[
'address' => '87 Grant Avenue',
'users' => [
[
'first_name' => 'Colin',
'last_name' => 'Thomas',
'user_type' => [
'label' => 'Admin'
]
]
]
]
];
$options = [
'associated' => [
'User' => [
'associated' => [
'UserType' => []
]
]
]
];

$index = new AccountsIndex();

$marshaller = new Marshaller($index);
$result = $marshaller->many($data, $options);

$this->assertCount(2, $result);
$this->assertCount(2, $result[0]->users);
$this->assertCount(1, $result[1]->users);
$this->assertSame('123 West Street', $result[0]->address);
$this->assertSame('87 Grant Avenue', $result[1]->address);
$this->assertInstanceOf('TestApp\Model\Document\User', $result[0]->users[0]);
$this->assertInstanceOf('TestApp\Model\Document\User', $result[0]->users[1]);
$this->assertInstanceOf('TestApp\Model\Document\User', $result[1]->users[0]);
$this->assertSame('Mark', $result[0]->users[0]->first_name);
$this->assertSame('Story', $result[0]->users[0]->last_name);
$this->assertSame('Clare', $result[0]->users[1]->first_name);
$this->assertSame('Smith', $result[0]->users[1]->last_name);
$this->assertSame('Colin', $result[1]->users[0]->first_name);
$this->assertSame('Thomas', $result[1]->users[0]->last_name);
$this->assertInstanceOf('Cake\ElasticSearch\Document', $result[0]->users[0]->user_type);
$this->assertSame('Admin', $result[0]->users[0]->user_type->label);
}
}
9 changes: 9 additions & 0 deletions tests/testapp/TestApp/src/Model/Document/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace TestApp\Model\Document;

use Cake\ElasticSearch\Document;

class User extends Document
{
}
25 changes: 25 additions & 0 deletions tests/testapp/TestApp/src/Model/Index/AccountsIndex.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace TestApp\Model\Index;

use Cake\ElasticSearch\Index;

class AccountsIndex extends Index
{
public function initialize(array $config)
{
parent::initialize($config);

$this->embedMany('User', ['property' => 'users']);
}

public function getName()
{
return 'accounts';
}

public function getType()
{
return 'accounts';
}
}
26 changes: 26 additions & 0 deletions tests/testapp/TestApp/src/Model/Index/UsersIndex.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace TestApp\Model\Index;

use Cake\ElasticSearch\Index;

class UsersIndex extends Index
{

public function initialize(array $config)
{
parent::initialize($config);

$this->embedOne('UserType');
}

public function getName()
{
return 'users';
}

public function getType()
{
return 'users';
}
}

0 comments on commit 642beb7

Please sign in to comment.