<?php
/*
* This file is part of the FOSRestBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS \ RestBundle \ DependencyInjection ;
use Symfony \ Component \ Config \ Definition \ Builder \ ArrayNodeDefinition ;
use Symfony \ Component \ Config \ Definition \ Builder \ TreeBuilder ;
use Symfony \ Component \ Config \ Definition \ ConfigurationInterface ;
use Symfony \ Component \ Config \ Definition \ Exception \ InvalidConfigurationException ;
use Symfony \ Component \ HttpFoundation \ Response ;
use Symfony \ Component \ OptionsResolver \ OptionsResolver ;
use Symfony \ Component \ Serializer \ Encoder \ XmlEncoder ;
/**
* This class contains the configuration information for the bundle.
*
* This information is solely responsible for how the different configuration
* sections are normalized, and merged.
*
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
*
* @internal
*/
final class Configuration implements ConfigurationInterface
{
/**
* Default debug mode value.
*
* @var bool
*/
private $debug ;
public function __construct ( bool $debug )
{
$this -> debug = $debug ;
}
public function getConfigTreeBuilder (): TreeBuilder
{
$treeBuilder = new TreeBuilder ( 'fos_rest' );
if ( method_exists ( $treeBuilder , 'getRootNode' )) {
$rootNode = $treeBuilder -> getRootNode ();
} else {
$rootNode = $treeBuilder -> root ( 'fos_rest' );
}
$rootNode
-> children ()
-> scalarNode ( 'disable_csrf_role' )-> defaultNull ()-> end ()
-> arrayNode ( 'access_denied_listener' )
-> canBeEnabled ()
-> setDeprecated ( 'The "%path%.%node%" option is deprecated since FOSRestBundle 2.8.' )
-> beforeNormalization ()
-> ifArray ()-> then (function ( $v ) {
if (!empty( $v ) && empty( $v [ 'formats' ])) {
unset( $v [ 'enabled' ]);
$v = [ 'enabled' => true , 'formats' => $v ];
}
return $v ;
})
-> end ()
-> fixXmlConfig ( 'format' , 'formats' )
-> children ()
-> scalarNode ( 'service' )-> defaultNull ()-> end ()
-> arrayNode ( 'formats' )
-> useAttributeAsKey ( 'name' )
-> prototype ( 'boolean' )-> end ()
-> end ()
-> end ()
-> end ()
-> scalarNode ( 'unauthorized_challenge' )-> defaultNull ()-> end ()
-> arrayNode ( 'param_fetcher_listener' )
-> beforeNormalization ()
-> ifString ()
-> then (function ( $v ) {
return [ 'enabled' => in_array ( $v , [ 'force' , 'true' ]), 'force' => 'force' === $v ];
})
-> end ()
-> canBeEnabled ()
-> children ()
-> booleanNode ( 'force' )-> defaultFalse ()-> end ()
-> scalarNode ( 'service' )-> defaultNull ()-> end ()
-> end ()
-> end ()
-> scalarNode ( 'cache_dir' )-> cannotBeEmpty ()-> defaultValue ( '%kernel.cache_dir%/fos_rest' )-> end ()
-> arrayNode ( 'allowed_methods_listener' )
-> canBeEnabled ()
-> children ()
-> scalarNode ( 'service' )-> defaultNull ()-> end ()
-> end ()
-> end ()
-> arrayNode ( 'routing_loader' )
-> addDefaultsIfNotSet ()
-> beforeNormalization ()
-> ifTrue (function ( $v ) { return isset( $v [ 'enabled' ]) && false !== $v [ 'enabled' ]; })
-> then (function ( $v ) {
@ trigger_error ( 'Enabling the route generation feature is deprecated since FOSRestBundle 2.8.' , E_USER_DEPRECATED );
return $v ;
})
-> end ()
-> beforeNormalization ()
-> ifTrue (function ( $v ) { return is_bool ( $v ); })
-> then (function ( $v ) {
return [
'enabled' => $v ,
];
})
-> end ()
-> children ()
-> booleanNode ( 'enabled' )
-> defaultValue (function () {
@ trigger_error ( 'Enabling the route generation feature is deprecated since FOSRestBundle 2.8.' , E_USER_DEPRECATED );
return true ;
})
-> end ()
-> scalarNode ( 'default_format' )-> defaultNull ()-> end ()
-> scalarNode ( 'prefix_methods' )-> defaultTrue ()-> end ()
-> scalarNode ( 'include_format' )-> defaultTrue ()-> end ()
-> end ()
-> end ()
-> arrayNode ( 'body_converter' )
-> canBeEnabled ()
-> children ()
-> scalarNode ( 'validate' )
-> defaultFalse ()
-> beforeNormalization ()
-> ifTrue ()
-> then (function ( $value ) {
if (! class_exists ( OptionsResolver ::class)) {
throw new InvalidConfigurationException ( "'body_converter.validate: true' requires OptionsResolver component installation ( composer require symfony/options-resolver )" );
}
return $value ;
})
-> end ()
-> end ()
-> scalarNode ( 'validation_errors_argument' )-> defaultValue ( 'validationErrors' )-> end ()
-> end ()
-> end ()
-> arrayNode ( 'service' )
-> addDefaultsIfNotSet ()
-> children ()
-> scalarNode ( 'router' )-> defaultValue ( 'router' )-> setDeprecated ( 'The "%path%.%node%" configuration key has been deprecated in FOSRestBundle 2.8.' )-> end ()
-> scalarNode ( 'templating' )
-> defaultValue ( 'templating' )
-> setDeprecated ( 'The "%path%.%node%" option is deprecated since FOSRestBundle 2.8.' )
-> end ()
-> scalarNode ( 'serializer' )-> defaultNull ()-> end ()
-> scalarNode ( 'view_handler' )-> defaultValue ( 'fos_rest.view_handler.default' )-> end ()
-> scalarNode ( 'inflector' )
-> defaultValue ( 'fos_rest.inflector.doctrine' )
-> setDeprecated ( 'The "%path%.%node%" option is deprecated since FOSRestBundle 2.8.' )
-> end ()
-> scalarNode ( 'validator' )-> defaultValue ( 'validator' )-> end ()
-> end ()
-> end ()
-> arrayNode ( 'serializer' )
-> addDefaultsIfNotSet ()
-> children ()
-> scalarNode ( 'version' )-> defaultNull ()-> end ()
-> arrayNode ( 'groups' )
-> prototype ( 'scalar' )-> end ()
-> end ()
-> booleanNode ( 'serialize_null' )-> defaultFalse ()-> end ()
-> end ()
-> end ()
-> arrayNode ( 'zone' )
-> cannotBeOverwritten ()
-> prototype ( 'array' )
-> fixXmlConfig ( 'ip' )
-> children ()
-> scalarNode ( 'path' )
-> defaultNull ()
-> info ( 'use the urldecoded format' )
-> example ( '^/path to resource/' )
-> end ()
-> scalarNode ( 'host' )-> defaultNull ()-> end ()
-> arrayNode ( 'methods' )
-> beforeNormalization ()-> ifString ()-> then (function ( $v ) {
return preg_split ( '/\s*,\s*/' , $v );
})-> end ()
-> prototype ( 'scalar' )-> end ()
-> end ()
-> arrayNode ( 'ips' )
-> beforeNormalization ()-> ifString ()-> then (function ( $v ) {
return [ $v ];
})-> end ()
-> prototype ( 'scalar' )-> end ()
-> end ()
-> end ()
-> end ()
-> end ()
-> end ();
$this -> addViewSection ( $rootNode );
$this -> addExceptionSection ( $rootNode );
$this -> addBodyListenerSection ( $rootNode );
$this -> addFormatListenerSection ( $rootNode );
$this -> addVersioningSection ( $rootNode );
return $treeBuilder ;
}
private function addViewSection ( ArrayNodeDefinition $rootNode )
{
$rootNode
-> children ()
-> arrayNode ( 'view' )
-> fixXmlConfig ( 'format' , 'formats' )
-> fixXmlConfig ( 'mime_type' , 'mime_types' )
-> fixXmlConfig ( 'templating_format' , 'templating_formats' )
-> fixXmlConfig ( 'force_redirect' , 'force_redirects' )
-> addDefaultsIfNotSet ()
-> children ()
-> scalarNode ( 'default_engine' )
-> setDeprecated ( 'The "%path%.%node%" option has been deprecated in FOSRestBundle 2.8.' )
-> defaultValue ( 'twig' )
-> end ()
-> arrayNode ( 'force_redirects' )
-> setDeprecated ( 'The "%path%.%node%" option has been deprecated in FOSRestBundle 2.8.' )
-> useAttributeAsKey ( 'name' )
-> defaultValue ([ 'html' => true ])
-> prototype ( 'boolean' )-> end ()
-> end ()
-> arrayNode ( 'mime_types' )
-> canBeEnabled ()
-> beforeNormalization ()
-> ifArray ()-> then (function ( $v ) {
if (!empty( $v ) && empty( $v [ 'formats' ])) {
unset( $v [ 'enabled' ]);
$v = [ 'enabled' => true , 'formats' => $v ];
}
return $v ;
})
-> end ()
-> fixXmlConfig ( 'format' , 'formats' )
-> children ()
-> scalarNode ( 'service' )-> defaultNull ()-> end ()
-> arrayNode ( 'formats' )
-> useAttributeAsKey ( 'name' )
-> prototype ( 'array' )
-> beforeNormalization ()
-> ifString ()
-> then (function ( $v ) { return [ $v ]; })
-> end ()
-> prototype ( 'scalar' )-> end ()
-> end ()
-> end ()
-> end ()
-> end ()
-> arrayNode ( 'formats' )
-> useAttributeAsKey ( 'name' )
-> defaultValue ([ 'json' => true , 'xml' => true ])
-> prototype ( 'boolean' )-> end ()
-> end ()
-> arrayNode ( 'templating_formats' )
-> setDeprecated ( 'The "%path%.%node%" configuration key has been deprecated in FOSRestBundle 2.8.' )
-> useAttributeAsKey ( 'name' )
-> defaultValue ([ 'html' => true ])
-> prototype ( 'boolean' )-> end ()
-> end ()
-> arrayNode ( 'view_response_listener' )
-> beforeNormalization ()
-> ifString ()
-> then (function ( $v ) {
return [ 'enabled' => in_array ( $v , [ 'force' , 'true' ]), 'force' => 'force' === $v ];
})
-> end ()
-> canBeEnabled ()
-> children ()
-> booleanNode ( 'force' )-> defaultFalse ()-> end ()
-> scalarNode ( 'service' )-> defaultNull ()-> end ()
-> end ()
-> end ()
-> scalarNode ( 'failed_validation' )-> defaultValue ( Response :: HTTP_BAD_REQUEST )-> end ()
-> scalarNode ( 'empty_content' )-> defaultValue ( Response :: HTTP_NO_CONTENT )-> end ()
-> booleanNode ( 'serialize_null' )-> defaultFalse ()-> end ()
-> arrayNode ( 'jsonp_handler' )
-> canBeUnset ()
-> children ()
-> scalarNode ( 'callback_param' )-> defaultValue ( 'callback' )-> end ()
-> scalarNode ( 'mime_type' )-> defaultValue ( 'application/javascript+jsonp' )-> end ()
-> end ()
-> end ()
-> end ()
-> end ()
-> end ();
}
private function addBodyListenerSection ( ArrayNodeDefinition $rootNode )
{
$decodersDefaultValue = [ 'json' => 'fos_rest.decoder.json' ];
if ( class_exists ( XmlEncoder ::class)) {
$decodersDefaultValue [ 'xml' ] = 'fos_rest.decoder.xml' ;
}
$rootNode
-> children ()
-> arrayNode ( 'body_listener' )
-> fixXmlConfig ( 'decoder' , 'decoders' )
-> addDefaultsIfNotSet ()
-> canBeUnset ()
-> treatFalseLike ([ 'enabled' => false ])
-> treatTrueLike ([ 'enabled' => true ])
-> treatNullLike ([ 'enabled' => true ])
-> children ()
-> booleanNode ( 'enabled' )
-> defaultValue (function () {
@ trigger_error ( 'The body_listener config has been enabled by default and will be disabled by default in FOSRestBundle 3.0. Please enable or disable it explicitly.' , E_USER_DEPRECATED );
return true ;
})
-> end ()
-> scalarNode ( 'service' )-> defaultNull ()-> end ()
-> scalarNode ( 'default_format' )-> defaultNull ()-> end ()
-> booleanNode ( 'throw_exception_on_unsupported_content_type' )
-> defaultFalse ()
-> end ()
-> arrayNode ( 'decoders' )
-> useAttributeAsKey ( 'name' )
-> defaultValue ( $decodersDefaultValue )
-> prototype ( 'scalar' )-> end ()
-> end ()
-> arrayNode ( 'array_normalizer' )
-> addDefaultsIfNotSet ()
-> beforeNormalization ()
-> ifString ()-> then (function ( $v ) {
return [ 'service' => $v ];
})
-> end ()
-> children ()
-> scalarNode ( 'service' )-> defaultNull ()-> end ()
-> booleanNode ( 'forms' )-> defaultFalse ()-> end ()
-> end ()
-> end ()
-> end ()
-> end ()
-> end ();
}
private function addFormatListenerSection ( ArrayNodeDefinition $rootNode )
{
$rootNode
-> children ()
-> arrayNode ( 'format_listener' )
-> fixXmlConfig ( 'rule' , 'rules' )
-> addDefaultsIfNotSet ()
-> canBeUnset ()
-> beforeNormalization ()
-> ifTrue (function ( $v ) {
// check if we got an assoc array in rules
return isset( $v [ 'rules' ])
&& is_array ( $v [ 'rules' ])
&& array_keys ( $v [ 'rules' ]) !== range ( 0 , count ( $v [ 'rules' ]) - 1 );
})
-> then (function ( $v ) {
$v [ 'rules' ] = [ $v [ 'rules' ]];
return $v ;
})
-> end ()
-> canBeEnabled ()
-> children ()
-> scalarNode ( 'service' )-> defaultNull ()-> end ()
-> arrayNode ( 'rules' )
-> performNoDeepMerging ()
-> prototype ( 'array' )
-> fixXmlConfig ( 'priority' , 'priorities' )
-> fixXmlConfig ( 'attribute' , 'attributes' )
-> children ()
-> scalarNode ( 'path' )-> defaultNull ()-> info ( 'URL path info' )-> end ()
-> scalarNode ( 'host' )-> defaultNull ()-> info ( 'URL host name' )-> end ()
-> variableNode ( 'methods' )-> defaultNull ()-> info ( 'Method for URL' )-> end ()
-> arrayNode ( 'attributes' )
-> useAttributeAsKey ( 'name' )
-> prototype ( 'variable' )-> end ()
-> end ()
-> booleanNode ( 'stop' )-> defaultFalse ()-> end ()
-> booleanNode ( 'prefer_extension' )-> defaultTrue ()-> end ()
-> scalarNode ( 'fallback_format' )-> defaultValue ( 'html' )-> end ()
-> arrayNode ( 'priorities' )
-> beforeNormalization ()-> ifString ()-> then (function ( $v ) {
return preg_split ( '/\s*,\s*/' , $v );
})-> end ()
-> prototype ( 'scalar' )-> end ()
-> end ()
-> end ()
-> end ()
-> end ()
-> end ()
-> end ()
-> end ();
}
private function addVersioningSection ( ArrayNodeDefinition $rootNode )
{
$rootNode
-> children ()
-> arrayNode ( 'versioning' )
-> canBeEnabled ()
-> children ()
-> scalarNode ( 'default_version' )-> defaultNull ()-> end ()
-> arrayNode ( 'resolvers' )
-> addDefaultsIfNotSet ()
-> children ()
-> arrayNode ( 'query' )
-> canBeDisabled ()
-> children ()
-> scalarNode ( 'parameter_name' )-> defaultValue ( 'version' )-> end ()
-> end ()
-> end ()
-> arrayNode ( 'custom_header' )
-> canBeDisabled ()
-> children ()
-> scalarNode ( 'header_name' )-> defaultValue ( 'X-Accept-Version' )-> end ()
-> end ()
-> end ()
-> arrayNode ( 'media_type' )
-> canBeDisabled ()
-> children ()
-> scalarNode ( 'regex' )-> defaultValue ( '/(v|version)=(?P<version>[0-9\.]+)/' )-> end ()
-> end ()
-> end ()
-> end ()
-> end ()
-> arrayNode ( 'guessing_order' )
-> defaultValue ([ 'query' , 'custom_header' , 'media_type' ])
-> validate ()
-> ifTrue (function ( $v ) {
foreach ( $v as $resolver ) {
if (! in_array ( $resolver , [ 'query' , 'custom_header' , 'media_type' ])) {
return true ;
}
}
})
-> thenInvalid ( 'Versioning guessing order can only contain "query", "custom_header", "media_type".' )
-> end ()
-> prototype ( 'scalar' )-> end ()
-> end ()
-> end ()
-> end ()
-> end ();
}
private function addExceptionSection ( ArrayNodeDefinition $rootNode )
{
$rootNode
-> children ()
-> arrayNode ( 'exception' )
-> fixXmlConfig ( 'code' , 'codes' )
-> fixXmlConfig ( 'message' , 'messages' )
-> addDefaultsIfNotSet ()
-> canBeEnabled ()
-> validate ()
-> always ()
-> then (function ( $v ) {
if (! $v [ 'enabled' ]) {
return $v ;
}
if ( $v [ 'exception_listener' ]) {
@ trigger_error ( 'Enabling the "fos_rest.exception.exception_listener" option is deprecated since FOSRestBundle 2.8.' , E_USER_DEPRECATED );
}
if ( $v [ 'serialize_exceptions' ]) {
@ trigger_error ( 'Enabling the "fos_rest.exception.serialize_exceptions" option is deprecated since FOSRestBundle 2.8.' , E_USER_DEPRECATED );
}
return $v ;
})
-> end ()
-> children ()
-> booleanNode ( 'map_exception_codes' )
-> defaultFalse ()
-> info ( 'Enables an event listener that maps exception codes to response status codes based on the map configured with the "fos_rest.exception.codes" option.' )
-> end ()
-> booleanNode ( 'exception_listener' )
-> defaultTrue ()
-> end ()
-> scalarNode ( 'exception_controller' )
-> defaultNull ()
-> setDeprecated ( 'The "%path%.%node%" option is deprecated since FOSRestBundle 2.8.' )
-> end ()
-> booleanNode ( 'serialize_exceptions' )
-> defaultTrue ()
-> end ()
-> enumNode ( 'flatten_exception_format' )
-> defaultValue ( 'legacy' )
-> values ([ 'legacy' , 'rfc7807' ])
-> end ()
-> scalarNode ( 'service' )
-> defaultNull ()
-> setDeprecated ( 'The "%path%.%node%" option is deprecated since FOSRestBundle 2.8.' )
-> end ()
-> booleanNode ( 'serializer_error_renderer' )-> defaultValue ( false )-> end ()
-> arrayNode ( 'codes' )
-> useAttributeAsKey ( 'name' )
-> beforeNormalization ()
-> ifArray ()
-> then (function (array $items ) {
foreach ( $items as & $item ) {
if ( is_int ( $item )) {
continue;
}
if (! defined ( sprintf ( '%s::%s' , Response ::class, $item ))) {
throw new InvalidConfigurationException ( sprintf ( 'Invalid HTTP code in fos_rest.exception.codes, see %s for all valid codes.' , Response ::class));
}
$item = constant ( sprintf ( '%s::%s' , Response ::class, $item ));
}
return $items ;
})
-> end ()
-> prototype ( 'integer' )-> end ()
-> validate ()
-> ifArray ()
-> then (function (array $items ) {
foreach ( $items as $class => $code ) {
$this -> testExceptionExists ( $class );
}
return $items ;
})
-> end ()
-> end ()
-> arrayNode ( 'messages' )
-> useAttributeAsKey ( 'name' )
-> prototype ( 'boolean' )-> end ()
-> validate ()
-> ifArray ()
-> then (function (array $items ) {
foreach ( $items as $class => $nomatter ) {
$this -> testExceptionExists ( $class );
}
return $items ;
})
-> end ()
-> end ()
-> booleanNode ( 'debug' )
-> defaultValue ( $this -> debug )
-> end ()
-> end ()
-> end ()
-> end ();
}
private function testExceptionExists ( string $throwable )
{
if (! is_a ( $throwable , \ Throwable ::class, true )) {
throw new InvalidConfigurationException ( sprintf ( 'FOSRestBundle exception mapper: Could not load class "%s" or the class does not extend from "%s". Most probably this is a configuration problem.' , $throwable , \ Throwable ::class));
}
}
}