From 95e14fa315839d3fe88251486d3087bfa4c1a162 Mon Sep 17 00:00:00 2001 From: dantleech Date: Sat, 13 Dec 2014 22:16:58 +0100 Subject: [PATCH 1/2] POC field slugifier generator --- .../PHPCR/Id/FieldSlugifierIdGenerator.php | 68 +++++++++++++++++++ .../ODM/PHPCR/Mapping/Annotations/Id.php | 5 ++ .../ODM/PHPCR/Mapping/ClassMetadata.php | 13 ++++ .../Id/FieldSlugifierIdGeneratorTest.php | 16 +++++ 4 files changed, 102 insertions(+) create mode 100644 lib/Doctrine/ODM/PHPCR/Id/FieldSlugifierIdGenerator.php create mode 100644 tests/Doctrine/Tests/ODM/PHPCR/Id/FieldSlugifierIdGeneratorTest.php diff --git a/lib/Doctrine/ODM/PHPCR/Id/FieldSlugifierIdGenerator.php b/lib/Doctrine/ODM/PHPCR/Id/FieldSlugifierIdGenerator.php new file mode 100644 index 000000000..6db554c72 --- /dev/null +++ b/lib/Doctrine/ODM/PHPCR/Id/FieldSlugifierIdGenerator.php @@ -0,0 +1,68 @@ +. + */ + +namespace Doctrine\ODM\PHPCR\Id; + +use Doctrine\ODM\PHPCR\DocumentManager; +use Doctrine\ODM\PHPCR\Mapping\ClassMetadata; +use PHPCR\RepositoryException; +use PHPCR\Util\NodeHelper; + +/** + * Generate the id using the auto naming strategy + */ +class AutoIdGenerator extends ParentIdGenerator +{ + /** + * Use the parent field together with an auto generated name to generate the id + * + * {@inheritDoc} + */ + public function generate($document, ClassMetadata $class, DocumentManager $dm, $parent = null) + { + if (null === $parent) { + $parent = $class->parentMapping ? $class->getFieldValue($document, $class->parentMapping) : null; + } + + $id = $class->getFieldValue($document, $class->identifier); + if (empty($id) && null === $parent) { + throw IdException::noIdNoParent($document, $class->parentMapping); + } + + if (empty($parent)) { + return $id; + } + + try { + $parentNode = $dm->getNodeForDocument($parent); + $existingNames = (array) $parentNode->getNodeNames(); + } catch (RepositoryException $e) { + // this typically happens while cascading persisting documents + $existingNames = array(); + } + $name = NodeHelper::generateAutoNodeName( + $existingNames, + $dm->getPhpcrSession()->getWorkspace()->getNamespaceRegistry()->getNamespaces(), + '', + '' + ); + + return $this->buildName($document ,$class, $dm, $parent, $name); + } +} diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Id.php b/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Id.php index 34044583a..14390b36c 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Id.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Id.php @@ -41,4 +41,9 @@ final class Id * @var string */ public $strategy; + + /** + * @var array + */ + public $options; } diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php index d9e741a15..063525160 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php @@ -88,6 +88,11 @@ class ClassMetadata implements ClassMetadataInterface */ const GENERATOR_TYPE_AUTO = 4; + /** + * Slugify the value of a specified field and use that as the node name + */ + const GENERATOR_TYPE_FIELD_SLUGIFY = 5; + protected static $validVersionableAnnotations = array('simple', 'full'); /** @@ -104,6 +109,13 @@ class ClassMetadata implements ClassMetadataInterface */ public $idGenerator = self::GENERATOR_TYPE_NONE; + /** + * READ-ONLY: The options for the ID generator + * + * @var array + */ + public $idGeneratorOptions = array(); + /** * READ-ONLY: The field name of the document identifier. */ @@ -659,6 +671,7 @@ public function mapId(array $mapping, ClassMetadata $inherited = null) $this->setIdentifier($mapping['fieldName']); if (isset($mapping['strategy'])) { $this->setIdGenerator($mapping['strategy']); + $this->idGeneratorOptions = isset($mapping['options']) ? : array(); } } diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Id/FieldSlugifierIdGeneratorTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Id/FieldSlugifierIdGeneratorTest.php new file mode 100644 index 000000000..ff578b77b --- /dev/null +++ b/tests/Doctrine/Tests/ODM/PHPCR/Id/FieldSlugifierIdGeneratorTest.php @@ -0,0 +1,16 @@ + Date: Sun, 14 Dec 2014 15:20:59 +0100 Subject: [PATCH 2/2] Work --- composer.json | 1 + lib/Doctrine/ODM/PHPCR/Configuration.php | 23 +++++ .../PHPCR/Id/FieldSlugifierIdGenerator.php | 99 +++++++++++++++---- lib/Doctrine/ODM/PHPCR/Id/IdGenerator.php | 7 +- .../PHPCR/Mapping/Annotations/AssocArray.php | 35 +++++++ .../ODM/PHPCR/Mapping/ClassMetadata.php | 2 +- lib/Doctrine/ODM/PHPCR/UnitOfWork.php | 2 +- tests/Doctrine/Tests/Models/CMS/CmsBlock.php | 18 ++++ .../ODM/PHPCR/Functional/PhpArrayTest.php | 61 ++++++++++++ .../Id/FieldSlugifierIdGeneratorTest.php | 58 ++++++++++- .../Tests/ODM/PHPCR/Id/IdGeneratorTest.php | 29 +++++- 11 files changed, 306 insertions(+), 29 deletions(-) create mode 100644 lib/Doctrine/ODM/PHPCR/Mapping/Annotations/AssocArray.php create mode 100644 tests/Doctrine/Tests/Models/CMS/CmsBlock.php create mode 100644 tests/Doctrine/Tests/ODM/PHPCR/Functional/PhpArrayTest.php diff --git a/composer.json b/composer.json index f7e8dd20b..1c0941b81 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ "doctrine/instantiator": "~1.0.1" }, "require-dev": { + "aferrandini/urlizer": "1.0.*", "symfony/yaml": "~2.0", "liip/rmt": "~1.1" }, diff --git a/lib/Doctrine/ODM/PHPCR/Configuration.php b/lib/Doctrine/ODM/PHPCR/Configuration.php index f9768ebfe..b0263f2d0 100644 --- a/lib/Doctrine/ODM/PHPCR/Configuration.php +++ b/lib/Doctrine/ODM/PHPCR/Configuration.php @@ -382,4 +382,27 @@ public function getUuidGenerator() } ; } + + /** + * Set slugifier callable + * + * PHP callable for peroforming slugification when using the + * slugify from field ID generator method + * + * @param Callable + */ + public function setSlugifier($callable) + { + $this->slugifier = $callable; + } + + /** + * Get slugifier callable + * + * @return callable + */ + public function getSlugifier() + { + return $this->slugifier; + } } diff --git a/lib/Doctrine/ODM/PHPCR/Id/FieldSlugifierIdGenerator.php b/lib/Doctrine/ODM/PHPCR/Id/FieldSlugifierIdGenerator.php index 6db554c72..6f1c557bb 100644 --- a/lib/Doctrine/ODM/PHPCR/Id/FieldSlugifierIdGenerator.php +++ b/lib/Doctrine/ODM/PHPCR/Id/FieldSlugifierIdGenerator.php @@ -27,8 +27,21 @@ /** * Generate the id using the auto naming strategy */ -class AutoIdGenerator extends ParentIdGenerator +class FieldSlugifierIdGenerator extends ParentIdGenerator { + /** + * @var callable + */ + private $slugifier; + + /** + * @param callable Slugifier callable + */ + public function __construct($slugifier) + { + $this->slugifier = $slugifier; + } + /** * Use the parent field together with an auto generated name to generate the id * @@ -40,29 +53,77 @@ public function generate($document, ClassMetadata $class, DocumentManager $dm, $ $parent = $class->parentMapping ? $class->getFieldValue($document, $class->parentMapping) : null; } - $id = $class->getFieldValue($document, $class->identifier); - if (empty($id) && null === $parent) { + if (null === $parent) { throw IdException::noIdNoParent($document, $class->parentMapping); } - if (empty($parent)) { - return $id; + if (!isset($class->idGeneratorOptions['field'])) { + throw new \InvalidArgumentException( + 'The field slugifier ID generator requires that you specify the '. + '"field" option specifying the field to be slugified.' + ); + } + + $fieldName = $class->idGeneratorOptions['field']; + + if (!$class->hasField($fieldName)) { + throw new \InvalidArgumentException(sprintf( + '"%s" has been specified as the field to be slugified by the ' . + 'field slugifier ID generator. But it is not mapped', + $fieldName + )); } - try { - $parentNode = $dm->getNodeForDocument($parent); - $existingNames = (array) $parentNode->getNodeNames(); - } catch (RepositoryException $e) { - // this typically happens while cascading persisting documents - $existingNames = array(); + $value = $class->getFieldValue($document, $fieldName); + + if (!$value) { + throw new \InvalidArgumentException(sprintf( + 'Cannot slugify node name from empty field value for field "%s"', + $fieldName + )); } - $name = NodeHelper::generateAutoNodeName( - $existingNames, - $dm->getPhpcrSession()->getWorkspace()->getNamespaceRegistry()->getNamespaces(), - '', - '' - ); - - return $this->buildName($document ,$class, $dm, $parent, $name); + + $slugified = $this->slugify($value); + + $parentId = $dm->getUnitOfWork()->getDocumentId($parent); + + return $parentId . '/' . $slugified; + } + + /** + * Try and call the slugifier + */ + private function slugify($string) + { + static $resolvedSlugifier = null; + + if ($resolvedSlugifier) { + return $resolvedSlugifier($string); + } + + $slugifier = $this->slugifier; + + if ($slugifier instanceof \Closure) { + return $resolvedSlugifier = function ($string) use ($slugifier) { + $slugifier($string); + }; + } + + if (is_array($string)) { + return $resolvedSlugifier = function ($string) use ($slugifier) { + call_user_func_array($slugifier[0], $slugifier[1]); + }; + } + + if (is_string($string)) { + return $resolvedSlugifier = function ($string) use ($slugifier) { + call_user_func($string); + }; + } + + throw new \InvalidArgumentException(sprintf( + 'Could not call given slugifier callable of type "%s"', + gettype($string) + )); } } diff --git a/lib/Doctrine/ODM/PHPCR/Id/IdGenerator.php b/lib/Doctrine/ODM/PHPCR/Id/IdGenerator.php index 76867e595..708184324 100644 --- a/lib/Doctrine/ODM/PHPCR/Id/IdGenerator.php +++ b/lib/Doctrine/ODM/PHPCR/Id/IdGenerator.php @@ -22,6 +22,8 @@ use Doctrine\ODM\PHPCR\DocumentManager; use Doctrine\ODM\PHPCR\Mapping\ClassMetadata; use Doctrine\ODM\PHPCR\Exception\InvalidArgumentException; +use Doctrine\ODM\PHPCR\Configuration; +use Doctrine\ODM\PHPCR\Id\FieldSlugifierIdGenerator; /** * Used to abstract ID generation @@ -41,7 +43,7 @@ abstract class IdGenerator * * @return IdGenerator */ - public static function create($generatorType) + public static function create($generatorType, Configuration $config) { switch ($generatorType) { case ClassMetadata::GENERATOR_TYPE_ASSIGNED: @@ -56,6 +58,9 @@ public static function create($generatorType) case ClassMetadata::GENERATOR_TYPE_AUTO: $instance = new AutoIdGenerator(); break; + case ClassMetadata::GENERATOR_TYPE_FIELD_SLUGIFIER: + $instance = new FieldSlugifierIdGenerator($config->getSlugifier()); + break; default: throw new InvalidArgumentException("ID Generator does not exist: $generatorType"); diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/AssocArray.php b/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/AssocArray.php new file mode 100644 index 000000000..a3a12c56e --- /dev/null +++ b/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/AssocArray.php @@ -0,0 +1,35 @@ +. + */ + +namespace Doctrine\ODM\PHPCR\Mapping\Annotations; + +use Doctrine\Common\Annotations\Annotation; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class AssocArray extends TranslatableProperty +{ + /** + * @var string + */ + public $type = 'assoc-array'; +} + diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php index 063525160..00b9bd667 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php @@ -91,7 +91,7 @@ class ClassMetadata implements ClassMetadataInterface /** * Slugify the value of a specified field and use that as the node name */ - const GENERATOR_TYPE_FIELD_SLUGIFY = 5; + const GENERATOR_TYPE_FIELD_SLUGIFIER = 5; protected static $validVersionableAnnotations = array('simple', 'full'); diff --git a/lib/Doctrine/ODM/PHPCR/UnitOfWork.php b/lib/Doctrine/ODM/PHPCR/UnitOfWork.php index ab9a8fde2..401c9ba57 100644 --- a/lib/Doctrine/ODM/PHPCR/UnitOfWork.php +++ b/lib/Doctrine/ODM/PHPCR/UnitOfWork.php @@ -892,7 +892,7 @@ private function cascadeScheduleParentInsert($class, $document, &$visited) private function getIdGenerator($type) { if (!isset($this->idGenerators[$type])) { - $this->idGenerators[$type] = IdGenerator::create($type); + $this->idGenerators[$type] = IdGenerator::create($type, $config); } return $this->idGenerators[$type]; diff --git a/tests/Doctrine/Tests/Models/CMS/CmsBlock.php b/tests/Doctrine/Tests/Models/CMS/CmsBlock.php new file mode 100644 index 000000000..8216e2870 --- /dev/null +++ b/tests/Doctrine/Tests/Models/CMS/CmsBlock.php @@ -0,0 +1,18 @@ +dm = $this->createDocumentManager(array(__DIR__)); + $this->node = $this->resetFunctionalNode($this->dm); + } + + public function providePersist() + { + return array( + array( + array( + 'rows' => 3, + 'title' => 'Foobar', + ), + ) + ); + } + + /** + * @dataProvider providePersist + */ + public function testPersist($config) + { + $block = new CmsBlock(); + $block->id = '/functional/test'; + $block->config = $config; + $this->dm->persist($block); + $this->dm->flush(); + $this->dm->clear(); + + $block = $this->dm->find(null, '/functional/test'); + $this->assertSame( + $config, + $block->config + ); + } +} + diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Id/FieldSlugifierIdGeneratorTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Id/FieldSlugifierIdGeneratorTest.php index ff578b77b..edd7c4165 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/Id/FieldSlugifierIdGeneratorTest.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/Id/FieldSlugifierIdGeneratorTest.php @@ -3,14 +3,68 @@ namespace Doctrine\Tests\ODM\PHPCR\Id; use Doctrine\ODM\PHPCR\Id\ParentIdGenerator; +use Doctrine\ODM\PHPCR\Id\FieldSlugifierIdGenerator; use Doctrine\ODM\PHPCR\Mapping\ClassMetadata; class FieldSlugifierIdGeneratorTest extends \PHPUnit_Framework_TestCase { + private $classMetadata; + private $documentManager; + + public function setUp() + { + $classMetadata = new ClassMetadata('\stdClass'); + $this->classMetadata = $classMetadata; + $this->documentManager = $this->getMockBuilder( + 'Doctrine\ODM\PHPCR\DocumentManager' + )->disableOriginalConstructor()->getMock(); + $this->parent = new \stdClass(); + $this->document = new \stdClass(); + $this->config = $this->getMockBuilder( + 'Doctrine\ODM\PHPCR\Configuration' + ); + $this->generator = new FieldSlugifierIdGenerator($this->config); + } + + public function provideGenerate() + { + return array( + array( + array( + 'expected_exception' => 'Doctrine\ODM\PHPCR\Id\IdException', + ) + ) + ); + } + + /** - * @covers Doctrine\ODM\PHPCR\Id\ParentIdGenerator::generate + * @dataProvider provideGenerate */ - public function testGenerate() + public function testGenerateNoParent($options) + { + $options = array_merge(array( + 'parent' => false, + 'parent_from_field' => false, + 'non_existing_field' => false, + 'empty_value' => false, + 'expected_exception' => null, + ), $options); + + if ($options['expected_exception']) { + $this->setExpectedException($options['expected_exception']); + } + + $this->generator->generate( + $this->document, + $this->classMetadata, + $this->documentManager, + $options['parent'] ? $this->parent : null + ); + } + + public static function slugify($string) { + return $string; } } diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Id/IdGeneratorTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Id/IdGeneratorTest.php index a50f92a31..c8de1bec3 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/Id/IdGeneratorTest.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/Id/IdGeneratorTest.php @@ -5,12 +5,22 @@ class IdGeneratorTest extends \PHPUnit_Framework_TestCase { + /** + * @var Doctrine\ODM\PHPCR\Configuration + */ + private $config; + + public function setUp() + { + $this->config = $this->getMock('Doctrine\ODM\PHPCR\Configuration'); + } + /** * @covers Doctrine\ODM\PHPCR\Id\IdGenerator::create */ public function testCreateGeneratorTypeAssigned() { - $generator = IdGenerator::create(ClassMetadata::GENERATOR_TYPE_ASSIGNED); + $generator = IdGenerator::create(ClassMetadata::GENERATOR_TYPE_ASSIGNED, $this->config); $this->assertInstanceOf('Doctrine\ODM\PHPCR\Id\AssignedIdGenerator', $generator); } @@ -19,7 +29,7 @@ public function testCreateGeneratorTypeAssigned() */ public function testCreateGeneratorTypeRepository() { - $generator = IdGenerator::create(ClassMetadata::GENERATOR_TYPE_REPOSITORY); + $generator = IdGenerator::create(ClassMetadata::GENERATOR_TYPE_REPOSITORY, $this->config); $this->assertInstanceOf('Doctrine\ODM\PHPCR\Id\RepositoryIdGenerator', $generator); } @@ -28,7 +38,7 @@ public function testCreateGeneratorTypeRepository() */ public function testCreateGeneratorTypeParent() { - $generator = IdGenerator::create(ClassMetadata::GENERATOR_TYPE_PARENT); + $generator = IdGenerator::create(ClassMetadata::GENERATOR_TYPE_PARENT, $this->config); $this->assertInstanceOf('Doctrine\ODM\PHPCR\Id\ParentIdGenerator', $generator); } @@ -37,17 +47,26 @@ public function testCreateGeneratorTypeParent() */ public function testCreateGeneratorTypeAuto() { - $generator = IdGenerator::create(ClassMetadata::GENERATOR_TYPE_AUTO); + $generator = IdGenerator::create(ClassMetadata::GENERATOR_TYPE_AUTO, $this->config); $this->assertInstanceOf('Doctrine\ODM\PHPCR\Id\AutoIdGenerator', $generator); } + /** + * @covers Doctrine\ODM\PHPCR\Id\IdGenerator::create + */ + public function testCreateGeneratorTypeFieldSlugifier() + { + $generator = IdGenerator::create(ClassMetadata::GENERATOR_TYPE_FIELD_SLUGIFIER, $this->config); + $this->assertInstanceOf('Doctrine\ODM\PHPCR\Id\FieldSlugifierIdGenerator', $generator); + } + /** * @expectedException \Doctrine\ODM\PHPCR\Exception\InvalidArgumentException * @covers Doctrine\ODM\PHPCR\Id\IdGenerator::create */ public function testCreateException() { - IdGenerator::create(null); + IdGenerator::create('asd', $this->config); } }