Skip to content
This repository was archived by the owner on Jan 1, 2020. It is now read-only.
12 changes: 10 additions & 2 deletions src/Midgard/CreatePHP/Mapper/BaseDoctrineRdfMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use Doctrine\Common\Persistence\ManagerRegistry;
use Midgard\CreatePHP\Entity\EntityInterface;

use PHPCR\ItemExistsException;

/**
* Base mapper for doctrine, removing the proxy class names in canonicalClassName
*
Expand Down Expand Up @@ -45,8 +47,14 @@ public function __construct($typeMap, ManagerRegistry $registry, $name = null)
*/
public function store(EntityInterface $entity)
{
$this->om->persist($entity);
$this->om->flush();
try {
$this->om->persist($entity->getObject());
$this->om->flush();
} catch (ItemExistsException $iee) {
//an item with the same title already exists
return false;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this will be quite ahrd to debug. i wonder how we should do that in general?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

but lets not block the merge by this question, before we just had a 500 in that case, now at least we handle it. maybe open a new issue?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Well, I think the cleanest solution here would be to catch this condition during validation. Of course, this is an entire new can of worms, but at least with a validation rule, you could give the user meaningful feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we also introduce a logging system like monolog and use it for all those kind of exceptions handling? But I don't know if one usually do it inside libraries like createphp or if it is part of the good practice...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

the sad thing is that fig did not want a general logger interface, so if we do something we tie to some implementation like Monolog (or at least the symfony logger interfaces)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

actually we are quite close to getting a log interface into fig.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

sure, logging would be good to have, but in a case like this, we will have to inform the end-user anyways, since they need to know that their input could not be saved. So we need a way to let the error bubble back to the user. If the store method is triggered programmatically, I think an exception would be the way to go, since then the calling code can decide what to do. If store is triggered via the REST handler, it could also catch the exception and send its message to the browser. This of course needs support from Create.js. I talked about this issue with @bergie at some point, but I'm not sure if there is a way to pass error messages back to Create.js yet.

But like i said, IMO this should ideally already be caught by validation (i.e. a unique constraint on the relevant field), and the exception should just be the fallback.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

i created #41

}

return true;
}

Expand Down
120 changes: 112 additions & 8 deletions src/Midgard/CreatePHP/Mapper/DoctrinePhpcrOdmMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
use Doctrine\Common\Persistence\ManagerRegistry;

use Midgard\CreatePHP\Type\TypeInterface;
use Midgard\CreatePHP\Entity\EntityInterface;

use \RuntimeException;

/**
* Mapper to handle PHPCR-ODM.
Expand All @@ -20,27 +23,128 @@
*/
class DoctrinePhpcrOdmMapper extends BaseDoctrineRdfMapper
{

/**
* {@inheritDoc}
*/
public function prepareObject(TypeInterface $type, $parent = null)
{
$object = parent::prepareObject($type);
if (null !== $parent) {
/** @var $meta \Doctrine\ODM\PHPCR\Mapping\ClassMetadata */
$meta = $this->om->getClassMetaData(get_class($object));
$meta->setFieldValue($object, $meta->parentMapping, $parent);
if (null == $parent) {
throw new RuntimeException('You need a parent to create new objects');
}

/** @var $meta \Doctrine\ODM\PHPCR\Mapping\ClassMetadata */
$meta = $this->om->getClassMetaData(get_class($object));

if (!property_exists($object, $meta->parentMapping)) {
throw new RuntimeException('parentMapping need to be mapped to '
. get_class($object));
}

$meta->setFieldValue($object, $meta->parentMapping, $parent);

return $object;
}

/**
* Overwrite to set the node name if not set
*
* @param EntityInterface $entity
* @return bool|void
*/
public function store(EntityInterface $entity)
{
/** @var $meta \Doctrine\ODM\PHPCR\Mapping\ClassMetadata */
$meta = $this->om->getClassMetaData(get_class($entity->getObject()));

if (!property_exists($entity->getObject(), $meta->nodename)) {
throw new RuntimeException('nodename need to be mapped to '
. get_class($entity->getObject()));
}

$nodename = $meta->getFieldValue($entity->getObject(), $meta->nodename);

if (empty($nodename)) { //in case of node creation the nodename is empty
$name = $this->generateNodeName($entity);
//set a guessed nodename
$meta->setFieldValue($entity->getObject(), $meta->nodename, $name);
}

return parent::store($entity);
}

/**
* {@inheritDoc}
*/
public function getBySubject($subject) {
public function getBySubject($subject)
{
if (empty($subject)) {
throw new \RuntimeException('Subject may not be empty');
throw new RuntimeException('Subject may not be empty');
}
$ret = $this->om->find(null, $subject);
if (empty($ret)) throw new \RuntimeException("Not found: $subject");
if (empty($ret)) throw new RuntimeException("Not found: $subject");
return $ret;
}

}
/**
* Generate an URL friendly node name from an entity
* Find a usable property and remove spaces, accents and special chars
*
* @param EntityInterface $entity
* @return mixed|string
*/
protected function generateNodeName(EntityInterface $entity)
{
$title = $this->determineEntityTitle($entity);

//TODO: find a better way? For example with Doctrine_Inflector or Gedmo\Sluggable\Util\Urlizer
setlocale(LC_CTYPE, 'en_US.UTF8');
$title = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $title);
$title = trim($title, "\x00..\x1F");
$title = preg_replace('/[^a-z0-9A-Z_.]/', '-', $title);

return $title;
}

/**
*
* Determine the title of the entity to be created, following this logic:
*
* 1. is there a getTitle method with a value set
* 2. is there a property containing "title" in rdf definition
* 3. take the first property defined in rdf and take first 50 characters
* 4. give up
*
* @param EntityInterface $entity
*/
private function determineEntityTitle(EntityInterface $entity)
{
$object = $entity->getObject();

//is there a getTitle method?
if (method_exists($object, 'getTitle') && $object->getTitle() !== '') {
return $object->getTitle();
} else {
//try to get a property containing title in the rdf description
foreach ($entity->getChildDefinitions() as $node) {
if (!($node instanceof \Midgard\CreatePHP\Entity\PropertyInterface) ||
strpos($node->getProperty(), "title") === false) {
continue;
}
return $entity->getMapper()->getPropertyValue($object, $node);
}

//try to get the first property to create a guessed title of max 50 characters
foreach ($entity->getChildDefinitions() as $node) {
if (!$node instanceof \Midgard\CreatePHP\Entity\PropertyInterface) {
continue;
}
return substr($entity->getMapper()->getPropertyValue($object, $node), 0, 49);
}

//give up
throw new RuntimeException('No title could be found four your new node.');
}
}
}
38 changes: 19 additions & 19 deletions src/Midgard/CreatePHP/RestService.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use Midgard\CreatePHP\Entity\EntityInterface;
use Midgard\CreatePHP\Entity\PropertyInterface;

