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

Commit

Permalink
PHPCR-ODM enhancer
Browse files Browse the repository at this point in the history
- Explode class metadata
- Do not generate for non-existing routes
- Added support for children links (Sylius)
  • Loading branch information
dantleech committed Jul 16, 2016
1 parent 012d553 commit 96c72ef
Show file tree
Hide file tree
Showing 9 changed files with 335 additions and 24 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
language: php

php:
- 5.5
- 5.6
- 7.0
- hhvm
Expand Down
25 changes: 25 additions & 0 deletions Description/Descriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,25 @@ final class Descriptor
{
/**
* Alias for the resource type for example `app.page`.
* Value should be a scalar string.
*/
const TYPE_ALIAS = 'type.alias';

/**
* Humanized representation of the resource type.
* Value should be a scalar string.
*/
const TYPE_TITLE = 'type.title';

/**
* Title of the actual payload, e.g. "My Blog Post".
* Value should be a scalar string.
*/
const PAYLOAD_TITLE = 'title';

/**
* Descriptors for HTML links.
* Values should be either a valid URI.
*/
const LINK_EDIT_HTML = 'link.edit.html';
const LINK_CREATE_HTML = 'link.create.html';
Expand All @@ -42,13 +46,34 @@ final class Descriptor
const LINK_SHOW_HTML = 'link.show.html';
const LINK_LIST_HTML = 'link.list.html';

/**
* Array of links for creating child resources.
*/
const LINKS_CREATE_CHILD_HTML = 'links.create_child.html';

/**
* Descriptors for REST links.
* Values should be either a valid URI.
*/
const LINK_EDIT_REST = 'link.edit.rest';
const LINK_CREATE_REST = 'link.create.rest';
const LINK_UPDATE_REST = 'link.update.rest';
const LINK_REMOVE_REST = 'link.remove.rest';
const LINK_SHOW_REST = 'link.show.rest';
const LINK_LIST_REST = 'link.show.rest';

/**
* Permitted children types for this resource.
* Value should be a scalar array, e.g. [ `stdClass`, `FooClass` ].
*
* NOTE: This should be an explicit list of types and should not include
* interfaces are abstract class names.
*/
const CHILDREN_TYPES = 'children.types';

/**
* If children are allowed to be added to this resource.
* Value should be a boolean.
*/
const CHILDREN_ALLOW = 'children.allow';
}
69 changes: 69 additions & 0 deletions Description/Enhancer/Doctrine/PhpcrOdmEnhancer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Cmf\Component\Resource\Description\Enhancer\Doctrine;

use Symfony\Cmf\Component\Resource\Description\DescriptionEnhancerInterface;
use Symfony\Cmf\Component\Resource\Description\Description;
use Puli\Repository\Api\Resource\PuliResource;
use Symfony\Cmf\Component\Resource\Repository\Resource\CmfResource;
use Symfony\Cmf\Component\Resource\Description\Descriptor;
use Doctrine\ODM\PHPCR\Mapping\ClassMetadataFactory;
use Doctrine\Common\Util\ClassUtils;

/**
* Add descriptors from the Doctrine PHPCR ODM.
*
* @author Daniel Leech <[email protected]>
*/
class PhpcrOdmEnhancer implements DescriptionEnhancerInterface
{
private $metadataFactory;

public function __construct(ClassMetadataFactory $metadataFactory)
{
$this->metadataFactory = $metadataFactory;
}

/**
* {@inheritdoc}
*/
public function enhance(Description $description)
{
$metadata = $this->metadataFactory->getMetadataFor($description->getResource()->getPayloadType());
$childClasses = $metadata->getChildClasses();
$childTypes = [];

// explode the allowed types into concrete classes
foreach ($this->metadataFactory->getAllMetadata() as $childMetadata) {
foreach ($childClasses as $childClass) {
if ($childClass == $childMetadata->name || $childMetadata->getReflectionClass()->isSubclassOf($childClass)) {
$childTypes[] = $childMetadata->name;
}
}
}

$description->set(Descriptor::CHILDREN_ALLOW, !$metadata->isLeaf());
$description->set(Descriptor::CHILDREN_TYPES, $childTypes);
}

/**
* {@inheritdoc}
*/
public function supports(PuliResource $resource)
{
if (false === $resource instanceof CmfResource) {
return false;
}

return $this->metadataFactory->hasMetadataFor(ClassUtils::getRealClass($resource->getPayloadType()));
}
}
54 changes: 47 additions & 7 deletions Description/Enhancer/Sylius/ResourceEnhancer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
use Sylius\Bundle\ResourceBundle\Controller\RequestConfigurationFactory;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Cmf\Component\Resource\Description\Descriptor;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\HttpFoundation\Request;
use Sylius\Component\Resource\Metadata\Metadata;

