Skip to content

Commit

Permalink
Strict check on class mappings
Browse files Browse the repository at this point in the history
  • Loading branch information
Nil Portugués committed Oct 14, 2015
1 parent 0cdb9e1 commit 1c50041
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 9 deletions.
49 changes: 49 additions & 0 deletions src/Mapping/ClassMapping/ApiMappingInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace NilPortugues\Api\Mapping\ClassMapping;

interface ApiMappingInterface
{
/**
* Returns a string with the full class name, including namespace.
*
* @return string
*/
public function getClass();

/**
* Returns a string representing the resource name as it will be shown after the mapping.
*
* @return string
*/
public function getAlias();

/**
* Returns an array of properties that will be renamed.
* Key is current property from the class. Value is the property's alias name.
*
* @return array
*/
public function getAliasedProperties();

/**
* List of properties in the class that will be ignored by the mapping.
*
* @return array
*/
public function getHideProperties();

/**
* Returns an array of properties that are used as an ID value.
*
* @return array
*/
public function getIdProperties();

/**
* Returns a list of URLs. This urls must have placeholders to be replaced with the getIdProperties() values.
*
* @return array
*/
public function getUrls();
}
13 changes: 13 additions & 0 deletions src/Mapping/ClassMapping/HalJsonMapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace NilPortugues\Api\Mapping\ClassMapping;

interface HalJsonMapping extends ApiMappingInterface
{
/**
* Returns an array of curies.
*
* @return array
*/
public function getCuries();
}
14 changes: 14 additions & 0 deletions src/Mapping/ClassMapping/JsonApiMapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace NilPortugues\Api\Mapping\ClassMapping;

interface JsonApiMapping
{
/**
* Returns an array containing the relationship mappings as an array.
* Key for each relationship defined must match a property of the mapped class.
*
* @return array
*/
public function getRelationships();
}
4 changes: 2 additions & 2 deletions src/Mapping/Mapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ public function __construct(array $mappings = null)
);
}

$this->classMap[ltrim($mapping->getClassName(), "\\")] = $mapping;
$this->aliasMap[ltrim($mapping->getClassAlias(), "\\")] = $mapping->getClassName();
$this->classMap[ltrim($mapping->getClassName(), '\\')] = $mapping;
$this->aliasMap[ltrim($mapping->getClassAlias(), '\\')] = $mapping->getClassName();
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/Mapping/Mapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ class Mapping
{
/**
* @var string
*/
*/
private $className = '';
/**
* @var string
*/
private $resourceUrlPattern = '';
*/
private $resourceUrlPattern = '';
/**
* @var string
*/
Expand Down
53 changes: 50 additions & 3 deletions src/Mapping/MappingFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,38 @@ public static function fromArray(array &$mappedClass)
$idProperties = self::getIdProperties($mappedClass);

$mapping = new Mapping($className, $resourceUrl, $idProperties);

$mapping->setClassAlias((empty($mappedClass['alias'])) ? $className : $mappedClass['alias']);

if (false === empty($mappedClass['aliased_properties'])) {
$mapping->setPropertyNameAliases($mappedClass['aliased_properties']);
foreach (array_keys($mapping->getAliasedProperties()) as $propertyName) {
if (false === in_array($propertyName, self::getClassProperties($className), true)) {
throw new MappingException(
sprintf('Could not alias property %s in class %s because it does not exist.', $propertyName, $className)
);
}
}
}

if (false === empty($mappedClass['hide_properties'])) {
$mapping->setHiddenProperties($mappedClass['hide_properties']);
foreach ($mapping->getHiddenProperties() as $propertyName) {
if (false === in_array($propertyName, self::getClassProperties($className), true)) {
throw new MappingException(
sprintf('Could not hide property %s in class %s because it does not exist.', $propertyName, $className)
);
}
}
}

if (!empty($mappedClass['relationships'])) {
foreach ($mappedClass['relationships'] as $propertyName => $urls) {
if (false === in_array($propertyName, self::getClassProperties($className), true)) {
throw new MappingException(
sprintf('Could not find property %s in class %s because it does not exist.', $propertyName, $className)
);
}

$mapping->setRelationshipUrls($propertyName, $urls);
}
}
Expand Down Expand Up @@ -99,8 +118,7 @@ private static function getSelfUrl(array &$mappedClass)
*/
private static function getIdProperties(array &$mappedClass)
{

return (!empty($mappedClass['id_properties']))? $mappedClass['id_properties'] : [];
return (!empty($mappedClass['id_properties'])) ? $mappedClass['id_properties'] : [];
}

/**
Expand All @@ -116,4 +134,33 @@ private static function getOtherUrls(array $mappedClass)

return $mappedClass['urls'];
}

/**
* Recursive function to get an associative array of class properties by
* property name, including inherited ones from extended classes.
*
* @param string $className Class name
*
* @return array
*
* @link http://php.net/manual/es/reflectionclass.getproperties.php#88405
*/
private static function getClassProperties($className)
{
$ref = new \ReflectionClass($className);
$properties = array();
foreach ($ref->getProperties() as $prop) {
$f = $prop->getName();
$properties[$f] = $prop;
}

if ($parentClass = $ref->getParentClass()) {
$parentPropsArr = self::getClassProperties($parentClass->getName());
if (count($parentPropsArr) > 0) {
$properties = array_merge($parentPropsArr, $properties);
}
}

return array_keys($properties);
}
}
21 changes: 21 additions & 0 deletions tests/Dummy/ComplexObject/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@

