diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php
index a0b10e613a3..ff0bffbf856 100644
--- a/features/bootstrap/FeatureContext.php
+++ b/features/bootstrap/FeatureContext.php
@@ -298,6 +298,7 @@ public function thereIsAFileConfigDummyObject()
{
$fileConfigDummy = new FileConfigDummy();
$fileConfigDummy->setName('ConfigDummy');
+ $fileConfigDummy->setFoo('Foo');
$this->manager->persist($fileConfigDummy);
$this->manager->flush();
diff --git a/features/configurable.feature b/features/configurable.feature
index b08a9de7b01..44662044c1c 100644
--- a/features/configurable.feature
+++ b/features/configurable.feature
@@ -19,6 +19,7 @@ Feature: Configurable resource CRUD
{
"@id": "/fileconfigdummies/1",
"@type": "fileconfigdummy",
+ "foo": "Foo",
"id": 1,
"name": "ConfigDummy"
}
@@ -55,6 +56,7 @@ Feature: Configurable resource CRUD
"@context": "\/contexts\/fileconfigdummy",
"@id": "\/fileconfigdummies\/1",
"@type": "fileconfigdummy",
+ "foo": "Foo",
"id": 1,
"name": "ConfigDummy"
}
diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
index d6b0f7489f3..2a7690797ed 100644
--- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
+++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
@@ -255,8 +255,14 @@ private function registerLoaders(ContainerBuilder $container, array $bundles)
$container->getDefinition('api_platform.metadata.resource.name_collection_factory.yaml')->replaceArgument(0, $yamlResources);
$container->getDefinition('api_platform.metadata.resource.metadata_factory.yaml')->replaceArgument(0, $yamlResources);
+ $container->getDefinition('api_platform.metadata.property.name_collection_factory.yaml')->replaceArgument(0, $yamlResources);
+ $container->getDefinition('api_platform.metadata.property.metadata_factory.yaml')->replaceArgument(0, $yamlResources);
+
$container->getDefinition('api_platform.metadata.resource.name_collection_factory.xml')->replaceArgument(0, $xmlResources);
$container->getDefinition('api_platform.metadata.resource.metadata_factory.xml')->replaceArgument(0, $xmlResources);
+
+ $container->getDefinition('api_platform.metadata.property.name_collection_factory.xml')->replaceArgument(0, $xmlResources);
+ $container->getDefinition('api_platform.metadata.property.metadata_factory.xml')->replaceArgument(0, $xmlResources);
}
/**
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata.xml
index 97d9dee2396..11b9961536b 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/metadata.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/metadata.xml
@@ -89,6 +89,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -108,6 +118,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Metadata/Property/Factory/XmlPropertyMetadataFactory.php b/src/Metadata/Property/Factory/XmlPropertyMetadataFactory.php
new file mode 100644
index 00000000000..19d79b0def9
--- /dev/null
+++ b/src/Metadata/Property/Factory/XmlPropertyMetadataFactory.php
@@ -0,0 +1,202 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Exception\InvalidArgumentException;
+use ApiPlatform\Core\Exception\PropertyNotFoundException;
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
+use Symfony\Component\Config\Util\XmlUtils;
+
+/**
+ * Creates a property metadata from XML {@see Property} configuration.
+ *
+ * @author Baptiste Meyer
+ */
+class XmlPropertyMetadataFactory implements PropertyMetadataFactoryInterface
+{
+ const RESOURCE_SCHEMA = __DIR__.'/../../schema/metadata.xsd';
+
+ private $paths;
+ private $decorated;
+
+ /**
+ * @param string[] $paths
+ * @param PropertyMetadataFactoryInterface|null $decorated
+ */
+ public function __construct(array $paths, PropertyMetadataFactoryInterface $decorated = null)
+ {
+ $this->paths = $paths;
+ $this->decorated = $decorated;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function create(string $resourceClass, string $property, array $options = []) : PropertyMetadata
+ {
+ $parentPropertyMetadata = null;
+ if ($this->decorated) {
+ try {
+ $parentPropertyMetadata = $this->decorated->create($resourceClass, $property, $options);
+ } catch (PropertyNotFoundException $propertyNotFoundException) {
+ // Ignore not found exception from decorated factories
+ }
+ }
+
+ if (
+ !property_exists($resourceClass, $property) ||
+ empty($propertyMetadata = $this->getMetadata($resourceClass, $property))
+ ) {
+ return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property);
+ }
+
+ if ($parentPropertyMetadata) {
+ return $this->update($parentPropertyMetadata, $propertyMetadata);
+ }
+
+ return new PropertyMetadata(
+ null,
+ $propertyMetadata['description'],
+ $propertyMetadata['readable'],
+ $propertyMetadata['writable'],
+ $propertyMetadata['readableLink'],
+ $propertyMetadata['writableLink'],
+ $propertyMetadata['required'],
+ $propertyMetadata['identifier'],
+ $propertyMetadata['iri'],
+ null,
+ $propertyMetadata['attributes']
+ );
+ }
+
+ /**
+ * Returns the metadata from the decorated factory if available or throws an exception.
+ *
+ * @param PropertyMetadata|null $parentPropertyMetadata
+ * @param string $resourceClass
+ * @param string $property
+ *
+ * @throws PropertyNotFoundException
+ *
+ * @return PropertyMetadata
+ */
+ private function handleNotFound(PropertyMetadata $parentPropertyMetadata = null, string $resourceClass, string $property) : PropertyMetadata
+ {
+ if ($parentPropertyMetadata) {
+ return $parentPropertyMetadata;
+ }
+
+ throw new PropertyNotFoundException(sprintf('Property "%s" of the resource class "%s" not found.', $property, $resourceClass));
+ }
+
+ /**
+ * Extracts metadata from the XML tree.
+ *
+ * @param string $resourceClass
+ * @param string $propertyName
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return array
+ */
+ private function getMetadata(string $resourceClass, string $propertyName) : array
+ {
+ foreach ($this->paths as $path) {
+ try {
+ $domDocument = XmlUtils::loadFile($path, self::RESOURCE_SCHEMA);
+ } catch (\InvalidArgumentException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+
+ $properties = (new \DOMXPath($domDocument))->query(sprintf('//resources/resource[@class="%s"]/property[@name="%s"]', $resourceClass, $propertyName));
+
+ if (
+ false === $properties ||
+ 0 >= $properties->length ||
+ null === $properties->item(0) ||
+ false === $property = simplexml_import_dom($properties->item(0))
+ ) {
+ continue;
+ }
+
+ return [
+ 'description' => (string) $property['description'] ?: null,
+ 'readable' => $property['readable'] ? (bool) XmlUtils::phpize($property['readable']) : null,
+ 'writable' => $property['writable'] ? (bool) XmlUtils::phpize($property['writable']) : null,
+ 'readableLink' => $property['readableLink'] ? (bool) XmlUtils::phpize($property['readableLink']) : null,
+ 'writableLink' => $property['writableLink'] ? (bool) XmlUtils::phpize($property['writableLink']) : null,
+ 'required' => $property['required'] ? (bool) XmlUtils::phpize($property['required']) : null,
+ 'identifier' => $property['identifier'] ? (bool) XmlUtils::phpize($property['identifier']) : null,
+ 'iri' => (string) $property['iri'] ?: null,
+ 'attributes' => $this->getAttributes($property),
+ ];
+ }
+
+ return [];
+ }
+
+ /**
+ * Recursively transforms an attribute structure into an associative array.
+ *
+ * @param \SimpleXMLElement $element
+ *
+ * @return array
+ */
+ private function getAttributes(\SimpleXMLElement $element) : array
+ {
+ $attributes = [];
+ foreach ($element->attribute as $attribute) {
+ $value = isset($attribute->attribute[0]) ? $this->getAttributes($attribute) : (string) $attribute;
+
+ if (isset($attribute['name'])) {
+ $attributes[(string) $attribute['name']] = $value;
+ } else {
+ $attributes[] = $value;
+ }
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Creates a new instance of metadata if the property is not already set.
+ *
+ * @param PropertyMetadata $propertyMetadata
+ * @param array $metadata
+ *
+ * @return PropertyMetadata
+ */
+ private function update(PropertyMetadata $propertyMetadata, array $metadata) : PropertyMetadata
+ {
+ $metadataAccessors = [
+ 'description' => 'get',
+ 'readable' => 'is',
+ 'writable' => 'is',
+ 'writableLink' => 'is',
+ 'readableLink' => 'is',
+ 'required' => 'is',
+ 'identifier' => 'is',
+ 'iri' => 'get',
+ 'attributes' => 'get',
+ ];
+
+ foreach ($metadataAccessors as $metadataKey => $accessorPrefix) {
+ if (null === $metadata[$metadataKey] || null !== $propertyMetadata->{$accessorPrefix.ucfirst($metadataKey)}()) {
+ continue;
+ }
+
+ $propertyMetadata = $propertyMetadata->{'with'.ucfirst($metadataKey)}($metadata[$metadataKey]);
+ }
+
+ return $propertyMetadata;
+ }
+}
diff --git a/src/Metadata/Property/Factory/XmlPropertyNameCollectionFactory.php b/src/Metadata/Property/Factory/XmlPropertyNameCollectionFactory.php
new file mode 100644
index 00000000000..995a048b1b4
--- /dev/null
+++ b/src/Metadata/Property/Factory/XmlPropertyNameCollectionFactory.php
@@ -0,0 +1,96 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Exception\InvalidArgumentException;
+use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
+use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
+use Symfony\Component\Config\Util\XmlUtils;
+
+/**
+ * Creates a property name collection from XML {@see Property} configuration files.
+ *
+ * @author Baptiste Meyer
+ */
+class XmlPropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
+{
+ const RESOURCE_SCHEMA = __DIR__.'/../../schema/metadata.xsd';
+
+ private $paths;
+ private $decorated;
+
+ /**
+ * @param array $paths
+ * @param PropertyNameCollectionFactoryInterface|null $decorated
+ */
+ public function __construct(array $paths, PropertyNameCollectionFactoryInterface $decorated = null)
+ {
+ $this->paths = $paths;
+ $this->decorated = $decorated;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws InvalidArgumentException
+ */
+ public function create(string $resourceClass, array $options = []) : PropertyNameCollection
+ {
+ if ($this->decorated) {
+ try {
+ $propertyNameCollection = $this->decorated->create($resourceClass, $options);
+ } catch (ResourceClassNotFoundException $resourceClassNotFoundException) {
+ // Ignore not found exceptions from parent
+ }
+ }
+
+ if (!class_exists($resourceClass)) {
+ if (isset($propertyNameCollection)) {
+ return $propertyNameCollection;
+ }
+
+ throw new ResourceClassNotFoundException(sprintf('The resource class "%s" does not exist.', $resourceClass));
+ }
+
+ $propertyNames = [];
+
+ foreach ($this->paths as $path) {
+ try {
+ $domDocument = XmlUtils::loadFile($path, self::RESOURCE_SCHEMA);
+ } catch (\InvalidArgumentException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+
+ $properties = (new \DOMXPath($domDocument))->query(sprintf('//resources/resource[@class="%s"]/property', $resourceClass));
+
+ if (false === $properties || 0 >= $properties->length) {
+ continue;
+ }
+
+ foreach ($properties as $property) {
+ if ('' === $propertyName = $property->getAttribute('name')) {
+ continue;
+ }
+
+ $propertyNames[$propertyName] = true;
+ }
+ }
+
+ if (isset($propertyNameCollection)) {
+ foreach ($propertyNameCollection as $propertyName) {
+ $propertyNames[$propertyName] = true;
+ }
+ }
+
+ return new PropertyNameCollection(array_keys($propertyNames));
+ }
+}
diff --git a/src/Metadata/Property/Factory/YamlPropertyMetadataFactory.php b/src/Metadata/Property/Factory/YamlPropertyMetadataFactory.php
new file mode 100644
index 00000000000..bda8e81f794
--- /dev/null
+++ b/src/Metadata/Property/Factory/YamlPropertyMetadataFactory.php
@@ -0,0 +1,214 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Exception\InvalidArgumentException;
+use ApiPlatform\Core\Exception\PropertyNotFoundException;
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * Creates a property metadata from YAML {@see Property} configuration files.
+ *
+ * @author Baptiste Meyer
+ */
+class YamlPropertyMetadataFactory implements PropertyMetadataFactoryInterface
+{
+ private $paths;
+ private $decorated;
+
+ /**
+ * @param array $paths
+ * @param PropertyMetadataFactoryInterface|null $decorated
+ */
+ public function __construct(array $paths, PropertyMetadataFactoryInterface $decorated = null)
+ {
+ $this->paths = $paths;
+ $this->decorated = $decorated;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function create(string $resourceClass, string $property, array $options = []) : PropertyMetadata
+ {
+ $parentPropertyMetadata = null;
+ if ($this->decorated) {
+ try {
+ $parentPropertyMetadata = $this->decorated->create($resourceClass, $property, $options);
+ } catch (PropertyNotFoundException $propertyNotFoundException) {
+ // Ignore not found exception from decorated factories
+ }
+ }
+
+ if (
+ !property_exists($resourceClass, $property) ||
+ empty($propertyMetadata = $this->getMetadata($resourceClass, $property))
+ ) {
+ return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property);
+ }
+
+ if ($parentPropertyMetadata) {
+ return $this->update($parentPropertyMetadata, $propertyMetadata);
+ }
+
+ return new PropertyMetadata(
+ null,
+ $propertyMetadata['description'],
+ $propertyMetadata['readable'],
+ $propertyMetadata['writable'],
+ $propertyMetadata['readableLink'],
+ $propertyMetadata['writableLink'],
+ $propertyMetadata['required'],
+ $propertyMetadata['identifier'],
+ $propertyMetadata['iri'],
+ null,
+ $propertyMetadata['attributes']
+ );
+ }
+
+ /**
+ * Returns the metadata from the decorated factory if available or throws an exception.
+ *
+ * @param PropertyMetadata|null $parentPropertyMetadata
+ * @param string $resourceClass
+ * @param string $property
+ *
+ * @throws PropertyNotFoundException
+ *
+ * @return PropertyMetadata
+ */
+ private function handleNotFound(PropertyMetadata $parentPropertyMetadata = null, string $resourceClass, string $property) : PropertyMetadata
+ {
+ if ($parentPropertyMetadata) {
+ return $parentPropertyMetadata;
+ }
+
+ throw new PropertyNotFoundException(sprintf('Property "%s" of the resource class "%s" not found.', $property, $resourceClass));
+ }
+
+ /**
+ * Extracts metadata from the YAML tree.
+ *
+ * @param string $resourceClass
+ * @param string $property
+ *
+ * @throws ParseException
+ * @throws InvalidArgumentException
+ *
+ * @return array
+ */
+ private function getMetadata(string $resourceClass, string $property) : array
+ {
+ foreach ($this->paths as $path) {
+ try {
+ $resources = Yaml::parse(file_get_contents($path));
+ } catch (ParseException $parseException) {
+ $parseException->setParsedFile($path);
+
+ throw $parseException;
+ }
+
+ if (null === $resources = $resources['resources'] ?? $resources) {
+ continue;
+ }
+
+ if (!is_array($resources)) {
+ throw new InvalidArgumentException(sprintf('"resources" setting is expected to be null or an array, %s given in "%s".', gettype($resources), $path));
+ }
+
+ foreach ($resources as $resourceName => $resource) {
+ if (null === $resource) {
+ continue;
+ }
+
+ if (!is_array($resource)) {
+ throw new InvalidArgumentException(sprintf('"%s" setting is expected to be null or an array, %s given in "%s".', $resourceName, gettype($resource), $path));
+ }
+
+ if (!isset($resource['class'])) {
+ throw new InvalidArgumentException(sprintf('"class" setting is expected to be a string, none given in "%s".', $path));
+ }
+
+ if ($resourceClass !== $resource['class'] || !isset($resource['properties'])) {
+ continue;
+ }
+
+ if (!is_array($resource['properties'])) {
+ throw new InvalidArgumentException(sprintf('"properties" setting is expected to be null or an array, %s given in "%s".', gettype($resource['properties']), $path));
+ }
+
+ foreach ($resource['properties'] as $propertyName => $propertyValues) {
+ if (null === $propertyValues) {
+ continue;
+ }
+
+ if (!is_array($propertyValues)) {
+ throw new InvalidArgumentException(sprintf('"%s" setting is expected to be null or an array, %s given in "%s".', $propertyName, gettype($propertyValues), $path));
+ }
+
+ if ($property !== $propertyName) {
+ continue;
+ }
+
+ return [
+ 'description' => isset($propertyValues['description']) && is_scalar($propertyValues['description']) ? $propertyValues['description'] : null,
+ 'readable' => isset($propertyValues['readable']) && is_bool($propertyValues['readable']) ? $propertyValues['readable'] : null,
+ 'writable' => isset($propertyValues['writable']) && is_bool($propertyValues['writable']) ? $propertyValues['writable'] : null,
+ 'readableLink' => isset($propertyValues['readableLink']) && is_bool($propertyValues['readableLink']) ? $propertyValues['readableLink'] : null,
+ 'writableLink' => isset($propertyValues['writableLink']) && is_bool($propertyValues['writableLink']) ? $propertyValues['writableLink'] : null,
+ 'required' => isset($propertyValues['required']) && is_bool($propertyValues['required']) ? $propertyValues['required'] : null,
+ 'identifier' => isset($propertyValues['identifier']) && is_bool($propertyValues['identifier']) ? $propertyValues['identifier'] : null,
+ 'iri' => isset($propertyValues['iri']) && is_scalar($propertyValues['iri']) ? $propertyValues['iri'] : null,
+ 'attributes' => $propertyValues['attributes'] ?? null,
+ ];
+ }
+ }
+ }
+
+ return [];
+ }
+
+ /**
+ * Creates a new instance of metadata if the property is not already set.
+ *
+ * @param PropertyMetadata $propertyMetadata
+ * @param array $metadata
+ *
+ * @return PropertyMetadata
+ */
+ private function update(PropertyMetadata $propertyMetadata, array $metadata) : PropertyMetadata
+ {
+ $metadataAccessors = [
+ 'description' => 'get',
+ 'readable' => 'is',
+ 'writable' => 'is',
+ 'writableLink' => 'is',
+ 'readableLink' => 'is',
+ 'required' => 'is',
+ 'identifier' => 'is',
+ 'iri' => 'get',
+ 'attributes' => 'get',
+ ];
+
+ foreach ($metadataAccessors as $metadataKey => $accessorPrefix) {
+ if (null === $metadata[$metadataKey] || null !== $propertyMetadata->{$accessorPrefix.ucfirst($metadataKey)}()) {
+ continue;
+ }
+
+ $propertyMetadata = $propertyMetadata->{'with'.ucfirst($metadataKey)}($metadata[$metadataKey]);
+ }
+
+ return $propertyMetadata;
+ }
+}
diff --git a/src/Metadata/Property/Factory/YamlPropertyNameCollectionFactory.php b/src/Metadata/Property/Factory/YamlPropertyNameCollectionFactory.php
new file mode 100644
index 00000000000..19121b119ce
--- /dev/null
+++ b/src/Metadata/Property/Factory/YamlPropertyNameCollectionFactory.php
@@ -0,0 +1,126 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Exception\InvalidArgumentException;
+use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
+use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * Creates a property name collection from YAML {@see Property} configuration files.
+ *
+ * @author Baptiste Meyer
+ */
+class YamlPropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
+{
+ private $paths;
+ private $decorated;
+
+ /**
+ * @param array $paths
+ * @param PropertyNameCollectionFactoryInterface|null $decorated
+ */
+ public function __construct(array $paths, PropertyNameCollectionFactoryInterface $decorated = null)
+ {
+ $this->paths = $paths;
+ $this->decorated = $decorated;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws ParseException
+ * @throws InvalidArgumentException
+ */
+ public function create(string $resourceClass, array $options = []) : PropertyNameCollection
+ {
+ if ($this->decorated) {
+ try {
+ $propertyNameCollection = $this->decorated->create($resourceClass, $options);
+ } catch (ResourceClassNotFoundException $resourceClassNotFoundException) {
+ // Ignore not found exceptions from parent
+ }
+ }
+
+ if (!class_exists($resourceClass)) {
+ if (isset($propertyNameCollection)) {
+ return $propertyNameCollection;
+ }
+
+ throw new ResourceClassNotFoundException(sprintf('The resource class "%s" does not exist.', $resourceClass));
+ }
+
+ $propertyNames = [];
+
+ foreach ($this->paths as $path) {
+ try {
+ $resources = Yaml::parse(file_get_contents($path));
+ } catch (ParseException $parseException) {
+ $parseException->setParsedFile($path);
+
+ throw $parseException;
+ }
+
+ if (null === $resources = $resources['resources'] ?? $resources) {
+ continue;
+ }
+
+ if (!is_array($resources)) {
+ throw new InvalidArgumentException(sprintf('"resources" setting is expected to be null or an array, %s given in "%s".', gettype($resources), $path));
+ }
+
+ foreach ($resources as $resourceName => $resource) {
+ if (null === $resource) {
+ continue;
+ }
+
+ if (!is_array($resource)) {
+ throw new InvalidArgumentException(sprintf('"%s" setting is expected to be null or an array, %s given in "%s".', $resourceName, gettype($resource), $path));
+ }
+
+ if (!isset($resource['class'])) {
+ throw new InvalidArgumentException(sprintf('"class" setting is expected to be a string, none given in "%s".', $path));
+ }
+
+ if ($resourceClass !== $resource['class'] || !isset($resource['properties'])) {
+ continue;
+ }
+
+ if (!is_array($resource['properties'])) {
+ throw new InvalidArgumentException(sprintf('"properties" setting is expected to be null or an array, %s given in "%s".', gettype($resource['properties']), $path));
+ }
+
+ foreach ($resource['properties'] as $propertyName => $propertyValues) {
+ if (null === $propertyValues) {
+ continue;
+ }
+
+ if (!is_array($propertyValues)) {
+ throw new InvalidArgumentException(sprintf('"%s" setting is expected to be null or an array, %s given in "%s".', $propertyName, gettype($propertyValues), $path));
+ }
+
+ $propertyNames[$propertyName] = true;
+ }
+ }
+ }
+
+ if (isset($propertyNameCollection)) {
+ foreach ($propertyNameCollection as $propertyName) {
+ $propertyNames[$propertyName] = true;
+ }
+ }
+
+ return new PropertyNameCollection(array_keys($propertyNames));
+ }
+}
diff --git a/src/Metadata/Resource/Factory/XmlResourceMetadataFactory.php b/src/Metadata/Resource/Factory/XmlResourceMetadataFactory.php
index 74cb2e04662..f3d7d1157ac 100644
--- a/src/Metadata/Resource/Factory/XmlResourceMetadataFactory.php
+++ b/src/Metadata/Resource/Factory/XmlResourceMetadataFactory.php
@@ -83,9 +83,9 @@ private function getMetadata(string $resourceClass) : array
}
return [
- (string) $resource['shortName'] ?? null,
- (string) $resource['description'] ?? null,
- (string) $resource['iri'] ?? null,
+ (string) $resource['shortName'] ?: null,
+ (string) $resource['description'] ?: null,
+ (string) $resource['iri'] ?: null,
$this->getAttributes($resource, 'itemOperation') ?: null,
$this->getAttributes($resource, 'collectionOperation') ?: null,
$this->getAttributes($resource, 'attribute') ?: null,
diff --git a/src/Metadata/Resource/Factory/YamlResourceMetadataFactory.php b/src/Metadata/Resource/Factory/YamlResourceMetadataFactory.php
index 22ad3d70eb9..dfcc21979ec 100644
--- a/src/Metadata/Resource/Factory/YamlResourceMetadataFactory.php
+++ b/src/Metadata/Resource/Factory/YamlResourceMetadataFactory.php
@@ -14,7 +14,8 @@
use ApiPlatform\Core\Exception\InvalidArgumentException;
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
-use Symfony\Component\Yaml\Parser as YamlParser;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Yaml;
/**
* Creates a resource metadata from yml {@see Resource} configuration.
@@ -23,7 +24,6 @@
*/
final class YamlResourceMetadataFactory implements ResourceMetadataFactoryInterface
{
- private $yamlParser;
private $decorated;
private $paths;
@@ -33,13 +33,14 @@ final class YamlResourceMetadataFactory implements ResourceMetadataFactoryInterf
*/
public function __construct(array $paths, ResourceMetadataFactoryInterface $decorated = null)
{
- $this->yamlParser = new YamlParser();
$this->paths = $paths;
$this->decorated = $decorated;
}
/**
* {@inheritdoc}
+ *
+ * @throws ParseException
*/
public function create(string $resourceClass) : ResourceMetadata
{
@@ -61,7 +62,14 @@ public function create(string $resourceClass) : ResourceMetadata
$metadata = null;
foreach ($this->paths as $path) {
- $resources = $this->yamlParser->parse(file_get_contents($path));
+ try {
+ $resources = Yaml::parse(file_get_contents($path));
+ } catch (ParseException $parseException) {
+ $parseException->setParsedFile($path);
+
+ throw $parseException;
+ }
+
$resources = $resources['resources'] ?? $resources;
foreach ($resources as $resource) {
diff --git a/src/Metadata/Resource/Factory/YamlResourceNameCollectionFactory.php b/src/Metadata/Resource/Factory/YamlResourceNameCollectionFactory.php
index 490220a8dfe..909ce85e3a1 100644
--- a/src/Metadata/Resource/Factory/YamlResourceNameCollectionFactory.php
+++ b/src/Metadata/Resource/Factory/YamlResourceNameCollectionFactory.php
@@ -13,7 +13,8 @@
use ApiPlatform\Core\Exception\InvalidArgumentException;
use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection;
-use Symfony\Component\Yaml\Parser as YamlParser;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Yaml;
/**
* Creates a resource name collection from {@see Resource} configuration files.
@@ -22,7 +23,6 @@
*/
final class YamlResourceNameCollectionFactory implements ResourceNameCollectionFactoryInterface
{
- private $yamlParser;
private $paths;
private $decorated;
@@ -32,13 +32,14 @@ final class YamlResourceNameCollectionFactory implements ResourceNameCollectionF
*/
public function __construct(array $paths, ResourceNameCollectionFactoryInterface $decorated = null)
{
- $this->yamlParser = new YamlParser();
$this->paths = $paths;
$this->decorated = $decorated;
}
/**
* {@inheritdoc}
+ *
+ * @throws ParseException
*/
public function create() : ResourceNameCollection
{
@@ -50,7 +51,14 @@ public function create() : ResourceNameCollection
}
foreach ($this->paths as $path) {
- $resources = $this->yamlParser->parse(file_get_contents($path));
+ try {
+ $resources = Yaml::parse(file_get_contents($path));
+ } catch (ParseException $parseException) {
+ $parseException->setParsedFile($path);
+
+ throw $parseException;
+ }
+
$resources = $resources['resources'] ?? $resources;
foreach ($resources as $resource) {
diff --git a/src/Metadata/schema/metadata.xsd b/src/Metadata/schema/metadata.xsd
index 8564d2adc38..0b6b1271a88 100644
--- a/src/Metadata/schema/metadata.xsd
+++ b/src/Metadata/schema/metadata.xsd
@@ -17,6 +17,7 @@
+
@@ -30,4 +31,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
index 9e8de1ab717..267c90eb03f 100644
--- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
+++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
@@ -219,6 +219,16 @@ private function getContainerBuilderProphecy()
$definition = $definitionProphecy->reveal();
$containerBuilderProphecy->getDefinition('api_platform.metadata.resource.metadata_factory.yaml')->willReturn($definition)->shouldBeCalled();
+ $definitionProphecy = $this->prophesize(Definition::class);
+ $definitionProphecy->replaceArgument(0, [])->shouldBeCalled();
+ $definition = $definitionProphecy->reveal();
+ $containerBuilderProphecy->getDefinition('api_platform.metadata.property.name_collection_factory.yaml')->willReturn($definition)->shouldBeCalled();
+
+ $definitionProphecy = $this->prophesize(Definition::class);
+ $definitionProphecy->replaceArgument(0, [])->shouldBeCalled();
+ $definition = $definitionProphecy->reveal();
+ $containerBuilderProphecy->getDefinition('api_platform.metadata.property.metadata_factory.yaml')->willReturn($definition)->shouldBeCalled();
+
$definitionProphecy = $this->prophesize(Definition::class);
$definitionProphecy->replaceArgument(0, [])->shouldBeCalled();
$definition = $definitionProphecy->reveal();
@@ -229,6 +239,16 @@ private function getContainerBuilderProphecy()
$definition = $definitionProphecy->reveal();
$containerBuilderProphecy->getDefinition('api_platform.metadata.resource.metadata_factory.xml')->willReturn($definition)->shouldBeCalled();
+ $definitionProphecy = $this->prophesize(Definition::class);
+ $definitionProphecy->replaceArgument(0, [])->shouldBeCalled();
+ $definition = $definitionProphecy->reveal();
+ $containerBuilderProphecy->getDefinition('api_platform.metadata.property.name_collection_factory.xml')->willReturn($definition)->shouldBeCalled();
+
+ $definitionProphecy = $this->prophesize(Definition::class);
+ $definitionProphecy->replaceArgument(0, [])->shouldBeCalled();
+ $definition = $definitionProphecy->reveal();
+ $containerBuilderProphecy->getDefinition('api_platform.metadata.property.metadata_factory.xml')->willReturn($definition)->shouldBeCalled();
+
$definitions = [
'api_platform.action.documentation',
'api_platform.action.placeholder',
@@ -301,9 +321,13 @@ private function getContainerBuilderProphecy()
'api_platform.metadata.property.metadata_factory.property_info',
'api_platform.metadata.property.metadata_factory.serializer',
'api_platform.metadata.property.metadata_factory.validator',
+ 'api_platform.metadata.property.metadata_factory.xml',
+ 'api_platform.metadata.property.metadata_factory.yaml',
'api_platform.metadata.property.name_collection_factory.cached',
'api_platform.metadata.property.name_collection_factory.inherited',
'api_platform.metadata.property.name_collection_factory.property_info',
+ 'api_platform.metadata.property.name_collection_factory.xml',
+ 'api_platform.metadata.property.name_collection_factory.yaml',
'api_platform.metadata.resource.metadata_factory.annotation',
'api_platform.metadata.resource.metadata_factory.cached',
'api_platform.metadata.resource.metadata_factory.operation',
diff --git a/tests/Fixtures/FileConfigurations/parse_exception.yml b/tests/Fixtures/FileConfigurations/parse_exception.yml
new file mode 100644
index 00000000000..87ff9d1e3bb
--- /dev/null
+++ b/tests/Fixtures/FileConfigurations/parse_exception.yml
@@ -0,0 +1,2 @@
+parse
+ exception
diff --git a/tests/Fixtures/FileConfigurations/propertiesinvalid.yml b/tests/Fixtures/FileConfigurations/propertiesinvalid.yml
new file mode 100644
index 00000000000..eac93bc1004
--- /dev/null
+++ b/tests/Fixtures/FileConfigurations/propertiesinvalid.yml
@@ -0,0 +1,4 @@
+resources:
+ configdummy:
+ class: 'ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy'
+ properties: 'invalid'
diff --git a/tests/Fixtures/FileConfigurations/propertyinvalid.xml b/tests/Fixtures/FileConfigurations/propertyinvalid.xml
new file mode 100644
index 00000000000..5d5cd30bfa1
--- /dev/null
+++ b/tests/Fixtures/FileConfigurations/propertyinvalid.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+ Foo
+
+
+
diff --git a/tests/Fixtures/FileConfigurations/propertyinvalid.yml b/tests/Fixtures/FileConfigurations/propertyinvalid.yml
new file mode 100644
index 00000000000..fe07282f62a
--- /dev/null
+++ b/tests/Fixtures/FileConfigurations/propertyinvalid.yml
@@ -0,0 +1,5 @@
+resources:
+ configdummy:
+ class: 'ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy'
+ properties:
+ 'foo': 'invalid'
diff --git a/tests/Fixtures/FileConfigurations/resources.xml b/tests/Fixtures/FileConfigurations/resources.xml
index 9dec432e086..f7cc41049c3 100644
--- a/tests/Fixtures/FileConfigurations/resources.xml
+++ b/tests/Fixtures/FileConfigurations/resources.xml
@@ -34,5 +34,28 @@
hydra:Operation
File config Dummy
+
+
+
+ Foo
+
+
+
+ Bar
+
+ Baz
+
+ Baz
+
+
+
diff --git a/tests/Fixtures/FileConfigurations/resources.yml b/tests/Fixtures/FileConfigurations/resources.yml
index 4ffaa1e9a42..3b574d15a17 100644
--- a/tests/Fixtures/FileConfigurations/resources.yml
+++ b/tests/Fixtures/FileConfigurations/resources.yml
@@ -23,3 +23,19 @@ resources:
'@type': 'hydra:Operation'
'@hydra:title': 'File config Dummy'
iri: 'someirischema'
+ properties:
+ 'foo':
+ description: 'The dummy foo'
+ readable: true
+ writable: true
+ readableLink: false
+ writableLink: false
+ required: true
+ attributes:
+ 'foo': ['Foo']
+ 'bar':
+ 0: ['Bar']
+ 'baz': 'Baz'
+ 'baz': 'Baz'
+ 'name':
+ description: 'The dummy name'
diff --git a/tests/Fixtures/FileConfigurations/resourcesinvalid.yml b/tests/Fixtures/FileConfigurations/resourcesinvalid.yml
new file mode 100644
index 00000000000..16b01989f54
--- /dev/null
+++ b/tests/Fixtures/FileConfigurations/resourcesinvalid.yml
@@ -0,0 +1 @@
+resources: 'invalid'
diff --git a/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php b/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php
index db32369f7f0..7b6b15c958f 100644
--- a/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php
+++ b/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php
@@ -36,6 +36,13 @@ class FileConfigDummy
*/
private $name;
+ /**
+ * @var string
+ *
+ * @ORM\Column
+ */
+ private $foo;
+
public function getId()
{
return $this->id;
@@ -50,4 +57,14 @@ public function getName()
{
return $this->name;
}
+
+ public function setFoo($foo)
+ {
+ $this->foo = $foo;
+ }
+
+ public function getFoo()
+ {
+ return $this->foo;
+ }
}
diff --git a/tests/Fixtures/TestBundle/Resources/config/api_resources.yml b/tests/Fixtures/TestBundle/Resources/config/api_resources.yml
index 0d9c6aaf858..cfa777ec990 100644
--- a/tests/Fixtures/TestBundle/Resources/config/api_resources.yml
+++ b/tests/Fixtures/TestBundle/Resources/config/api_resources.yml
@@ -7,3 +7,6 @@ resources:
custom_operation:
method: 'GET'
controller: 'app.config_dummy_resource.action'
+ properties:
+ foo:
+ description: 'The dummy foo'
diff --git a/tests/Metadata/Property/Factory/FileConfigurationMetadataFactoryProvider.php b/tests/Metadata/Property/Factory/FileConfigurationMetadataFactoryProvider.php
new file mode 100644
index 00000000000..b4bac39f1e9
--- /dev/null
+++ b/tests/Metadata/Property/Factory/FileConfigurationMetadataFactoryProvider.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Tests\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
+
+/**
+ * Property metadata provider for file configured factories tests.
+ *
+ * @author Baptiste Meyer
+ */
+abstract class FileConfigurationMetadataFactoryProvider extends \PHPUnit_Framework_TestCase
+{
+ public function propertyMetadataProvider()
+ {
+ $metadata = [
+ 'description' => 'The dummy foo',
+ 'readable' => true,
+ 'writable' => true,
+ 'readableLink' => false,
+ 'writableLink' => false,
+ 'required' => true,
+ 'attributes' => [
+ 'foo' => [
+ 'Foo',
+ ],
+ 'bar' => [
+ ['Bar'],
+ 'baz' => 'Baz',
+ ],
+ 'baz' => 'Baz',
+ ],
+ ];
+
+ return [[$this->getPropertyMetadata($metadata)]];
+ }
+
+ public function decoratedPropertyMetadataProvider()
+ {
+ $metadata = [
+ 'description' => 'The dummy foo',
+ 'readable' => true,
+ 'writable' => true,
+ 'readableLink' => true,
+ 'writableLink' => false,
+ 'required' => true,
+ 'identifier' => false,
+ 'attributes' => [
+ 'Foo',
+ ],
+ ];
+
+ return [[$this->getPropertyMetadata($metadata)]];
+ }
+
+ private function getPropertyMetadata(array $metadata) : PropertyMetadata
+ {
+ $propertyMetadata = new PropertyMetadata();
+
+ foreach ($metadata as $propertyName => $propertyValue) {
+ $propertyMetadata = $propertyMetadata->{'with'.ucfirst($propertyName)}($propertyValue);
+ }
+
+ return $propertyMetadata;
+ }
+}
diff --git a/tests/Metadata/Property/Factory/XmlPropertyMetadataFactoryTest.php b/tests/Metadata/Property/Factory/XmlPropertyMetadataFactoryTest.php
new file mode 100644
index 00000000000..747ee53fd70
--- /dev/null
+++ b/tests/Metadata/Property/Factory/XmlPropertyMetadataFactoryTest.php
@@ -0,0 +1,90 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Tests\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
+use ApiPlatform\Core\Metadata\Property\Factory\XmlPropertyMetadataFactory;
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy;
+
+/**
+ * @author Baptiste Meyer
+ */
+class XmlPropertyMetadataFactoryTest extends FileConfigurationMetadataFactoryProvider
+{
+ /**
+ * @dataProvider propertyMetadataProvider
+ */
+ public function testCreate(PropertyMetadata $expectedPropertyMetadata)
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.xml';
+
+ $propertyMetadataFactory = new XmlPropertyMetadataFactory([$configPath]);
+ $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo');
+
+ $this->assertInstanceOf(PropertyMetadata::class, $propertyMetadata);
+ $this->assertEquals($expectedPropertyMetadata, $propertyMetadata);
+ }
+
+ /**
+ * @dataProvider decoratedPropertyMetadataProvider
+ */
+ public function testCreateWithParentPropertyMetadataFactory(PropertyMetadata $expectedPropertyMetadata)
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.xml';
+
+ $decorated = $this->prophesize(PropertyMetadataFactoryInterface::class);
+ $decorated
+ ->create(FileConfigDummy::class, 'foo', [])
+ ->willReturn(new PropertyMetadata(null, null, null, null, true, null, null, false, null, null, ['Foo']))
+ ->shouldBeCalled();
+
+ $propertyMetadataFactory = new XmlPropertyMetadataFactory([$configPath], $decorated->reveal());
+ $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo');
+
+ $this->assertInstanceOf(PropertyMetadata::class, $propertyMetadata);
+ $this->assertEquals($expectedPropertyMetadata, $propertyMetadata);
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\PropertyNotFoundException
+ * @expectedExceptionMessage Property "foo" of the resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist" not found.
+ */
+ public function testCreateWithNonexistentResource()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenotfound.xml';
+
+ (new XmlPropertyMetadataFactory([$configPath]))->create(\ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist::class, 'foo');
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\PropertyNotFoundException
+ * @expectedExceptionMessage Property "bar" of the resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy" not found.
+ */
+ public function testCreateWithNonexistentProperty()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.xml';
+
+ (new XmlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'bar');
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+ * @expectedExceptionMessageRegExp /.+Element 'foo': This element is not expected\..+/
+ */
+ public function testCreateWithInvalidXml()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertyinvalid.xml';
+
+ (new XmlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+ }
+}
diff --git a/tests/Metadata/Property/Factory/XmlPropertyNameCollectionFactoryTest.php b/tests/Metadata/Property/Factory/XmlPropertyNameCollectionFactoryTest.php
new file mode 100644
index 00000000000..38a8c16b680
--- /dev/null
+++ b/tests/Metadata/Property/Factory/XmlPropertyNameCollectionFactoryTest.php
@@ -0,0 +1,71 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Tests\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
+use ApiPlatform\Core\Metadata\Property\Factory\XmlPropertyNameCollectionFactory;
+use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy;
+
+/**
+ * @author Baptiste Meyer
+ */
+class XmlPropertyNameCollectionFactoryTest extends \PHPUnit_Framework_TestCase
+{
+ public function testCreate()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.xml';
+
+ $this->assertEquals(
+ (new XmlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class),
+ new PropertyNameCollection(['foo', 'name'])
+ );
+ }
+
+ public function testCreateWithParentPropertyNameCollectionFactory()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.xml';
+
+ $decorated = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
+ $decorated
+ ->create(FileConfigDummy::class, [])
+ ->willReturn(new PropertyNameCollection(['id']))
+ ->shouldBeCalled();
+
+ $this->assertEquals(
+ (new XmlPropertyNameCollectionFactory([$configPath], $decorated->reveal()))->create(FileConfigDummy::class),
+ new PropertyNameCollection(['foo', 'name', 'id'])
+ );
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\ResourceClassNotFoundException
+ * @expectedExceptionMessage The resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist" does not exist.
+ */
+ public function testCreateWithNonexistentResource()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenotfound.xml';
+
+ (new XmlPropertyNameCollectionFactory([$configPath]))->create(\ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist::class);
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+ * @expectedExceptionMessageRegExp /.+Element 'foo': This element is not expected\..+/
+ */
+ public function testCreateWithInvalidXml()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertyinvalid.xml';
+
+ (new XmlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+ }
+}
diff --git a/tests/Metadata/Property/Factory/YamlPropertyMetadataFactoryTest.php b/tests/Metadata/Property/Factory/YamlPropertyMetadataFactoryTest.php
new file mode 100644
index 00000000000..8c902002d4f
--- /dev/null
+++ b/tests/Metadata/Property/Factory/YamlPropertyMetadataFactoryTest.php
@@ -0,0 +1,133 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Tests\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
+use ApiPlatform\Core\Metadata\Property\Factory\YamlPropertyMetadataFactory;
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy;
+
+/**
+ * @author Baptiste Meyer
+ */
+class YamlPropertyMetadataFactoryTest extends FileConfigurationMetadataFactoryProvider
+{
+ /**
+ * @dataProvider propertyMetadataProvider
+ */
+ public function testCreate(PropertyMetadata $expectedPropertyMetadata)
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.yml';
+
+ $propertyMetadataFactory = new YamlPropertyMetadataFactory([$configPath]);
+ $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo');
+
+ $this->assertInstanceOf(PropertyMetadata::class, $propertyMetadata);
+ $this->assertEquals($expectedPropertyMetadata, $propertyMetadata);
+ }
+
+ /**
+ * @dataProvider decoratedPropertyMetadataProvider
+ */
+ public function testCreateWithParentPropertyMetadataFactory(PropertyMetadata $expectedPropertyMetadata)
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.yml';
+
+ $decorated = $this->prophesize(PropertyMetadataFactoryInterface::class);
+ $decorated
+ ->create(FileConfigDummy::class, 'foo', [])
+ ->willReturn(new PropertyMetadata(null, null, null, null, true, null, null, false, null, null, ['Foo']))
+ ->shouldBeCalled();
+
+ $propertyMetadataFactory = new YamlPropertyMetadataFactory([$configPath], $decorated->reveal());
+ $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo');
+
+ $this->assertInstanceOf(PropertyMetadata::class, $propertyMetadata);
+ $this->assertEquals($expectedPropertyMetadata, $propertyMetadata);
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\PropertyNotFoundException
+ * @expectedExceptionMessage Property "foo" of the resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist" not found.
+ */
+ public function testCreateWithNonexistentResource()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenotfound.yml';
+
+ (new YamlPropertyMetadataFactory([$configPath]))->create(\ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist::class, 'foo');
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\PropertyNotFoundException
+ * @expectedExceptionMessage Property "bar" of the resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy" not found.
+ */
+ public function testCreateWithNonexistentProperty()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.yml';
+
+ (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'bar');
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+ * @expectedExceptionMessageRegExp /"resources" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/resourcesinvalid\.yml"\./
+ */
+ public function testCreateWithMalformedResourcesSetting()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcesinvalid.yml';
+
+ (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+ * @expectedExceptionMessageRegExp /"properties" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/propertiesinvalid\.yml"\./
+ */
+ public function testCreateWithMalformedPropertiesSetting()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertiesinvalid.yml';
+
+ (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+ * @expectedExceptionMessageRegExp /"foo" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/propertyinvalid\.yml"\./
+ */
+ public function testCreateWithMalformedPropertySetting()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertyinvalid.yml';
+
+ (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+ * @expectedExceptionMessageRegExp /"class" setting is expected to be a string, none given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/resourcenoclass\.yml"\./
+ */
+ public function testCreateWithoutResourceClass()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenoclass.yml';
+
+ (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Yaml\Exception\ParseException
+ */
+ public function testCreateWithMalformedYaml()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/parse_exception.yml';
+
+ (new YamlPropertyMetadataFactory([$configPath]))->create(FileConfigDummy::class, 'foo');
+ }
+}
diff --git a/tests/Metadata/Property/Factory/YamlPropertyNameCollectionFactoryTest.php b/tests/Metadata/Property/Factory/YamlPropertyNameCollectionFactoryTest.php
new file mode 100644
index 00000000000..83d8219a327
--- /dev/null
+++ b/tests/Metadata/Property/Factory/YamlPropertyNameCollectionFactoryTest.php
@@ -0,0 +1,114 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Tests\Metadata\Property\Factory;
+
+use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
+use ApiPlatform\Core\Metadata\Property\Factory\YamlPropertyNameCollectionFactory;
+use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy;
+
+/**
+ * @author Baptiste Meyer
+ */
+class YamlPropertyNameCollectionFactoryTest extends \PHPUnit_Framework_TestCase
+{
+ public function testCreate()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.yml';
+
+ $this->assertEquals(
+ (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class),
+ new PropertyNameCollection(['foo', 'name'])
+ );
+ }
+
+ public function testCreateWithParentPropertyMetadataFactory()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resources.yml';
+
+ $decorated = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
+ $decorated
+ ->create(FileConfigDummy::class, [])
+ ->willReturn(new PropertyNameCollection(['id']))
+ ->shouldBeCalled();
+
+ $this->assertEquals(
+ (new YamlPropertyNameCollectionFactory([$configPath], $decorated->reveal()))->create(FileConfigDummy::class),
+ new PropertyNameCollection(['foo', 'name', 'id'])
+ );
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\ResourceClassNotFoundException
+ * @expectedExceptionMessage The resource class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist" does not exist.
+ */
+ public function testCreateWithNonexistentResource()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenotfound.yml';
+
+ (new YamlPropertyNameCollectionFactory([$configPath]))->create(\ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist::class);
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+ * @expectedExceptionMessageRegExp /"resources" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/resourcesinvalid\.yml"\./
+ */
+ public function testCreateWithMalformedResourcesSetting()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcesinvalid.yml';
+
+ (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+ * @expectedExceptionMessageRegExp /"properties" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/propertiesinvalid\.yml"\./
+ */
+ public function testCreateWithMalformedPropertiesSetting()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertiesinvalid.yml';
+
+ (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+ * @expectedExceptionMessageRegExp /"foo" setting is expected to be null or an array, string given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/propertyinvalid\.yml"\./
+ */
+ public function testCreateWithMalformedPropertySetting()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/propertyinvalid.yml';
+
+ (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+ }
+
+ /**
+ * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
+ * @expectedExceptionMessageRegExp /"class" setting is expected to be a string, none given in ".+\/\.\.\/\.\.\/\.\.\/Fixtures\/FileConfigurations\/resourcenoclass\.yml"\./
+ */
+ public function testCreateWithoutResourceClass()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/resourcenoclass.yml';
+
+ (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Yaml\Exception\ParseException
+ */
+ public function testCreateWithMalformedYaml()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/parse_exception.yml';
+
+ (new YamlPropertyNameCollectionFactory([$configPath]))->create(FileConfigDummy::class);
+ }
+}
diff --git a/tests/Metadata/Resource/Factory/FileConfigurationMetadataFactoryProvider.php b/tests/Metadata/Resource/Factory/FileConfigurationMetadataFactoryProvider.php
index b70bcf78184..86f88ce8260 100644
--- a/tests/Metadata/Resource/Factory/FileConfigurationMetadataFactoryProvider.php
+++ b/tests/Metadata/Resource/Factory/FileConfigurationMetadataFactoryProvider.php
@@ -14,7 +14,7 @@
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
/**
- * Resource metadata provider for file configurated factories tests.
+ * Resource metadata provider for file configured factories tests.
*
* @author Antoine Bluchet
*/
diff --git a/tests/Metadata/Resource/Factory/YamlResourceMetadataFactoryTest.php b/tests/Metadata/Resource/Factory/YamlResourceMetadataFactoryTest.php
index d2a8db7b42e..cb60ae70189 100644
--- a/tests/Metadata/Resource/Factory/YamlResourceMetadataFactoryTest.php
+++ b/tests/Metadata/Resource/Factory/YamlResourceMetadataFactoryTest.php
@@ -127,4 +127,14 @@ public function testYamlExistingParentResourceMetadataFactory(ResourceMetadata $
$this->assertEquals($expectedResourceMetadata, $resourceMetadata);
}
+
+ /**
+ * @expectedException \Symfony\Component\Yaml\Exception\ParseException
+ */
+ public function testCreateWithMalformedYaml()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/parse_exception.yml';
+
+ (new YamlResourceMetadataFactory([$configPath]))->create(FileConfigDummy::class);
+ }
}
diff --git a/tests/Metadata/Resource/Factory/YamlResourceNameCollectionFactoryTest.php b/tests/Metadata/Resource/Factory/YamlResourceNameCollectionFactoryTest.php
index dc629c3dade..2288f1c43fd 100644
--- a/tests/Metadata/Resource/Factory/YamlResourceNameCollectionFactoryTest.php
+++ b/tests/Metadata/Resource/Factory/YamlResourceNameCollectionFactoryTest.php
@@ -55,4 +55,14 @@ public function testNoClassYamlResourceNameCollectionFactory()
$resourceMetadataFactory->create();
}
+
+ /**
+ * @expectedException \Symfony\Component\Yaml\Exception\ParseException
+ */
+ public function testCreateWithMalformedYaml()
+ {
+ $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/parse_exception.yml';
+
+ (new YamlResourceNameCollectionFactory([$configPath]))->create();
+ }
}