/**
* Add descriptors from the Sylius Resource component.
Expand Down Expand Up @@ -81,13 +84,26 @@ public function enhance(Description $description)
];

foreach ($map as $descriptor => $action) {
$url = $this->urlGenerator->generate(
$configuration->getRouteName($action),
[
'id' => $payload->getId(),
]
);
$description->set($descriptor, $url);

// note that some resources may not have routes
// registered with sonata (f.e. folder resources)
// so we ignore route-not-found exceptions.
try {
$url = $this->urlGenerator->generate(
$configuration->getRouteName($action),
[
'id' => $payload->getId(),
]
);
$description->set($descriptor, $url);
} catch (RouteNotFoundException $e) {
}
}

// if a previous enhancer has set the children types descriptor, then
// we can generate the LINKS_CREATE_CHILD_HTML descriptor.
if ($description->has(Descriptor::CHILDREN_TYPES)) {
$this->processChildrenTypes($description, $metadata, $request, $payload);
}
}

Expand All @@ -108,4 +124,28 @@ public function supports(PuliResource $resource)

return true;
}

private function processChildrenTypes(Description $description, Metadata $metadata, Request $request, $payload)
{
$childClasses = $description->get(Descriptor::CHILDREN_TYPES);
$childLinks = [];

foreach ($childClasses as $childClass) {
try {
$metadata = $this->registry->getByClass($childClass);
} catch (\InvalidArgumentException $e) {
continue;
}

$configuration = $this->requestConfigurationFactory->create($metadata, $request);

$url = $this->urlGenerator->generate(
$configuration->getRouteName('create')
);

$childLinks[$metadata->getAlias()] = $url.'?parent='.$payload->getId();
}

$description->set(Descriptor::LINKS_CREATE_CHILD_HTML, $childLinks);
}
}
4 changes: 2 additions & 2 deletions Repository/Api/EditableRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ interface EditableRepository extends PuliEditableRepository
*
* @return int
*
* @throws InvalidArgumentException If the sourceQuery is invalid.
* @throws UnsupportedLanguageException If the language is not supported.
* @throws InvalidArgumentException If the sourceQuery is invalid
* @throws UnsupportedLanguageException If the language is not supported
*/
public function move($sourceQuery, $targetPath, $language = 'glob');