use Midgard\CreatePHP\Helper\NamespaceHelper;

/**
* @package Midgard.CreatePHP
*/
Expand Down Expand Up @@ -119,6 +121,7 @@ public function getMapper()
*/
public function run($data, TypeInterface $type, $subject = null, $method = null)
{

if (null === $method) {
$method = strtolower($_SERVER['REQUEST_METHOD']);
}
Expand Down Expand Up @@ -152,32 +155,29 @@ public function run($data, TypeInterface $type, $subject = null, $method = null)
/**
* Handle post request
*
* Find a reverse mapping to the parent, into received data and type
* reverse options. The mapping is used to create the entity to store.
*
* @param array $received_data
* @param TypeInterface $type parent node of this type (TODO: refactor)
* @param TypeInterface $type type of the node to create
* @return array|null
*/
private function _handleCreate($received_data, TypeInterface $type)
{
foreach ($type->getChildDefinitions() as $node) {
if (!$node instanceof CollectionDefinitionInterface) {
continue;
}
$types = $node->getTypes();
if (count($types) != 1) {
throw new \Exception('TODO: refactor creation');
}
/** @var $node CollectionDefinitionInterface */
$child_type = reset($types);
$parentfield = $this->_expandPropertyName($node->getRev(), $child_type);
if (!empty($received_data[$parentfield])) {
$parent_identifier = $this->jsonldDecode($received_data[$parentfield][0]);
$parent = $this->_mapper->getBySubject($parent_identifier);
$object = $this->_mapper->prepareObject($child_type, $parent);
$entity = $child_type->createWithObject($object);
return $this->_storeData($received_data, $entity);
$parent = null;
foreach ($type->getRevOptions() as $option) {
$rdf = NamespaceHelper::expandNamespace($option, $type->getVocabularies());
$about = $received_data[$this->jsonldEncode($rdf)];

if (! empty($about)) {
$parent = $this->_mapper->getBySubject($this->jsonldDecode(current($about)));
break;
}
}
$object = $this->_mapper->prepareObject($type);

$object = $this->_mapper->prepareObject($type, $parent);
$entity = $type->createWithObject($object);

return $this->_storeData($received_data, $entity);
}

Expand Down
9 changes: 7 additions & 2 deletions tests/Test/Midgard/CreatePHP/RestServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,13 @@ public function testRunPost()
;

$this->type->expects($this->any())
->method('getChildDefinitions')
->will($this->returnValue(array('title' => $this->property, 'children' => $this->collection)))
->method('getRevOptions')
->will($this->returnValue(array('dcterms:partOf')))
;

$this->type->expects($this->any())
->method('createWithObject')
->will($this->returnValue($this->entity))
;

$rest = new RestService($this->mapper);
Expand Down