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(); + } }