Expand Down
2 changes: 1 addition & 1 deletion Repository/Resource/CmfResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function getPayloadType()
/**
* Returns additional, implementation-specific data attached to the resource.
*
* @return mixed The payload of the resource.
* @return mixed The payload of the resource
*/
public function getPayload()
{
Expand Down
143 changes: 143 additions & 0 deletions Tests/Unit/Description/Enhancer/Doctrine/PhpcrOdmEnhancerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Cmf\Component\Resource\Tests\Unit\Description\Enhancer\Doctrine;

use Symfony\Cmf\Component\Resource\Description\Description;
use Symfony\Cmf\Component\Resource\Repository\Resource\CmfResource;
use Symfony\Cmf\Component\Resource\Description\Descriptor;
use Doctrine\ODM\PHPCR\Mapping\ClassMetadataFactory;
use Symfony\Cmf\Component\Resource\Description\Enhancer\Doctrine\PhpcrOdmEnhancer;
use Puli\Repository\Api\Resource\PuliResource;
use Doctrine\ODM\PHPCR\Mapping\ClassMetadata;

class PhpcrOdmEnhancerTest extends \PHPUnit_Framework_TestCAse
{
private $metadataFactory;
private $enhancer;
private $cmfResource;
private $puliResource;
private $odmMetadata;
private $description;

public function setUp()
{
$this->metadataFactory = $this->prophesize(ClassMetadataFactory::class);
$this->enhancer = new PhpcrOdmEnhancer($this->metadataFactory->reveal());

$this->cmfResource = $this->prophesize(CmfResource::class);
$this->puliResource = $this->prophesize(PuliResource::class);
$this->odmMetadata = $this->prophesize(ClassMetadata::class);
$this->description = $this->prophesize(Description::class);
}

/**
* It should return true it supports a given resource.
*/
public function testSupportsResource()
{
$this->cmfResource->getPayloadType()->willReturn(\stdClass::class);
$this->metadataFactory->hasMetadataFor(\stdClass::class)->willReturn(true);

$result = $this->enhancer->supports($this->cmfResource->reveal());
$this->assertTrue($result);
}

/**
* It should return false if the resource is not an instance of CmfResource.
*/
public function testNotSupportsNonCmfResource()
{
$this->assertFalse(
$this->enhancer->supports($this->puliResource->reveal())
);
}

/**
* It should return false if the resource is not known by the PHPCR-ODM metadata factory.
*/
public function testNotSupportsNotSupportedByPhpcrOdm()
{
$this->cmfResource->getPayloadType()->willReturn(\stdClass::class);
$this->metadataFactory->hasMetadataFor(\stdClass::class)->willReturn(false);

$this->assertFalse(
$this->enhancer->supports($this->cmfResource->reveal())
);
}

/**
* It should enhance the description with the child mapping information from the PHPCR-ODM metadata.
*/
public function testEnhanceDescription()
{
// object the implements an allowed interface
$mappedObject1 = $this->prophesize();
$mappedObject1->willImplement(FooInterface::class);
$metadata1 = $this->prophesize(ClassMetadata::class);
$metadata1->name = get_class($mappedObject1->reveal());
$metadata1->getReflectionClass()->willReturn(new \ReflectionClass($metadata1->name));

// object the extends an allowed abstract class
$mappedObject2 = $this->prophesize();
$mappedObject2->willExtend(AbstractFoo::class);
$metadata2 = $this->prophesize(ClassMetadata::class);
$metadata2->name = get_class($mappedObject2->reveal());
$metadata2->getReflectionClass()->willReturn(new \ReflectionClass($metadata2->name));

// object of exact type that is allowed
$mappedObject3 = $this->prophesize();
$metadata3 = $this->prophesize(ClassMetadata::class);
$metadata3->name = get_class($mappedObject3->reveal());
$metadata3->getReflectionClass()->willReturn(new \ReflectionClass($metadata3->reveal()));

// object that is not permitted
$metadata4 = $this->prophesize(ClassMetadata::class);
$metadata4->name = NotAllowedFoo::class;
$metadata4->getReflectionClass()->willReturn(new \ReflectionClass($metadata4->reveal()));

$this->description->getResource()->willReturn($this->cmfResource->reveal());
$this->cmfResource->getPayloadType()->willReturn('payload_type');
$this->metadataFactory->getMetadataFor('payload_type')->willReturn($this->odmMetadata->reveal());
$this->metadataFactory->getAllMetadata()->willReturn([
$metadata1->reveal(),
$metadata2->reveal(),
$metadata3->reveal(),
]);

$this->odmMetadata->isLeaf()->willReturn(false);
$this->odmMetadata->getChildClasses()->willReturn([
FooInterface::class,
AbstractFoo::class,
$metadata3->name,
]);

$this->description->set(Descriptor::CHILDREN_ALLOW, true)->shouldBeCalled();
$this->description->set(Descriptor::CHILDREN_TYPES, [
$metadata1->name,
$metadata2->name,
$metadata3->name,
])->shouldBeCalled();
$this->enhancer->enhance($this->description->reveal());
}
}

interface FooInterface
{
}

abstract class AbstractFoo
{
}

class NotAllowedFoo
{
}
Loading

0 comments on commit 96c72ef

Please sign in to comment.