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

PHPCR-ODM enhancer #27

Merged
merged 1 commit into from
Jul 17, 2016
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
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';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add cmment


/**
* 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';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #25 for a possible alternative to this system of enhancement by using vendor-provided objects as descriptors.

}
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) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some resources may not have routes (they may not be administrable, for example a folder resource). I wonder if there is a better way to handle this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if its that bad. it could hide issues though, if i misconfigured something and then i will just not get a link instead of an exception that makes me notice i mistyped a name somewhere.

could configuration->getRouteName decide if there is a route or not, so that you could do an if here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly not -- its just concatenates some of the metadata string attributes and hopes for the best.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this should be tested and I will have a closer look at it again.

Copy link
Member Author

@dantleech dantleech Jun 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if there were a "has route" the result would be the same: the descriptor would not be added. So I am OK with this solution.

}
}

// 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)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment

$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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a PR to add hasByClass..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} 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