class Post
{
/**
* @var PostId
*/
private $postId;
/**
* @var
*/
private $title;
/**
* @var
*/
private $content;
/**
* @var User
*/
private $author;
/**
* @var array
*/
private $comments;

/**
* @param PostId $id
* @param $title
Expand Down
91 changes: 90 additions & 1 deletion tests/Mapping/MappingFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,103 @@ public function testItCanBuildMappingsFromArray()
$this->assertEquals('http://example.com/posts/{postId}/relationships/author', $mapping->getRelationshipSelfUrl('author'));
}

public function testItCanBuildMappingsFromArrayWillThrowExceptionIfAliasPropertyDoesNotExist()
{
$mappedClass = [
'class' => Post::class,
'alias' => 'Message',
'aliased_properties' => [
'I_do_not_exist' => 'headline',
'content' => 'body',
],
'hide_properties' => [
'comments',
],
'id_properties' => [
'postId',
],
'urls' => [
'self' => 'http://example.com/posts/{postId}',
],
'relationships' => [
'author' => [
'related' => 'http://example.com/posts/{postId}/author',
'self' => 'http://example.com/posts/{postId}/relationships/author',
],
],
];

$this->setExpectedException(MappingException::class);
MappingFactory::fromArray($mappedClass);
}

public function testItCanBuildMappingsFromArrayWillThrowExceptionIfHidePropertyDoesNotExist()
{
$mappedClass = [
'class' => Post::class,
'alias' => 'Message',
'aliased_properties' => [
'title' => 'headline',
'content' => 'body',
],
'hide_properties' => [
'I_do_not_exist',
],
'id_properties' => [
'postId',
],
'urls' => [
'self' => 'http://example.com/posts/{postId}',
],
'relationships' => [
'author' => [
'related' => 'http://example.com/posts/{postId}/author',
'self' => 'http://example.com/posts/{postId}/relationships/author',
],
],
];

$this->setExpectedException(MappingException::class);
MappingFactory::fromArray($mappedClass);
}

public function testItCanBuildMappingsFromArrayWillThrowExceptionIfRelationshipPropertyDoesNotExist()
{
$mappedClass = [
'class' => Post::class,
'alias' => 'Message',
'aliased_properties' => [
'title' => 'headline',
'content' => 'body',
],
'hide_properties' => [
'comments',
],
'id_properties' => [
'postId',
],
'urls' => [
'self' => 'http://example.com/posts/{postId}',
],
'relationships' => [
'I_do_not_exist' => [
'related' => 'http://example.com/posts/{postId}/author',
'self' => 'http://example.com/posts/{postId}/relationships/author',
],
],
];

$this->setExpectedException(MappingException::class);
MappingFactory::fromArray($mappedClass);
}

public function testItWillThrowExceptionIfArrayHasNoClassKey()
{
$this->setExpectedException(MappingException::class);
$mappedClass = [];
MappingFactory::fromArray($mappedClass);
}


public function testItWillThrowExceptionIfArrayHasNoSelfUrlKey()
{
$this->setExpectedException(MappingException::class);
Expand Down

0 comments on commit 1c50041

Please sign in to comment.