.gitignore 0000666 00000000075 13052362416 0006544 0 ustar 00 phpunit.xml
vendor
composer.lock
composer.phar
.php_cs.cache
.php_cs 0000666 00000001115 13052362416 0006025 0 ustar 00 in(__DIR__)
->exclude('features/fixtures/TestApp/cache')
;
$header = <<
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
EOF;
HeaderCommentFixer::setHeader($header);
return Symfony\CS\Config\Config::create()
->setUsingCache(true)
->fixers(['-psr0'])
->finder($finder)
;
.styleci.yml 0000666 00000000045 13052362416 0007026 0 ustar 00 preset: symfony
disabled:
- braces
.travis.yml 0000666 00000002013 13052362416 0006657 0 ustar 00 language: php
php:
- 5.5
- 5.6
- 7.0
- hhvm
sudo: false
cache:
directories:
- $HOME/.composer/cache/files
branches:
only:
- master
- /^\d+\.\d+$/
matrix:
fast_finish: true
include:
- php: 5.5
env: COMPOSER_FLAGS="--prefer-lowest" SYMFONY_DEPRECATIONS_HELPER=weak
- php: 7.0
env: SYMFONY_VERSION='2.8.*'
- php: 7.0
env: SYMFONY_VERSION='3.1.*'
before_install:
- if [[ "$TRAVIS_PHP_VERSION" != "5.6" && "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then phpenv config-rm xdebug.ini; fi
- composer self-update
- if [ "$SYMFONY_VERSION" != "" ]; then composer require --dev --no-update symfony/symfony=$SYMFONY_VERSION; fi
install: composer update $COMPOSER_FLAGS --prefer-dist
script: if [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then phpunit --coverage-clover=coverage.clover; else phpunit; fi
after_script:
- if [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi
CHANGELOG.md 0000666 00000010447 13052362416 0006371 0 ustar 00 CHANGELOG
=========
1.8.0
-----
* added a new `InvalidParameterException` as a specialization of the `BadRequestHttpException`
* deprecated the `FOS\RestBundle\Util\ViolationFormatter` class and the
`FOS\RestBundle\Util\ViolationFormatterInterface`
* deprecated the `ViolationFormatterInterface` argument of the `ParamFetcher` class constructor
* deprecated the `RedirectView` and `RouteRedirectView` classes, use `View::createRedirect()` and
`View::createRouteRedirect()` instead
* added a `fos_rest.exception.debug` config option that defaults to the `kernel.debug` container
parameter and can be turned on to include the caught exception message in the exception controller's
response
* introduced the concept of REST zones which makes it possible to disable all REST listeners
when a request matches certain attributes
* fixed that serialization groups are always passed to the constructor as an array
* added annotations to support additional HTTP methods defined by RFC 2518 (WebDAV)
* added a new loader that allows to extract REST routes from all controller classes from a
directory
* introduced a serializer adapter layer to ease the integration of custom serialization
implementations
* deprecated the getter methods of the `ViewHandler` class
* fixed an issue that prevented decoration of the `TemplateReferenceInterface` from the Symfony
Templating component
* fixed: no longer overwrite an explicitly configured template in the view response listener
* added support for API versioning in URL parameters, the `Accept` header or using a custom header
* marked some classes and methods as internal, do no longer use them in your code as they are likely
to be removed in future releases
* deprecated the `DoctrineInflector` class and the `InflectorInterface` from the
`FOS\RestBundle\Util\Inflector`in favor of their replacements in the `FOS\RestBundle\Inflector`
namespace
* deprecated the `FormatNegotiator` class and the `FormatNegotiatorInterface` from the
`FOS\RestBundle\Util` namespace in favor of the new `FOS\RestBundle\Negotiation\FormatNegotiator`
class
* deprecated the `FOS\RestBundle\Util\MediaTypeNegotiatorInterface` which should no longer be used
1.7.9
-----
* handle `\Throwable` instances in the `ExceptionController`
* fixed that the default exclusion strategy groups for the serializer are not the empty string
* fixed a BC break that prevented the `CamelKeysNormalizer` from removing leading underscores
* fixed the `AllowedMethodsRouteLoader` to work with Symfony 3.0
1.7.8
-----
* removed uses of the reflection API in favor of faster solutions when possible
* fixed the configuration to use serialization groups and versions at the same time
1.7.7
-----
* when using Symfony 3.x, the bundle doesn't call methods anymore that have been deprecated in
Symfony 2.x and were removed in Symfony 3.0
* the `ViewResponseListener` does not overwrite explicitly configured templates anymore
* fixed the `ParamFetcher` class to properly handle sub requests
1.7.6
-----
* added a `CamelKeysNormalizerWithLeadingUnderscore` that keeps leading underscores when
converting snake case to camel case (for example, leaving `_username` unchanged)
1.7.5
-----
**CAUTION:** Accidentally, this patch release was never published.
1.7.4
-----
* removed some code from the `ViewResponseListener` class that was already present in the parent
`TemplateListener` class
1.7.3
-----
* made it possible to use the bundle with Symfony 3.x and fixed some compatibility issues with
Symfony 3.0
* fixed the exception controller to return a 406 (Not Acceptable) response when the format
negotiator throws an exception
1.7.2
-----
* fixed loading XML schema definition files in case the paths contain special characters (like
spaces)
* return the FQCN in the form type extension's `getExtendedType()` method to be compatible with
Symfony >= 2.8
* added the `extended-type` attribute to the `form.type_extension` tag to be compatible with
Symfony >= 2.8
* fixed some code examples in the documentation
* fixed exception message when using non-numeric identifiers (like UUID or GUID)
* allow version 1.x of `jms/serializer` and `jms/serializer-bundle`
* allow to use the Symfony serializer even if the JMS serializer is present
1.7.1
-----
* fix regression when handling methods in `@Route` annotations
Context/Context.php 0000666 00000007367 13052362416 0010350 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Context;
/**
* Stores the serialization or deserialization context (groups, version, ...).
*
* @author Ener-Getick
*/
final class Context
{
/**
* @var array
*/
private $attributes = array();
/**
* @var int|null
*/
private $version;
/**
* @var array
*/
private $groups = array();
/**
* @var int
*/
private $maxDepth;
/**
* @var bool
*/
private $serializeNull;
/**
* Sets an attribute.
*
* @param string $key
* @param mixed $value
*
* @return self
*/
public function setAttribute($key, $value)
{
$this->attributes[$key] = $value;
return $this;
}
/**
* Checks if contains a normalization attribute.
*
* @param string $key
*
* @return bool
*/
public function hasAttribute($key)
{
return isset($this->attributes[$key]);
}
/**
* Gets an attribute.
*
* @param string $key
*
* @return mixed
*/
public function getAttribute($key)
{
if (isset($this->attributes[$key])) {
return $this->attributes[$key];
}
}
/**
* Gets the attributes.
*
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* Sets the normalization version.
*
* @param int|null $version
*
* @return self
*/
public function setVersion($version)
{
$this->version = $version;
return $this;
}
/**
* Gets the normalization version.
*
* @return int|null
*/
public function getVersion()
{
return $this->version;
}
/**
* Adds a normalization group.
*
* @param string $group
*
* @return self
*/
public function addGroup($group)
{
if (!in_array($group, $this->groups)) {
$this->groups[] = $group;
}
return $this;
}
/**
* Adds normalization groups.
*
* @param string[] $groups
*
* @return self
*/
public function addGroups(array $groups)
{
foreach ($groups as $group) {
$this->addGroup($group);
}
return $this;
}
/**
* Gets the normalization groups.
*
* @return string[]
*/
public function getGroups()
{
return $this->groups;
}
/**
* Set the normalization groups.
*
* @param string[] $groups
*
* @return self
*/
public function setGroups(array $groups)
{
$this->groups = $groups;
return $this;
}
/**
* Sets the normalization max depth.
*
* @param int|null $maxDepth
*
* @return self
*/
public function setMaxDepth($maxDepth)
{
$this->maxDepth = $maxDepth;
return $this;
}
/**
* Gets the normalization max depth.
*
* @return int|null
*/
public function getMaxDepth()
{
return $this->maxDepth;
}
/**
* Sets serialize null.
*
* @param bool|null $serializeNull
*
* @return self
*/
public function setSerializeNull($serializeNull)
{
$this->serializeNull = $serializeNull;
return $this;
}
/**
* Gets serialize null.
*
* @return bool|null
*/
public function getSerializeNull()
{
return $this->serializeNull;
}
}
Controller/Annotations/AbstractParam.php 0000666 00000003402 13052362416 0014426 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
use Symfony\Component\Validator\Constraints;
/**
* {@inheritdoc}
*
* @author Jordi Boggiano
* @author Boris Guéry
* @author Ener-Getick
*/
abstract class AbstractParam implements ParamInterface
{
/** @var string */
public $name;
/** @var string */
public $key;
/** @var mixed */
public $default;
/** @var string */
public $description;
/** @var bool */
public $strict = false;
/** @var bool */
public $nullable = false;
/** @var array */
public $incompatibles = array();
/** {@inheritdoc} */
public function getName()
{
return $this->name;
}
/** {@inheritdoc} */
public function getDefault()
{
return $this->default;
}
/** {@inheritdoc} */
public function getDescription()
{
return $this->description;
}
/** {@inheritdoc} */
public function getIncompatibilities()
{
return $this->incompatibles;
}
/** {@inheritdoc} */
public function getConstraints()
{
$constraints = array();
if (!$this->nullable) {
$constraints[] = new Constraints\NotNull();
}
return $constraints;
}
/** {@inheritdoc} */
public function isStrict()
{
return $this->strict;
}
/**
* @return string
*/
protected function getKey()
{
return $this->key ?: $this->name;
}
}
Controller/Annotations/AbstractScalarParam.php 0000666 00000004145 13052362416 0015561 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
use FOS\RestBundle\Validator\Constraints\Regex;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\All;
/**
* {@inheritdoc}
*
* @author Ener-Getick
*/
abstract class AbstractScalarParam extends AbstractParam
{
/** @var mixed */
public $requirements = null;
/** @var bool */
public $map = false;
/** @var bool */
public $allowBlank = true;
/** {@inheritdoc} */
public function getConstraints()
{
$constraints = parent::getConstraints();
if ($this->requirements instanceof Constraint) {
$constraints[] = $this->requirements;
} elseif (is_scalar($this->requirements)) {
$constraints[] = new Regex(array(
'pattern' => '#^(?:'.$this->requirements.')$#xsu',
'message' => sprintf(
'Parameter \'%s\' value, does not match requirements \'%s\'',
$this->getName(),
$this->requirements
),
));
} elseif (is_array($this->requirements) && isset($this->requirements['rule']) && $this->requirements['error_message']) {
$constraints[] = new Regex(array(
'pattern' => '#^(?:'.$this->requirements['rule'].')$#xsu',
'message' => $this->requirements['error_message'],
));
}
if (false === $this->allowBlank) {
$constraints[] = new NotBlank();
}
// If the user wants to map the value, apply all constraints to every
// value of the map
if ($this->map) {
$constraints = array(
new All(array('constraints' => $constraints)),
);
}
return $constraints;
}
}
Controller/Annotations/Copy.php 0000666 00000001045 13052362416 0012615 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* COPY Route annotation class.
*
* @Annotation
* @Target("METHOD")
*
* @author Maximilian Bosch
*/
class Copy extends Route
{
public function getMethod()
{
return 'COPY';
}
}
Controller/Annotations/Delete.php 0000666 00000000754 13052362416 0013113 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* DELETE Route annotation class.
*
* @Annotation
* @Target("METHOD")
*/
class Delete extends Route
{
public function getMethod()
{
return 'DELETE';
}
}
Controller/Annotations/FileParam.php 0000666 00000002732 13052362416 0013547 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints;
/**
* Represents a file that must be present.
*
* @Annotation
* @Target("METHOD")
*
* @author Ener-Getick
*/
class FileParam extends AbstractParam
{
/** @var bool */
public $strict = true;
/** @var mixed */
public $requirements = null;
/** @var bool */
public $image = false;
/**
* {@inheritdoc}
*/
public function getConstraints()
{
$constraints = parent::getConstraints();
if ($this->requirements instanceof Constraint) {
$constraints[] = $this->requirements;
}
$options = is_array($this->requirements) ? $this->requirements : array();
if ($this->image) {
$constraints[] = new Constraints\Image($options);
} else {
$constraints[] = new Constraints\File($options);
}
return $constraints;
}
/**
* {@inheritdoc}
*/
public function getValue(Request $request, $default = null)
{
return $request->files->get($this->getKey(), $default);
}
}
Controller/Annotations/Get.php 0000666 00000000743 13052362416 0012426 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* GET Route annotation class.
*
* @Annotation
* @Target("METHOD")
*/
class Get extends Route
{
public function getMethod()
{
return 'GET';
}
}
Controller/Annotations/Head.php 0000666 00000000746 13052362416 0012553 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* HEAD Route annotation class.
*
* @Annotation
* @Target("METHOD")
*/
class Head extends Route
{
public function getMethod()
{
return 'HEAD';
}
}
Controller/Annotations/Link.php 0000666 00000000746 13052362416 0012607 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* LINK Route annotation class.
*
* @Annotation
* @Target("METHOD")
*/
class Link extends Route
{
public function getMethod()
{
return 'LINK';
}
}
Controller/Annotations/Lock.php 0000666 00000001045 13052362416 0012573 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* LOCK Route annotation class.
*
* @Annotation
* @Target("METHOD")
*
* @author Maximilian Bosch
*/
class Lock extends Route
{
public function getMethod()
{
return 'LOCK';
}
}
Controller/Annotations/Mkcol.php 0000666 00000001050 13052362416 0012744 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* MKCOL Route annotation class.
*
* @Annotation
* @Target("METHOD")
*
* @author Maximilian Bosch
*/
class Mkcol extends Route
{
public function getMethod()
{
return 'MKCOL';
}
}
Controller/Annotations/Move.php 0000666 00000001045 13052362416 0012611 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* MOVE Route annotation class.
*
* @Annotation
* @Target("METHOD")
*
* @author Maximilian Bosch
*/
class Move extends Route
{
public function getMethod()
{
return 'MOVE';
}
}
Controller/Annotations/NamePrefix.php 0000666 00000000740 13052362416 0013742 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
use Doctrine\Common\Annotations\Annotation;
/**
* NamePrefix Route annotation class.
*
* @Annotation
* @Target("CLASS")
*/
class NamePrefix extends Annotation
{
}
Controller/Annotations/NoRoute.php 0000666 00000000656 13052362416 0013305 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* No Route annotation class.
*
* @Annotation
* @Target({"METHOD","CLASS"})
*/
class NoRoute extends Route
{
}
Controller/Annotations/Options.php 0000666 00000000757 13052362416 0013347 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* OPTIONS Route annotation class.
*
* @Annotation
* @Target("METHOD")
*/
class Options extends Route
{
public function getMethod()
{
return 'OPTIONS';
}
}
Controller/Annotations/ParamInterface.php 0000666 00000002451 13052362416 0014566 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Constraint;
/**
* Represents a parameter that can be present in the request attributes.
*
* @author Ener-Getick
*/
interface ParamInterface
{
/**
* Get param name.
*
* @return string
*/
public function getName();
/**
* @return mixed
*/
public function getDefault();
/**
* @return string
*/
public function getDescription();
/**
* Get incompatibles parameters.
*
* @return array
*/
public function getIncompatibilities();
/**
* @return Constraint[]
*/
public function getConstraints();
/**
* @return bool
*/
public function isStrict();
/**
* Get param value in function of the current request.
*
* @param Request $request
* @param mixed $default value
*
* @return mixed
*/
public function getValue(Request $request, $default);
}
Controller/Annotations/Patch.php 0000666 00000000751 13052362416 0012745 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* PATCH Route annotation class.
*
* @Annotation
* @Target("METHOD")
*/
class Patch extends Route
{
public function getMethod()
{
return 'PATCH';
}
}
Controller/Annotations/Post.php 0000666 00000000746 13052362416 0012637 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* POST Route annotation class.
*
* @Annotation
* @Target("METHOD")
*/
class Post extends Route
{
public function getMethod()
{
return 'POST';
}
}
Controller/Annotations/Prefix.php 0000666 00000000730 13052362416 0013140 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
use Doctrine\Common\Annotations\Annotation;
/**
* Prefix Route annotation class.
*
* @Annotation
* @Target("CLASS")
*/
class Prefix extends Annotation
{
}
Controller/Annotations/PropFind.php 0000666 00000001061 13052362416 0013422 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* PROPFIND Route annotation class.
*
* @Annotation
* @Target("METHOD")
*
* @author Maximilian Bosch
*/
class PropFind extends Route
{
public function getMethod()
{
return 'PROPFIND';
}
}
Controller/Annotations/PropPatch.php 0000666 00000001064 13052362416 0013604 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* PROPPATCH Route annotation class.
*
* @Annotation
* @Target("METHOD")
*
* @author Maximilian Bosch
*/
class PropPatch extends Route
{
public function getMethod()
{
return 'PROPPATCH';
}
}
Controller/Annotations/Put.php 0000666 00000000743 13052362416 0012457 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* PUT Route annotation class.
*
* @Annotation
* @Target("METHOD")
*/
class Put extends Route
{
public function getMethod()
{
return 'PUT';
}
}
Controller/Annotations/QueryParam.php 0000666 00000001354 13052362416 0013774 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
use Symfony\Component\HttpFoundation\Request;
/**
* Represents a parameter that must be present in GET data.
*
* @Annotation
* @Target({"CLASS", "METHOD"})
*
* @author Alexander
*/
class QueryParam extends AbstractScalarParam
{
/**
* {@inheritdoc}
*/
public function getValue(Request $request, $default = null)
{
return $request->query->get($this->getKey(), $default);
}
}
Controller/Annotations/RequestParam.php 0000666 00000001512 13052362416 0014313 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
use Symfony\Component\HttpFoundation\Request;
/**
* Represents a parameter that must be present in POST data.
*
* @Annotation
* @Target("METHOD")
*
* @author Jordi Boggiano
* @author Boris Guéry
*/
class RequestParam extends AbstractScalarParam
{
/** @var bool */
public $strict = true;
/**
* {@inheritdoc}
*/
public function getValue(Request $request, $default = null)
{
return $request->request->get($this->getKey(), $default);
}
}
Controller/Annotations/Route.php 0000666 00000001407 13052362416 0013003 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route as BaseRoute;
/**
* Route annotation class.
*
* @Annotation
*/
class Route extends BaseRoute
{
public function __construct(array $data)
{
parent::__construct($data);
if (!$this->getMethods()) {
$this->setMethods((array) $this->getMethod());
}
}
/**
* @return string|null
*/
public function getMethod()
{
return;
}
}
Controller/Annotations/RouteResource.php 0000666 00000001041 13052362416 0014505 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* RouteResource annotation class.
*
* @Annotation
* @Target("CLASS")
*/
class RouteResource
{
/**
* @var string required
*/
public $resource;
/**
* @var bool
*/
public $pluralize = true;
}
Controller/Annotations/Unlink.php 0000666 00000000754 13052362416 0013151 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* UNLINK Route annotation class.
*
* @Annotation
* @Target("METHOD")
*/
class Unlink extends Route
{
public function getMethod()
{
return 'UNLINK';
}
}
Controller/Annotations/Unlock.php 0000666 00000001053 13052362416 0013135 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
/**
* UNLOCK Route annotation class.
*
* @Annotation
* @Target("METHOD")
*
* @author Maximilian Bosch
*/
class Unlock extends Route
{
public function getMethod()
{
return 'UNLOCK';
}
}
Controller/Annotations/Version.php 0000666 00000000732 13052362416 0013332 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
use Doctrine\Common\Annotations\Annotation;
/**
* Version Route annotation class.
*
* @Annotation
* @Target("CLASS")
*/
class Version extends Annotation
{
}
Controller/Annotations/View.php 0000666 00000005037 13052362416 0012622 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller\Annotations;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/**
* View annotation class.
*
* @Annotation
* @Target({"METHOD","CLASS"})
*/
class View extends Template
{
/**
* @var string
*/
protected $templateVar;
/**
* @var int
*/
protected $statusCode;
/**
* @var array
*/
protected $serializerGroups;
/**
* @var bool
*/
protected $populateDefaultVars = true;
/**
* @var bool
*/
protected $serializerEnableMaxDepthChecks;
/**
* Sets the template var name to be used for templating formats.
*
* @param string $templateVar
*/
public function setTemplateVar($templateVar)
{
$this->templateVar = $templateVar;
}
/**
* Returns the template var name to be used for templating formats.
*
* @return string
*/
public function getTemplateVar()
{
return $this->templateVar;
}
/**
* @param int $statusCode
*/
public function setStatusCode($statusCode)
{
$this->statusCode = $statusCode;
}
/**
* @return int
*/
public function getStatusCode()
{
return $this->statusCode;
}
/**
* @param array $serializerGroups
*/
public function setSerializerGroups($serializerGroups)
{
$this->serializerGroups = $serializerGroups;
}
/**
* @return array
*/
public function getSerializerGroups()
{
return $this->serializerGroups;
}
/**
* @param bool $populateDefaultVars
*/
public function setPopulateDefaultVars($populateDefaultVars)
{
$this->populateDefaultVars = (bool) $populateDefaultVars;
}
/**
* @return bool
*/
public function isPopulateDefaultVars()
{
return $this->populateDefaultVars;
}
/**
* @param bool $serializerEnableMaxDepthChecks
*/
public function setSerializerEnableMaxDepthChecks($serializerEnableMaxDepthChecks)
{
$this->serializerEnableMaxDepthChecks = $serializerEnableMaxDepthChecks;
}
/**
* @return bool
*/
public function getSerializerEnableMaxDepthChecks()
{
return $this->serializerEnableMaxDepthChecks;
}
}
Controller/ControllerTrait.php 0000666 00000005725 13052362416 0012546 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* Trait for Controllers using the View functionality of FOSRestBundle.
*
* @author Benjamin Eberlei
* @author Lukas Kahwe Smith
*/
trait ControllerTrait
{
/**
* @var ViewHandlerInterface
*/
private $viewhandler;
/**
* Get the ViewHandler.
*
* @param ViewHandlerInterface $viewhandler
*/
public function setViewHandler(ViewHandlerInterface $viewhandler)
{
$this->viewhandler = $viewhandler;
}
/**
* Get the ViewHandler.
*
* @return ViewHandlerInterface
*/
protected function getViewHandler()
{
if (!$this->viewhandler instanceof ViewHandlerInterface) {
throw new \RuntimeException('A "ViewHandlerInterface" instance must be set when using the FOSRestBundle "ControllerTrait".');
}
return $this->viewhandler;
}
/**
* Creates a view.
*
* Convenience method to allow for a fluent interface.
*
* @param mixed $data
* @param int $statusCode
* @param array $headers
*
* @return View
*/
protected function view($data = null, $statusCode = null, array $headers = [])
{
return View::create($data, $statusCode, $headers);
}
/**
* Creates a Redirect view.
*
* Convenience method to allow for a fluent interface.
*
* @param string $url
* @param int $statusCode
* @param array $headers
*
* @return View
*/
protected function redirectView($url, $statusCode = Response::HTTP_FOUND, array $headers = [])
{
return View::createRedirect($url, $statusCode, $headers);
}
/**
* Creates a Route Redirect View.
*
* Convenience method to allow for a fluent interface.
*
* @param string $route
* @param mixed $parameters
* @param int $statusCode
* @param array $headers
*
* @return View
*/
protected function routeRedirectView($route, array $parameters = [], $statusCode = Response::HTTP_CREATED, array $headers = [])
{
return View::createRouteRedirect($route, $parameters, $statusCode, $headers);
}
/**
* Converts view into a response object.
*
* Not necessary to use, if you are using the "ViewResponseListener", which
* does this conversion automatically in kernel event "onKernelView".
*
* @param View $view
*
* @return Response
*/
protected function handleView(View $view)
{
return $this->getViewHandler()->handle($view);
}
}
Controller/ExceptionController.php 0000666 00000011032 13052362416 0013405 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller;
use FOS\RestBundle\Util\ExceptionValueMap;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
/**
* Custom ExceptionController that uses the view layer and supports HTTP response status code mapping.
*/
class ExceptionController
{
/**
* @var ViewHandlerInterface
*/
private $viewHandler;
/**
* @var ExceptionValueMap
*/
private $exceptionCodes;
/**
* @var bool
*/
private $showException;
public function __construct(
ViewHandlerInterface $viewHandler,
ExceptionValueMap $exceptionCodes,
$showException
) {
$this->viewHandler = $viewHandler;
$this->exceptionCodes = $exceptionCodes;
$this->showException = $showException;
}
/**
* Converts an Exception to a Response.
*
* @param Request $request
* @param \Exception|\Throwable $exception
* @param DebugLoggerInterface|null $logger
*
* @throws \InvalidArgumentException
*
* @return Response
*/
public function showAction(Request $request, $exception, DebugLoggerInterface $logger = null)
{
$currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1));
$code = $this->getStatusCode($exception);
$templateData = $this->getTemplateData($currentContent, $code, $exception, $logger);
$view = $this->createView($exception, $code, $templateData, $request, $this->showException);
$response = $this->viewHandler->handle($view);
return $response;
}
/**
* @param \Exception $exception
* @param int $code
* @param array $templateData
* @param Request $request
* @param bool $showException
*
* @return View
*/
protected function createView(\Exception $exception, $code, array $templateData, Request $request, $showException)
{
$view = new View($exception, $code, $exception instanceof HttpExceptionInterface ? $exception->getHeaders() : []);
$view->setTemplateVar('raw_exception');
$view->setTemplateData($templateData);
return $view;
}
/**
* Determines the status code to use for the response.
*
* @param \Exception $exception
*
* @return int
*/
protected function getStatusCode(\Exception $exception)
{
// If matched
if ($statusCode = $this->exceptionCodes->resolveException($exception)) {
return $statusCode;
}
// Otherwise, default
if ($exception instanceof HttpExceptionInterface) {
return $exception->getStatusCode();
}
return 500;
}
/**
* Determines the template parameters to pass to the view layer.
*
* @param string $currentContent
* @param int $code
* @param \Exception $exception
* @param DebugLoggerInterface $logger
*
* @return array
*/
private function getTemplateData($currentContent, $code, \Exception $exception, DebugLoggerInterface $logger = null)
{
return [
'exception' => FlattenException::create($exception),
'status' => 'error',
'status_code' => $code,
'status_text' => array_key_exists($code, Response::$statusTexts) ? Response::$statusTexts[$code] : 'error',
'currentContent' => $currentContent,
'logger' => $logger,
];
}
/**
* Gets and cleans any content that was already outputted.
*
* This code comes from Symfony and should be synchronized on a regular basis
* see src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php
*
* @return string
*/
private function getAndCleanOutputBuffering($startObLevel)
{
if (ob_get_level() <= $startObLevel) {
return '';
}
Response::closeOutputBuffers($startObLevel + 1, true);
return ob_get_clean();
}
}
Controller/FOSRestController.php 0000666 00000001660 13052362416 0012742 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller;
use FOS\RestBundle\View\ViewHandlerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
/**
* Controllers using the View functionality of FOSRestBundle.
*
* @author Lukas Kahwe Smith
*/
abstract class FOSRestController extends Controller
{
use ControllerTrait;
/**
* Get the ViewHandler.
*
* @return ViewHandlerInterface
*/
protected function getViewHandler()
{
if (!$this->viewhandler instanceof ViewHandlerInterface) {
$this->viewhandler = $this->container->get('fos_rest.view_handler');
}
return $this->viewhandler;
}
}
Controller/TemplatingExceptionController.php 0000666 00000002433 13052362416 0015437 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller;
use FOS\RestBundle\Util\ExceptionValueMap;
use FOS\RestBundle\View\ViewHandlerInterface;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Templating\TemplateReferenceInterface;
abstract class TemplatingExceptionController extends ExceptionController
{
protected $templating;
public function __construct(
ViewHandlerInterface $viewHandler,
ExceptionValueMap $exceptionCodes,
$showException,
EngineInterface $templating
) {
parent::__construct($viewHandler, $exceptionCodes, $showException);
$this->templating = $templating;
}
/**
* Finds the template for the given format and status code.
*
* @param Request $request
* @param int $statusCode
* @param bool $showException
*
* @return TemplateReferenceInterface
*/
abstract protected function findTemplate(Request $request, $statusCode, $showException);
}
Controller/TwigExceptionController.php 0000666 00000004512 13052362416 0014245 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Symfony\Component\HttpFoundation\Request;
/**
* Custom ExceptionController that uses the view layer and supports HTTP response status code mapping.
* It additionally is able to prepare the template parameters for the core EngineInterface.
*/
class TwigExceptionController extends TemplatingExceptionController
{
/**
* {@inheritdoc}
*/
protected function createView(\Exception $exception, $code, array $templateData, Request $request, $showException)
{
$view = parent::createView($exception, $code, $templateData, $request, $showException);
$view->setTemplate($this->findTemplate($request, $code, $showException));
return $view;
}
/**
* {@inheritdoc}
*
* This code is inspired by TwigBundle and should be synchronized on a regular basis
* see src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php
*/
protected function findTemplate(Request $request, $statusCode, $showException)
{
$format = $request->getRequestFormat();
$name = $showException ? 'exception' : 'error';
if ($showException && 'html' === $format) {
$name = 'exception_full';
}
// when not in debug, try to find a template for the specific HTTP status code and format
if (!$showException) {
$template = new TemplateReference('TwigBundle', 'Exception', $name.$statusCode, $format, 'twig');
if ($this->templating->exists($template)) {
return $template;
}
}
// try to find a template for the given format
$template = new TemplateReference('TwigBundle', 'Exception', $name, $format, 'twig');
if ($this->templating->exists($template)) {
return $template;
}
// default to a generic HTML exception
$request->setRequestFormat('html');
return new TemplateReference('TwigBundle', 'Exception', $showException ? 'exception_full' : $name, 'html', 'twig');
}
}
Decoder/ContainerDecoderProvider.php 0000666 00000002720 13052362416 0013554 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Decoder;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides encoders through the Symfony DIC.
*
* @author Igor Wiedler
*/
class ContainerDecoderProvider implements DecoderProviderInterface
{
private $container;
private $decoders;
/**
* Constructor.
*
* @param ContainerInterface $container The container from which the actual decoders are retrieved
* @param array $decoders List of key (format) value (service ids) of decoders
*/
public function __construct(ContainerInterface $container, array $decoders)
{
$this->container = $container;
$this->decoders = $decoders;
}
/**
* {@inheritdoc}
*/
public function supports($format)
{
return isset($this->decoders[$format]);
}
/**
* {@inheritdoc}
*/
public function getDecoder($format)
{
if (!$this->supports($format)) {
throw new \InvalidArgumentException(
sprintf("Format '%s' is not supported by ContainerDecoderProvider.", $format)
);
}
return $this->container->get($this->decoders[$format]);
}
}
Decoder/DecoderInterface.php 0000666 00000001156 13052362416 0012021 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Decoder;
/**
* Defines the interface of decoders.
*
* @author Jordi Boggiano
*/
interface DecoderInterface
{
/**
* Decodes a string into PHP data.
*
* @param string $data
*
* @return mixed False in case the content could not be decoded
*/
public function decode($data);
}
Decoder/DecoderProviderInterface.php 0000666 00000001441 13052362416 0013531 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Decoder;
/**
* Defines the interface of decoder providers.
*
* @author Igor Wiedler
*/
interface DecoderProviderInterface
{
/**
* Checks if a certain format is supported.
*
* @param string $format
*
* @return bool
*/
public function supports($format);
/**
* Provides decoders, possibly lazily.
*
* @param string $format
*
* @return \FOS\RestBundle\Decoder\DecoderInterface
*/
public function getDecoder($format);
}
Decoder/JsonDecoder.php 0000666 00000001047 13052362416 0011031 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Decoder;
/**
* Decodes JSON data.
*
* @author Jordi Boggiano
*/
class JsonDecoder implements DecoderInterface
{
/**
* {@inheritdoc}
*/
public function decode($data)
{
return @json_decode($data, true);
}
}
Decoder/JsonToFormDecoder.php 0000666 00000003257 13052362416 0012165 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Decoder;
/**
* Decodes JSON data and make it compliant with application/x-www-form-encoded style.
*
* @author Kévin Dunglas
*/
class JsonToFormDecoder implements DecoderInterface
{
/**
* Makes data decoded from JSON application/x-www-form-encoded compliant.
*
* @param array $data
*/
private function xWwwFormEncodedLike(&$data)
{
foreach ($data as $key => &$value) {
if (is_array($value)) {
// Encode recursively
$this->xWwwFormEncodedLike($value);
} elseif (false === $value) {
// Checkbox-like behavior removes false data but PATCH HTTP method with just checkboxes does not work
// To fix this issue we prefer transform false data to null
// See https://github.com/FriendsOfSymfony/FOSRestBundle/pull/883
$value = null;
} elseif (!is_string($value)) {
// Convert everything to string
// true values will be converted to '1', this is the default checkbox behavior
$value = strval($value);
}
}
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
$decodedData = @json_decode($data, true);
if ($decodedData) {
$this->xWwwFormEncodedLike($decodedData);
}
return $decodedData;
}
}
Decoder/XmlDecoder.php 0000666 00000001737 13052362416 0010666 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Decoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* Decodes XML data.
*
* @author Jordi Boggiano
* @author John Wards
* @author Fabian Vogler
*/
class XmlDecoder implements DecoderInterface
{
private $encoder;
public function __construct()
{
$this->encoder = new XmlEncoder();
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
try {
return $this->encoder->decode($data, 'xml');
} catch (UnexpectedValueException $e) {
return;
}
}
}
DependencyInjection/Compiler/ConfigurationCheckPass.php 0000666 00000002736 13052362416 0017362 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Checks if the SensioFrameworkExtraBundle views annotations are disabled when using the View Response listener.
*
* @author Eriksen Costa
*
* @internal
*/
final class ConfigurationCheckPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if ($container->has('fos_rest.converter.request_body') && !$container->has('sensio_framework_extra.converter.listener')) {
throw new \RuntimeException('You need to enable the parameter converter listeners in SensioFrameworkExtraBundle when using the FOSRestBundle RequestBodyParamConverter');
}
if ($container->has('fos_rest.view_response_listener') && isset($container->getParameter('kernel.bundles')['SensioFrameworkExtraBundle'])) {
if (!$container->has('sensio_framework_extra.view.listener')) {
throw new \RuntimeException('You must enable the SensioFrameworkExtraBundle view annotations to use the ViewResponseListener.');
}
}
}
}
DependencyInjection/Compiler/FormatListenerRulesPass.php 0000666 00000005703 13052362416 0017563 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Eduardo Gulias Davis
*
* @internal
*/
final class FormatListenerRulesPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('fos_rest.format_listener')) {
return;
}
if ($container->hasParameter('web_profiler.debug_toolbar.mode')) {
$path = '_profiler';
if (2 === $container->getParameter('web_profiler.debug_toolbar.mode')) {
$path .= '|_wdt';
}
$profilerRule = [
'host' => null,
'methods' => null,
'path' => "^/$path/",
'priorities' => ['html', 'json'],
'fallback_format' => 'html',
'attributes' => array(),
'prefer_extension' => true,
];
$this->addRule($profilerRule, $container);
}
$rules = $container->getParameter('fos_rest.format_listener.rules');
foreach ($rules as $rule) {
$this->addRule($rule, $container);
}
$container->setParameter('fos_rest.format_listener.rules', null);
}
private function addRule(array $rule, ContainerBuilder $container)
{
$matcher = $this->createRequestMatcher(
$container,
$rule['path'],
$rule['host'],
$rule['methods'],
$rule['attributes']
);
unset($rule['path'], $rule['host']);
if (is_bool($rule['prefer_extension']) && $rule['prefer_extension']) {
$rule['prefer_extension'] = '2.0';
}
$container->getDefinition('fos_rest.format_negotiator')
->addMethodCall('add', [$matcher, $rule]);
}
private function createRequestMatcher(ContainerBuilder $container, $path = null, $host = null, $methods = null, array $attributes = array())
{
$arguments = [$path, $host, $methods, null, $attributes];
$serialized = serialize($arguments);
$id = 'fos_rest.request_matcher.'.md5($serialized).sha1($serialized);
if (!$container->hasDefinition($id)) {
// only add arguments that are necessary
$container
->setDefinition($id, new DefinitionDecorator('fos_rest.format_request_matcher'))
->setArguments($arguments);
}
return new Reference($id);
}
}
DependencyInjection/Compiler/JMSHandlersPass.php 0000666 00000003725 13052362416 0015726 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Checks if the JMS serializer is available to be able to use handlers.
*
* @author Christian Flothmann
*
* @internal
*/
final class JMSHandlersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if ($container->has('jms_serializer.handler_registry')) {
$this->registerHandlerRegistry($container);
return;
}
$container->removeDefinition('fos_rest.serializer.handler_registry');
$container->removeDefinition('fos_rest.serializer.exception_normalizer.jms');
$container->getParameterBag()->remove('jms_serializer.form_error_handler.class');
}
/**
* Inlined because {@link JMS\SerializerBundle\DependencyInjection\Compiler\CustomHandlersPass}
* must be executed before replacing jms_serializer.handler_registry.
*/
public function registerHandlerRegistry(ContainerBuilder $container)
{
$handlerRegistry = new Definition('FOS\RestBundle\Serializer\JMSHandlerRegistry', array(new Reference('fos_rest.serializer.jms_handler_registry')));
$oldRegistry = $container->getDefinition('jms_serializer.handler_registry');
$oldRegistry->setPublic(false);
$container->setDefinition('jms_serializer.handler_registry', $handlerRegistry);
$container->setDefinition('fos_rest.serializer.jms_handler_registry', $oldRegistry);
}
}
DependencyInjection/Compiler/SerializerConfigurationPass.php 0000666 00000006147 13052362416 0020456 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Checks if a serializer is either set or can be auto-configured.
*
* @author Christian Flothmann
* @author Florian Voutzinos
*
* @internal
*/
final class SerializerConfigurationPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if ($container->has('fos_rest.serializer')) {
$class = $container->getParameterBag()->resolveValue(
$container->findDefinition('fos_rest.serializer')->getClass()
);
if (!is_subclass_of($class, 'FOS\RestBundle\Serializer\Serializer')) {
throw new \InvalidArgumentException(sprintf('"fos_rest.serializer" must implement FOS\RestBundle\Serializer\Serializer (instance of "%s" given).', $class));
}
return;
}
if (!$container->has('serializer') && !$container->has('jms_serializer.serializer')) {
throw new \InvalidArgumentException('Neither a service called "jms_serializer.serializer" nor "serializer" is available and no serializer is explicitly configured. You must either enable the JMSSerializerBundle, enable the FrameworkBundle serializer or configure a custom serializer.');
}
if ($container->has('serializer')) {
$class = $container->getParameterBag()->resolveValue(
$container->findDefinition('serializer')->getClass()
);
if (is_subclass_of($class, 'Symfony\Component\Serializer\SerializerInterface')) {
$container->setAlias('fos_rest.serializer', 'fos_rest.serializer.symfony');
$container->removeDefinition('fos_rest.serializer.exception_wrapper_serialize_handler');
} elseif (is_subclass_of($class, 'JMS\Serializer\SerializerInterface')) {
$container->setAlias('fos_rest.serializer', 'fos_rest.serializer.jms');
} elseif (is_subclass_of($class, 'FOS\RestBundle\Serializer\Serializer')) {
$container->setAlias('fos_rest.serializer', 'serializer');
} else {
throw new \InvalidArgumentException(sprintf('"fos_rest.serializer" must implement FOS\RestBundle\Serializer\Serializer (instance of "%s" given).', $class));
}
return;
} else {
$container->removeDefinition('fos_rest.serializer.exception_wrapper_normalizer');
}
if ($container->has('jms_serializer.serializer')) {
$container->setAlias('fos_rest.serializer', 'fos_rest.serializer.jms');
} else {
$container->removeDefinition('fos_rest.serializer.exception_wrapper_serialize_handler');
}
}
}
DependencyInjection/Compiler/TwigExceptionPass.php 0000666 00000001507 13052362416 0016401 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Remove the 'fos_rest.exception.twig_controller' service if twig is enabled.
*
* @internal
*/
final class TwigExceptionPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->has('templating.engine.twig')) {
$container->removeDefinition('fos_rest.exception.twig_controller');
}
}
}
DependencyInjection/Configuration.php 0000666 00000044441 13052362416 0014022 0 ustar 00
*
* 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\HttpFoundation\Response;
/**
* 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
*
* @internal
*/
final class Configuration implements ConfigurationInterface
{
/**
* Default debug mode value.
*
* @var bool
*/
private $debug;
/**
* @param bool $debug
*/
public function __construct($debug)
{
$this->debug = (bool) $debug;
}
/**
* Generates the configuration tree.
*
* @return TreeBuilder
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('fos_rest', 'array');
$rootNode
->children()
->scalarNode('disable_csrf_role')->defaultNull()->end()
->arrayNode('access_denied_listener')
->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('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('enabled')->defaultFalse()->end()
->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()
->children()
->scalarNode('default_format')->defaultNull()->end()
->scalarNode('include_format')->defaultTrue()->end()
->end()
->end()
->arrayNode('body_converter')
->addDefaultsIfNotSet()
->children()
->scalarNode('enabled')->defaultFalse()->end()
->scalarNode('validate')->defaultFalse()->end()
->scalarNode('validation_errors_argument')->defaultValue('validationErrors')->end()
->end()
->end()
->arrayNode('service')
->addDefaultsIfNotSet()
->children()
->scalarNode('router')->defaultValue('router')->end()
->scalarNode('templating')->defaultValue('templating')->end()
->scalarNode('serializer')->defaultNull()->end()
->scalarNode('view_handler')->defaultValue('fos_rest.view_handler.default')->end()
->scalarNode('inflector')->defaultValue('fos_rest.inflector.doctrine')->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 array($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')->defaultValue('twig')->end()
->arrayNode('force_redirects')
->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('variable')->end()
->end()
->end()
->end()
->arrayNode('formats')
->useAttributeAsKey('name')
->defaultValue(['json' => true, 'xml' => true])
->prototype('boolean')->end()
->end()
->arrayNode('templating_formats')
->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('enabled')->defaultFalse()->end()
->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)
{
$rootNode
->children()
->arrayNode('body_listener')
->fixXmlConfig('decoder', 'decoders')
->addDefaultsIfNotSet()
->canBeUnset()
->canBeDisabled()
->children()
->scalarNode('service')->defaultNull()->end()
->scalarNode('default_format')->defaultNull()->end()
->booleanNode('throw_exception_on_unsupported_content_type')
->defaultFalse()
->end()
->arrayNode('decoders')
->useAttributeAsKey('name')
->defaultValue(['json' => 'fos_rest.decoder.json', 'xml' => 'fos_rest.decoder.xml'])
->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')
->cannotBeOverwritten()
->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[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()
->children()
->scalarNode('exception_controller')->defaultNull()->end()
->arrayNode('codes')
->useAttributeAsKey('name')
->validate()
->ifTrue(function ($v) { return 0 !== count(array_filter($v, function ($i) { return !defined('Symfony\Component\HttpFoundation\Response::'.$i) && !is_int($i); })); })
->thenInvalid('Invalid HTTP code in fos_rest.exception.codes, see Symfony\Component\HttpFoundation\Response for all valid codes.')
->end()
->prototype('scalar')->end()
->end()
->arrayNode('messages')
->useAttributeAsKey('name')
->prototype('boolean')->end()
->end()
->booleanNode('debug')
->defaultValue($this->debug)
->end()
->end()
->end()
->end();
}
}
DependencyInjection/FOSRestExtension.php 0000666 00000047046 13052362416 0014401 0 ustar 00
*
* 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\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\HttpFoundation\Response;
class FOSRestExtension extends Extension
{
/**
* {@inheritdoc}
*/
public function getConfiguration(array $config, ContainerBuilder $container)
{
return new Configuration($container->getParameter('kernel.debug'));
}
/**
* Loads the services based on your application configuration.
*
* @param array $configs
* @param ContainerBuilder $container
*
* @throws \InvalidArgumentException
* @throws \LogicException
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration($container->getParameter('kernel.debug'));
$config = $this->processConfiguration($configuration, $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('view.xml');
$loader->load('routing.xml');
$loader->load('request.xml');
$loader->load('serializer.xml');
$container->getDefinition('fos_rest.routing.loader.controller')->replaceArgument(4, $config['routing_loader']['default_format']);
$container->getDefinition('fos_rest.routing.loader.yaml_collection')->replaceArgument(4, $config['routing_loader']['default_format']);
$container->getDefinition('fos_rest.routing.loader.xml_collection')->replaceArgument(4, $config['routing_loader']['default_format']);
$container->getDefinition('fos_rest.routing.loader.yaml_collection')->replaceArgument(2, $config['routing_loader']['include_format']);
$container->getDefinition('fos_rest.routing.loader.xml_collection')->replaceArgument(2, $config['routing_loader']['include_format']);
$container->getDefinition('fos_rest.routing.loader.reader.action')->replaceArgument(3, $config['routing_loader']['include_format']);
// The validator service alias is only set if validation is enabled for the request body converter
$validator = $config['service']['validator'];
unset($config['service']['validator']);
foreach ($config['service'] as $key => $service) {
if (null !== $service) {
$container->setAlias('fos_rest.'.$key, $service);
}
}
$this->loadForm($config, $loader, $container);
$this->loadException($config, $loader, $container);
$this->loadBodyConverter($config, $validator, $loader, $container);
$this->loadView($config, $loader, $container);
$this->loadBodyListener($config, $loader, $container);
$this->loadFormatListener($config, $loader, $container);
$this->loadVersioning($config, $loader, $container);
$this->loadParamFetcherListener($config, $loader, $container);
$this->loadAllowedMethodsListener($config, $loader, $container);
$this->loadAccessDeniedListener($config, $loader, $container);
$this->loadZoneMatcherListener($config, $loader, $container);
// Needs RequestBodyParamConverter and View Handler loaded.
$this->loadSerializer($config, $container);
}
private function loadForm(array $config, XmlFileLoader $loader, ContainerBuilder $container)
{
if (!empty($config['disable_csrf_role'])) {
$loader->load('forms.xml');
$definition = $container->getDefinition('fos_rest.form.extension.csrf_disable');
$definition->replaceArgument(1, $config['disable_csrf_role']);
// BC for Symfony < 2.8: the extended_type attribute is used on higher versions
if (!method_exists(AbstractType::class, 'getBlockPrefix')) {
$definition->addTag('form.type_extension', ['alias' => 'form']);
} else {
$definition->addTag('form.type_extension', ['extended_type' => FormType::class]);
}
}
}
private function loadAccessDeniedListener(array $config, XmlFileLoader $loader, ContainerBuilder $container)
{
if ($config['access_denied_listener']['enabled'] && !empty($config['access_denied_listener']['formats'])) {
$loader->load('access_denied_listener.xml');
$service = $container->getDefinition('fos_rest.access_denied_listener');
if (!empty($config['access_denied_listener']['service'])) {
$service->clearTag('kernel.event_subscriber');
}
$service->replaceArgument(0, $config['access_denied_listener']['formats']);
$service->replaceArgument(1, $config['unauthorized_challenge']);
}
}
private function loadAllowedMethodsListener(array $config, XmlFileLoader $loader, ContainerBuilder $container)
{
if ($config['allowed_methods_listener']['enabled']) {
if (!empty($config['allowed_methods_listener']['service'])) {
$service = $container->getDefinition('fos_rest.allowed_methods_listener');
$service->clearTag('kernel.event_listener');
}
$loader->load('allowed_methods_listener.xml');
$container->getDefinition('fos_rest.allowed_methods_loader')->replaceArgument(1, $config['cache_dir']);
}
}
private function loadBodyListener(array $config, XmlFileLoader $loader, ContainerBuilder $container)
{
if ($config['body_listener']['enabled']) {
$loader->load('body_listener.xml');
$service = $container->getDefinition('fos_rest.body_listener');
if (!empty($config['body_listener']['service'])) {
$service->clearTag('kernel.event_listener');
}
$service->replaceArgument(1, $config['body_listener']['throw_exception_on_unsupported_content_type']);
$service->addMethodCall('setDefaultFormat', array($config['body_listener']['default_format']));
$container->getDefinition('fos_rest.decoder_provider')->replaceArgument(1, $config['body_listener']['decoders']);
$arrayNormalizer = $config['body_listener']['array_normalizer'];
if (null !== $arrayNormalizer['service']) {
$bodyListener = $container->getDefinition('fos_rest.body_listener');
$bodyListener->addArgument(new Reference($arrayNormalizer['service']));
$bodyListener->addArgument($arrayNormalizer['forms']);
}
}
}
private function loadFormatListener(array $config, XmlFileLoader $loader, ContainerBuilder $container)
{
if ($config['format_listener']['enabled'] && !empty($config['format_listener']['rules'])) {
$loader->load('format_listener.xml');
if (!empty($config['format_listener']['service'])) {
$service = $container->getDefinition('fos_rest.format_listener');
$service->clearTag('kernel.event_listener');
}
$container->setParameter(
'fos_rest.format_listener.rules',
$config['format_listener']['rules']
);
}
}
private function loadVersioning(array $config, XmlFileLoader $loader, ContainerBuilder $container)
{
if (!empty($config['versioning']['enabled'])) {
$loader->load('versioning.xml');
$versionListener = $container->getDefinition('fos_rest.versioning.listener');
$versionListener->replaceArgument(2, $config['versioning']['default_version']);
$resolvers = [];
if ($config['versioning']['resolvers']['query']['enabled']) {
$resolvers['query'] = $container->getDefinition('fos_rest.versioning.query_parameter_resolver');
$resolvers['query']->replaceArgument(0, $config['versioning']['resolvers']['query']['parameter_name']);
}
if ($config['versioning']['resolvers']['custom_header']['enabled']) {
$resolvers['custom_header'] = $container->getDefinition('fos_rest.versioning.header_resolver');
$resolvers['custom_header']->replaceArgument(0, $config['versioning']['resolvers']['custom_header']['header_name']);
}
if ($config['versioning']['resolvers']['media_type']['enabled']) {
$resolvers['media_type'] = $container->getDefinition('fos_rest.versioning.media_type_resolver');
$resolvers['media_type']->replaceArgument(0, $config['versioning']['resolvers']['media_type']['regex']);
}
$chainResolver = $container->getDefinition('fos_rest.versioning.chain_resolver');
foreach ($config['versioning']['guessing_order'] as $resolver) {
if (isset($resolvers[$resolver])) {
$chainResolver->addMethodCall('addResolver', [$resolvers[$resolver]]);
}
}
}
}
private function loadParamFetcherListener(array $config, XmlFileLoader $loader, ContainerBuilder $container)
{
if ($config['param_fetcher_listener']['enabled']) {
$loader->load('param_fetcher_listener.xml');
if (!empty($config['param_fetcher_listener']['service'])) {
$service = $container->getDefinition('fos_rest.param_fetcher_listener');
$service->clearTag('kernel.event_listener');
}
if ($config['param_fetcher_listener']['force']) {
$container->getDefinition('fos_rest.param_fetcher_listener')->replaceArgument(1, true);
}
}
}
private function loadBodyConverter(array $config, $validator, XmlFileLoader $loader, ContainerBuilder $container)
{
if (empty($config['body_converter'])) {
return;
}
if (!empty($config['body_converter']['enabled'])) {
$loader->load('request_body_param_converter.xml');
if (!empty($config['body_converter']['validation_errors_argument'])) {
$container->getDefinition('fos_rest.converter.request_body')->replaceArgument(4, $config['body_converter']['validation_errors_argument']);
}
}
if (!empty($config['body_converter']['validate'])) {
$container->setAlias('fos_rest.validator', $validator);
}
}
private function loadView(array $config, XmlFileLoader $loader, ContainerBuilder $container)
{
if (!empty($config['view']['jsonp_handler'])) {
$handler = new DefinitionDecorator($config['service']['view_handler']);
$handler->setPublic(true);
$jsonpHandler = new Reference('fos_rest.view_handler.jsonp');
$handler->addMethodCall('registerHandler', ['jsonp', [$jsonpHandler, 'createResponse']]);
$container->setDefinition('fos_rest.view_handler', $handler);
$container->getDefinition('fos_rest.view_handler.jsonp')->replaceArgument(0, $config['view']['jsonp_handler']['callback_param']);
if (empty($config['view']['mime_types']['jsonp'])) {
$config['view']['mime_types']['jsonp'] = $config['view']['jsonp_handler']['mime_type'];
}
}
if ($config['view']['mime_types']['enabled']) {
$loader->load('mime_type_listener.xml');
if (!empty($config['mime_type_listener']['service'])) {
$service = $container->getDefinition('fos_rest.mime_type_listener');
$service->clearTag('kernel.event_listener');
}
$container->getDefinition('fos_rest.mime_type_listener')->replaceArgument(0, $config['view']['mime_types']['formats']);
}
if ($config['view']['view_response_listener']['enabled']) {
$loader->load('view_response_listener.xml');
$service = $container->getDefinition('fos_rest.view_response_listener');
if (!empty($config['view_response_listener']['service'])) {
$service->clearTag('kernel.event_listener');
}
$service->replaceArgument(1, $config['view']['view_response_listener']['force']);
}
$formats = [];
foreach ($config['view']['formats'] as $format => $enabled) {
if ($enabled) {
$formats[$format] = false;
}
}
foreach ($config['view']['templating_formats'] as $format => $enabled) {
if ($enabled) {
$formats[$format] = true;
}
}
$container->getDefinition('fos_rest.routing.loader.yaml_collection')->replaceArgument(3, $formats);
$container->getDefinition('fos_rest.routing.loader.xml_collection')->replaceArgument(3, $formats);
$container->getDefinition('fos_rest.routing.loader.reader.action')->replaceArgument(4, $formats);
foreach ($config['view']['force_redirects'] as $format => $code) {
if (true === $code) {
$config['view']['force_redirects'][$format] = Response::HTTP_FOUND;
}
}
if (!is_numeric($config['view']['failed_validation'])) {
$config['view']['failed_validation'] = constant('\Symfony\Component\HttpFoundation\Response::'.$config['view']['failed_validation']);
}
$defaultViewHandler = $container->getDefinition('fos_rest.view_handler.default');
$defaultViewHandler->replaceArgument(4, $formats);
$defaultViewHandler->replaceArgument(5, $config['view']['failed_validation']);
if (!is_numeric($config['view']['empty_content'])) {
$config['view']['empty_content'] = constant('\Symfony\Component\HttpFoundation\Response::'.$config['view']['empty_content']);
}
$defaultViewHandler->replaceArgument(6, $config['view']['empty_content']);
$defaultViewHandler->replaceArgument(7, $config['view']['serialize_null']);
$defaultViewHandler->replaceArgument(8, $config['view']['force_redirects']);
$defaultViewHandler->replaceArgument(9, $config['view']['default_engine']);
}
private function loadException(array $config, XmlFileLoader $loader, ContainerBuilder $container)
{
if ($config['exception']['enabled']) {
$loader->load('exception_listener.xml');
if (!empty($config['exception']['service'])) {
$service = $container->getDefinition('fos_rest.exception_listener');
$service->clearTag('kernel.event_subscriber');
}
if ($config['exception']['exception_controller']) {
$container->getDefinition('fos_rest.exception_listener')->replaceArgument(0, $config['exception']['exception_controller']);
} elseif (isset($container->getParameter('kernel.bundles')['TwigBundle'])) {
$container->getDefinition('fos_rest.exception_listener')->replaceArgument(0, 'fos_rest.exception.twig_controller:showAction');
}
$container->getDefinition('fos_rest.exception.codes_map')
->replaceArgument(0, $config['exception']['codes']);
$container->getDefinition('fos_rest.exception.messages_map')
->replaceArgument(0, $config['exception']['messages']);
$container->getDefinition('fos_rest.exception.controller')
->replaceArgument(2, $config['exception']['debug']);
$container->getDefinition('fos_rest.serializer.exception_normalizer.jms')
->replaceArgument(1, $config['exception']['debug']);
$container->getDefinition('fos_rest.serializer.exception_normalizer.symfony')
->replaceArgument(1, $config['exception']['debug']);
}
foreach ($config['exception']['codes'] as $exception => $code) {
if (!is_numeric($code)) {
$config['exception']['codes'][$exception] = constant("\Symfony\Component\HttpFoundation\Response::$code");
}
$this->testExceptionExists($exception);
}
foreach ($config['exception']['messages'] as $exception => $message) {
$this->testExceptionExists($exception);
}
}
private function loadSerializer(array $config, ContainerBuilder $container)
{
$bodyConverter = $container->hasDefinition('fos_rest.converter.request_body') ? $container->getDefinition('fos_rest.converter.request_body') : null;
$viewHandler = $container->getDefinition('fos_rest.view_handler.default');
if (!empty($config['serializer']['version'])) {
if ($bodyConverter) {
$bodyConverter->replaceArgument(2, $config['serializer']['version']);
}
$viewHandler->addMethodCall('setExclusionStrategyVersion', array($config['serializer']['version']));
}
if (!empty($config['serializer']['groups'])) {
if ($bodyConverter) {
$bodyConverter->replaceArgument(1, $config['serializer']['groups']);
}
$viewHandler->addMethodCall('setExclusionStrategyGroups', array($config['serializer']['groups']));
}
$viewHandler->addMethodCall('setSerializeNullStrategy', array($config['serializer']['serialize_null']));
}
private function loadZoneMatcherListener(array $config, XmlFileLoader $loader, ContainerBuilder $container)
{
if (!empty($config['zone'])) {
$loader->load('zone_matcher_listener.xml');
$zoneMatcherListener = $container->getDefinition('fos_rest.zone_matcher_listener');
foreach ($config['zone'] as $zone) {
$matcher = $this->createZoneRequestMatcher($container,
$zone['path'],
$zone['host'],
$zone['methods'],
$zone['ips']
);
$zoneMatcherListener->addMethodCall('addRequestMatcher', array($matcher));
}
}
}
private function createZoneRequestMatcher(ContainerBuilder $container, $path = null, $host = null, $methods = array(), $ip = null)
{
if ($methods) {
$methods = array_map('strtoupper', (array) $methods);
}
$serialized = serialize(array($path, $host, $methods, $ip));
$id = 'fos_rest.zone_request_matcher.'.md5($serialized).sha1($serialized);
// only add arguments that are necessary
$arguments = array($path, $host, $methods, $ip);
while (count($arguments) > 0 && !end($arguments)) {
array_pop($arguments);
}
$container
->setDefinition($id, new DefinitionDecorator('fos_rest.zone_request_matcher'))
->setArguments($arguments)
;
return new Reference($id);
}
/**
* Checks if an exception is loadable.
*
* @param string $exception Class to test
*
* @throws \InvalidArgumentException if the class was not found
*/
private function testExceptionExists($exception)
{
if (!is_subclass_of($exception, \Exception::class) && !is_a($exception, \Exception::class, true)) {
throw new \InvalidArgumentException("FOSRestBundle exception mapper: Could not load class '$exception' or the class does not extend from '\Exception'. Most probably this is a configuration problem.");
}
}
}
EventListener/AccessDeniedListener.php 0000666 00000005627 13052362416 0014104 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\EventListener;
use FOS\RestBundle\FOSRestBundle;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* This listener handles ensures that for specific formats AccessDeniedExceptions
* will return a 403 regardless of how the firewall is configured.
*
* @author Lukas Kahwe Smith
*
* @internal
*/
class AccessDeniedListener implements EventSubscriberInterface
{
private $formats;
private $challenge;
/**
* Constructor.
*
* @param array $formats An array with keys corresponding to request formats or content types
* that must be processed by this listener
* @param string $challenge
*/
public function __construct($formats, $challenge)
{
$this->formats = $formats;
$this->challenge = $challenge;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
static $handling;
if (true === $handling) {
return false;
}
$request = $event->getRequest();
if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
return false;
}
if (empty($this->formats[$request->getRequestFormat()]) && empty($this->formats[$request->getContentType()])) {
return false;
}
$handling = true;
$exception = $event->getException();
if ($exception instanceof AccessDeniedException) {
$exception = new AccessDeniedHttpException('You do not have the necessary permissions', $exception);
$event->setException($exception);
} elseif ($exception instanceof AuthenticationException) {
if ($this->challenge) {
$exception = new UnauthorizedHttpException($this->challenge, 'You are not authenticated', $exception);
} else {
$exception = new HttpException(401, 'You are not authenticated', $exception);
}
$event->setException($exception);
}
$handling = false;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::EXCEPTION => ['onKernelException', 5],
];
}
}
EventListener/AllowedMethodsListener.php 0000666 00000002705 13052362416 0014477 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\EventListener;
use FOS\RestBundle\FOSRestBundle;
use FOS\RestBundle\Response\AllowedMethodsLoader\AllowedMethodsLoaderInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
/**
* Listener to append Allow-ed methods for a given route/resource.
*
* @author Boris Guéry
*
* @internal
*/
class AllowedMethodsListener
{
private $loader;
/**
* Constructor.
*
* @param AllowedMethodsLoaderInterface $loader
*/
public function __construct(AllowedMethodsLoaderInterface $loader)
{
$this->loader = $loader;
}
/**
* @param FilterResponseEvent $event
*/
public function onKernelResponse(FilterResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
return;
}
$allowedMethods = $this->loader->getAllowedMethods();
if (isset($allowedMethods[$event->getRequest()->get('_route')])) {
$event->getResponse()
->headers
->set('Allow', implode(', ', $allowedMethods[$event->getRequest()->get('_route')]));
}
}
}
EventListener/BodyListener.php 0000666 00000012752 13052362416 0012464 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\EventListener;
use FOS\RestBundle\Decoder\DecoderProviderInterface;
use FOS\RestBundle\FOSRestBundle;
use FOS\RestBundle\Normalizer\ArrayNormalizerInterface;
use FOS\RestBundle\Normalizer\Exception\NormalizationException;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
/**
* This listener handles Request body decoding.
*
* @author Lukas Kahwe Smith
*
* @internal
*/
class BodyListener
{
private $decoderProvider;
private $throwExceptionOnUnsupportedContentType;
private $defaultFormat;
private $arrayNormalizer;
private $normalizeForms;
/**
* Constructor.
*
* @param DecoderProviderInterface $decoderProvider
* @param bool $throwExceptionOnUnsupportedContentType
* @param ArrayNormalizerInterface $arrayNormalizer
* @param bool $normalizeForms
*/
public function __construct(
DecoderProviderInterface $decoderProvider,
$throwExceptionOnUnsupportedContentType = false,
ArrayNormalizerInterface $arrayNormalizer = null,
$normalizeForms = false
) {
$this->decoderProvider = $decoderProvider;
$this->throwExceptionOnUnsupportedContentType = $throwExceptionOnUnsupportedContentType;
$this->arrayNormalizer = $arrayNormalizer;
$this->normalizeForms = $normalizeForms;
}
/**
* Sets the fallback format if there's no Content-Type in the request.
*
* @param string $defaultFormat
*/
public function setDefaultFormat($defaultFormat)
{
$this->defaultFormat = $defaultFormat;
}
/**
* Core request handler.
*
* @param GetResponseEvent $event
*
* @throws BadRequestHttpException
* @throws UnsupportedMediaTypeHttpException
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
return;
}
$method = $request->getMethod();
$contentType = $request->headers->get('Content-Type');
$normalizeRequest = $this->normalizeForms && $this->isFormRequest($request);
if ($this->isDecodeable($request)) {
$format = null === $contentType
? $request->getRequestFormat()
: $request->getFormat($contentType);
$format = $format ?: $this->defaultFormat;
$content = $request->getContent();
if (!$this->decoderProvider->supports($format)) {
if ($this->throwExceptionOnUnsupportedContentType
&& $this->isNotAnEmptyDeleteRequestWithNoSetContentType($method, $content, $contentType)
) {
throw new UnsupportedMediaTypeHttpException("Request body format '$format' not supported");
}
return;
}
if (!empty($content)) {
$decoder = $this->decoderProvider->getDecoder($format);
$data = $decoder->decode($content);
if (is_array($data)) {
$request->request = new ParameterBag($data);
$normalizeRequest = true;
} else {
throw new BadRequestHttpException('Invalid '.$format.' message received');
}
}
}
if (null !== $this->arrayNormalizer && $normalizeRequest) {
$data = $request->request->all();
try {
$data = $this->arrayNormalizer->normalize($data);
} catch (NormalizationException $e) {
throw new BadRequestHttpException($e->getMessage());
}
$request->request = new ParameterBag($data);
}
}
/**
* Check if the Request is not a DELETE with no content and no Content-Type.
*
* @param $method
* @param $content
* @param $contentType
*
* @return bool
*/
private function isNotAnEmptyDeleteRequestWithNoSetContentType($method, $content, $contentType)
{
return false === ('DELETE' === $method && empty($content) && empty($contentType));
}
/**
* Check if we should try to decode the body.
*
* @param Request $request
*
* @return bool
*/
protected function isDecodeable(Request $request)
{
if (!in_array($request->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
return false;
}
return !$this->isFormRequest($request);
}
/**
* Check if the content type indicates a form submission.
*
* @param Request $request
*
* @return bool
*/
private function isFormRequest(Request $request)
{
$contentTypeParts = explode(';', $request->headers->get('Content-Type'));
if (isset($contentTypeParts[0])) {
return in_array($contentTypeParts[0], ['multipart/form-data', 'application/x-www-form-urlencoded']);
}
return false;
}
}
EventListener/ExceptionListener.php 0000666 00000003426 13052362416 0013523 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\EventListener;
use FOS\RestBundle\FOSRestBundle;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\EventListener\ExceptionListener as HttpKernelExceptionListener;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
/**
* ExceptionListener.
*
* @author Ener-Getick
*
* @internal
*/
class ExceptionListener extends HttpKernelExceptionListener
{
/**
* {@inheritdoc}
*/
public function onKernelException(GetResponseForExceptionEvent $event)
{
$request = $event->getRequest();
if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
return;
}
parent::onKernelException($event);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::EXCEPTION => array('onKernelException', -100),
);
}
/**
* {@inheritdoc}
*/
protected function duplicateRequest(\Exception $exception, Request $request)
{
$attributes = array(
'_controller' => $this->controller,
'exception' => $exception,
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
);
$request = $request->duplicate(null, null, $attributes);
$request->setMethod('GET');
return $request;
}
}
EventListener/FormatListener.php 0000666 00000004533 13052362416 0013015 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\EventListener;
use FOS\RestBundle\FOSRestBundle;
use FOS\RestBundle\Util\StopFormatListenerException;
use FOS\RestBundle\Negotiation\FormatNegotiator;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* This listener handles Accept header format negotiations.
*
* @author Lukas Kahwe Smith
*
* @internal
*/
class FormatListener
{
private $formatNegotiator;
/**
* Initialize FormatListener.
*
* @param FormatNegotiatorInterface $formatNegotiator
*/
public function __construct(FormatNegotiator $formatNegotiator)
{
$this->formatNegotiator = $formatNegotiator;
}
/**
* Determines and sets the Request format.
*
* @param GetResponseEvent $event The event
*
* @throws NotAcceptableHttpException
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
return;
}
try {
$format = $request->getRequestFormat(null);
if (null === $format) {
$accept = $this->formatNegotiator->getBest('');
if (null !== $accept && 0.0 < $accept->getQuality()) {
$format = $request->getFormat($accept->getValue());
if (null !== $format) {
$request->attributes->set('media_type', $accept->getValue());
}
}
}
if (null === $format) {
if ($event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
throw new NotAcceptableHttpException('No matching accepted Response format could be determined');
}
return;
}
$request->setRequestFormat($format);
} catch (StopFormatListenerException $e) {
// nothing to do
}
}
}
EventListener/MimeTypeListener.php 0000666 00000004034 13052362416 0013312 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\EventListener;
use FOS\RestBundle\FOSRestBundle;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* This listener handles registering custom mime types.
*
* @author Lukas Kahwe Smith
*
* @internal
*/
class MimeTypeListener
{
private $mimeTypes;
/**
* Constructor.
*
* @param array $mimeTypes An array with the format as key and
* the corresponding mime type as value
*/
public function __construct(array $mimeTypes)
{
$this->mimeTypes = $mimeTypes;
}
/**
* Core request handler.
*
* @param GetResponseEvent $event The event
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
return;
}
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
foreach ($this->mimeTypes as $format => $mimeTypes) {
if (method_exists(Request::class, 'getMimeTypes')) {
$mimeTypes = array_merge($mimeTypes, Request::getMimeTypes($format));
} elseif (null !== $request->getMimeType($format)) {
$class = new \ReflectionClass(Request::class);
$properties = $class->getStaticProperties();
if (isset($properties['formats'][$format])) {
$mimeTypes = array_merge($mimeTypes, $properties['formats'][$format]);
}
}
$request->setFormat($format, $mimeTypes);
}
}
}
}
EventListener/ParamFetcherListener.php 0000666 00000007172 13052362416 0014130 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\EventListener;
use FOS\RestBundle\FOSRestBundle;
use FOS\RestBundle\Request\ParamFetcherInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
/**
* This listener handles various setup tasks related to the query fetcher.
*
* Setting the controller callable on the query fetcher
* Setting the query fetcher as a request attribute
*
* @author Lukas Kahwe Smith
*
* @internal
*/
class ParamFetcherListener
{
private $paramFetcher;
private $setParamsAsAttributes;
/**
* Constructor.
*
* @param ParamFetcherInterface $paramFetcher
* @param bool $setParamsAsAttributes
*/
public function __construct(ParamFetcherInterface $paramFetcher, $setParamsAsAttributes = false)
{
$this->paramFetcher = $paramFetcher;
$this->setParamsAsAttributes = $setParamsAsAttributes;
}
/**
* Core controller handler.
*
* @param FilterControllerEvent $event
*
* @throws \InvalidArgumentException
*/
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
return;
}
$controller = $event->getController();
if (is_callable($controller) && method_exists($controller, '__invoke')) {
$controller = [$controller, '__invoke'];
}
$this->paramFetcher->setController($controller);
$attributeName = $this->getAttributeName($controller);
$request->attributes->set($attributeName, $this->paramFetcher);
if ($this->setParamsAsAttributes) {
$params = $this->paramFetcher->all();
foreach ($params as $name => $param) {
if ($request->attributes->has($name) && null !== $request->attributes->get($name)) {
$msg = sprintf("ParamFetcher parameter conflicts with a path parameter '$name' for route '%s'", $request->attributes->get('_route'));
throw new \InvalidArgumentException($msg);
}
$request->attributes->set($name, $param);
}
}
}
/**
* Determines which attribute the ParamFetcher should be injected as.
*
* @param callable $controller The controller action as an "array" callable
*
* @return string
*/
private function getAttributeName(callable $controller)
{
list($object, $name) = $controller;
$method = new \ReflectionMethod($object, $name);
foreach ($method->getParameters() as $param) {
if ($this->isParamFetcherType($param)) {
return $param->getName();
}
}
// If there is no typehint, inject the ParamFetcher using a default name.
return 'paramFetcher';
}
/**
* Returns true if the given controller parameter is type-hinted as
* an instance of ParamFetcher.
*
* @param \ReflectionParameter $controllerParam A parameter of the controller action
*
* @return bool
*/
private function isParamFetcherType(\ReflectionParameter $controllerParam)
{
$type = $controllerParam->getClass();
if (null === $type) {
return false;
}
return $type->implementsInterface(ParamFetcherInterface::class);
}
}
EventListener/VersionListener.php 0000666 00000003522 13052362416 0013207 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\EventListener;
use FOS\RestBundle\FOSRestBundle;
use FOS\RestBundle\Version\VersionResolverInterface;
use FOS\RestBundle\View\ConfigurableViewHandlerInterface;
use FOS\RestBundle\View\ViewHandlerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
/**
* @internal
*/
class VersionListener
{
private $viewHandler;
private $versionResolver;
private $defaultVersion;
private $version = false;
public function __construct(ViewHandlerInterface $viewHandler, VersionResolverInterface $versionResolver, $defaultVersion = null)
{
$this->viewHandler = $viewHandler;
$this->versionResolver = $versionResolver;
$this->defaultVersion = $defaultVersion;
}
/**
* Gets the version.
*
* @return mixed
*/
public function getVersion()
{
return $this->version;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
return;
}
$this->version = $this->versionResolver->resolve($request);
if (false === $this->version && null !== $this->defaultVersion) {
$this->version = $this->defaultVersion;
}
if (false !== $this->version) {
$request->attributes->set('version', $this->version);
if ($this->viewHandler instanceof ConfigurableViewHandlerInterface) {
$this->viewHandler->setExclusionStrategyVersion($this->version);
}
}
}
}
EventListener/ViewResponseListener.php 0000666 00000012366 13052362416 0014221 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\EventListener;
use FOS\RestBundle\Controller\Annotations\View as ViewAnnotation;
use FOS\RestBundle\FOSRestBundle;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Templating\TemplateReferenceInterface;
/**
* The ViewResponseListener class handles the View core event as well as the "@extra:Template" annotation.
*
* @author Lukas Kahwe Smith
*
* @internal
*/
class ViewResponseListener implements EventSubscriberInterface
{
private $viewHandler;
private $forceView;
/**
* Constructor.
*
* @param ViewHandlerInterface $viewHandler
* @param bool $forceView
*/
public function __construct(ViewHandlerInterface $viewHandler, $forceView)
{
$this->viewHandler = $viewHandler;
$this->forceView = $forceView;
}
/**
* Renders the parameters and template and initializes a new response object with the
* rendered content.
*
* @param GetResponseForControllerResultEvent $event
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$request = $event->getRequest();
if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
return false;
}
$configuration = $request->attributes->get('_template');
$view = $event->getControllerResult();
if (!$view instanceof View) {
if (!$configuration instanceof ViewAnnotation && !$this->forceView) {
return;
}
$view = new View($view);
}
if ($configuration instanceof ViewAnnotation) {
if ($configuration->getTemplateVar()) {
$view->setTemplateVar($configuration->getTemplateVar());
}
if (null !== $configuration->getStatusCode() && (null === $view->getStatusCode() || Response::HTTP_OK === $view->getStatusCode())) {
$view->setStatusCode($configuration->getStatusCode());
}
$context = $view->getContext();
if ($configuration->getSerializerGroups()) {
$context->addGroups($configuration->getSerializerGroups());
}
if ($configuration->getSerializerEnableMaxDepthChecks()) {
$context->setMaxDepth(0);
}
list($controller, $action) = $configuration->getOwner();
$vars = $this->getDefaultVars($configuration, $controller, $action);
} else {
$vars = null;
}
if (null === $view->getFormat()) {
$view->setFormat($request->getRequestFormat());
}
if ($this->viewHandler->isFormatTemplating($view->getFormat())
&& !$view->getRoute()
&& !$view->getLocation()
) {
if (null !== $vars && 0 !== count($vars)) {
$parameters = (array) $this->viewHandler->prepareTemplateParameters($view);
foreach ($vars as $var) {
if (!array_key_exists($var, $parameters)) {
$parameters[$var] = $request->attributes->get($var);
}
}
$view->setData($parameters);
}
if ($configuration && ($template = $configuration->getTemplate()) && !$view->getTemplate()) {
if ($template instanceof TemplateReferenceInterface) {
$template->set('format', null);
}
$view->setTemplate($template);
}
}
$response = $this->viewHandler->handle($view, $request);
$event->setResponse($response);
}
public static function getSubscribedEvents()
{
// Must be executed before SensioFrameworkExtraBundle's listener
return array(
KernelEvents::VIEW => array('onKernelView', 30),
);
}
/**
* @param Request $request
* @param Template $template
* @param object $controller
* @param string $action
*
* @return array
*
* @see \Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener::resolveDefaultParameters()
*/
private function getDefaultVars(Template $template = null, $controller, $action)
{
if (0 !== count($arguments = $template->getVars())) {
return $arguments;
}
if (!$template instanceof ViewAnnotation || $template->isPopulateDefaultVars()) {
$r = new \ReflectionObject($controller);
$arguments = array();
foreach ($r->getMethod($action)->getParameters() as $param) {
$arguments[] = $param->getName();
}
return $arguments;
}
}
}
EventListener/ZoneMatcherListener.php 0000666 00000002633 13052362416 0014003 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\EventListener;
use FOS\RestBundle\FOSRestBundle;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
/**
* Matches FOSRest's zones.
*
* @author Florian Voutzinos
*
* @internal
*/
class ZoneMatcherListener
{
/**
* @var RequestMatcherInterface[]
*/
private $requestMatchers = array();
public function addRequestMatcher(RequestMatcherInterface $requestMatcher)
{
$this->requestMatchers[] = $requestMatcher;
}
/**
* Adds an optional "_fos_rest_zone" request attribute to be checked for existence by other listeners.
*
* @param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
foreach ($this->requestMatchers as $requestMatcher) {
if ($requestMatcher->matches($request)) {
$request->attributes->set(FOSRestBundle::ZONE_ATTRIBUTE, true);
return;
}
}
$request->attributes->set(FOSRestBundle::ZONE_ATTRIBUTE, false);
}
}
Exception/InvalidParameterException.php 0000666 00000003732 13052362416 0014334 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Exception;
use FOS\RestBundle\Controller\Annotations\ParamInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Validator\ConstraintViolationListInterface;
class InvalidParameterException extends BadRequestHttpException
{
private $parameter;
private $violations;
public function getParameter()
{
return $this->parameter;
}
public function getViolations()
{
return $this->violations;
}
public static function withViolations(ParamInterface $parameter, ConstraintViolationListInterface $violations)
{
$message = '';
foreach ($violations as $key => $violation) {
if ($key > 0) {
$message .= "\n";
}
$message .= sprintf(
'Parameter "%s" of value "%s" violated a constraint "%s"',
$parameter->getName(),
$violation->getInvalidValue(),
$violation->getMessage()
);
}
return self::withViolationsAndMessage($parameter, $violations, $message);
}
/**
* Do not use this method. It will be removed in 2.0.
*
* @param ParamInterface $parameter
* @param ConstraintViolationListInterface $violations
* @param string $message
*
* @return self
*
* @internal
*/
public static function withViolationsAndMessage(ParamInterface $parameter, ConstraintViolationListInterface $violations, $message)
{
$exception = new self($message);
$exception->parameter = $parameter;
$exception->violations = $violations;
return $exception;
}
}
FOSRestBundle.php 0000666 00000002673 13052362416 0007712 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle;
use FOS\RestBundle\DependencyInjection\Compiler\ConfigurationCheckPass;
use FOS\RestBundle\DependencyInjection\Compiler\JMSHandlersPass;
use FOS\RestBundle\DependencyInjection\Compiler\FormatListenerRulesPass;
use FOS\RestBundle\DependencyInjection\Compiler\SerializerConfigurationPass;
use FOS\RestBundle\DependencyInjection\Compiler\TwigExceptionPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* @author Lukas Kahwe Smith
* @author Eriksen Costa
*/
class FOSRestBundle extends Bundle
{
const ZONE_ATTRIBUTE = '_fos_rest_zone';
/**
* {@inheritdoc}
*/
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new SerializerConfigurationPass());
$container->addCompilerPass(new ConfigurationCheckPass());
$container->addCompilerPass(new FormatListenerRulesPass());
$container->addCompilerPass(new TwigExceptionPass());
$container->addCompilerPass(new JMSHandlersPass(), PassConfig::TYPE_REMOVE);
}
}
Form/Extension/DisableCSRFExtension.php 0000666 00000003466 13052362416 0014071 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Form\Extension;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
/**
* Class DisableCSRFExtension.
*
* @author Grégoire Pineau
*/
class DisableCSRFExtension extends AbstractTypeExtension
{
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
* @var string
*/
private $role;
/**
* @var AuthorizationCheckerInterface
*/
private $authorizationChecker;
public function __construct(TokenStorageInterface $tokenStorage, $role, AuthorizationCheckerInterface $authorizationChecker)
{
$this->tokenStorage = $tokenStorage;
$this->role = $role;
$this->authorizationChecker = $authorizationChecker;
}
public function configureOptions(OptionsResolver $resolver)
{
if (!$this->tokenStorage->getToken()) {
return;
}
if (!$this->authorizationChecker->isGranted($this->role)) {
return;
}
$resolver->setDefaults([
'csrf_protection' => false,
]);
}
public function getExtendedType()
{
return method_exists(AbstractType::class, 'getBlockPrefix')
? FormType::class
: 'form' // SF <2.8 BC
;
}
}
Form/Transformer/EntityToIdObjectTransformer.php 0000666 00000004252 13052362416 0016101 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Form\Transformer;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
/**
* Class EntityToIdObjectTransformer.
*
* @author Marc Juchli
*/
class EntityToIdObjectTransformer implements DataTransformerInterface
{
/**
* @var ObjectManager
*/
private $om;
/**
* @var string
*/
private $entityName;
/**
* @param ObjectManager $om
* @param string $entityName
*/
public function __construct(ObjectManager $om, $entityName)
{
$this->entityName = $entityName;
$this->om = $om;
}
/**
* Do nothing.
*
* @param object|null $object
*
* @return string
*/
public function transform($object)
{
if (null === $object) {
return '';
}
return current(array_values($this->om->getClassMetadata($this->entityName)->getIdentifierValues($object)));
}
/**
* Transforms an array including an identifier to an object.
*
* @param array $idObject
*
* @throws TransformationFailedException if object is not found
*
* @return object|null
*/
public function reverseTransform($idObject)
{
if (!is_array($idObject)) {
return;
}
$identifier = current(array_values($this->om->getClassMetadata($this->entityName)->getIdentifier()));
$id = $idObject[$identifier];
$object = $this->om
->getRepository($this->entityName)
->findOneBy([$identifier => $id]);
if (null === $object) {
throw new TransformationFailedException(sprintf(
'An object with identifier key "%s" and value "%s" does not exist!',
$identifier, $id
));
}
return $object;
}
}
Inflector/DoctrineInflector.php 0000666 00000001160 13052362416 0012623 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Inflector;
use Doctrine\Common\Inflector\Inflector;
/**
* Inflector object using the Doctrine/Inflector.
*
* @author Mark Kazemier
*/
class DoctrineInflector implements InflectorInterface
{
/**
* {@inheritdoc}
*/
public function pluralize($word)
{
return Inflector::pluralize($word);
}
}
Inflector/InflectorInterface.php 0000666 00000001036 13052362416 0012756 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Inflector;
/**
* Inflector interface.
*
* @author Mark Kazemier
*/
interface InflectorInterface
{
/**
* Pluralizes noun.
*
* @param string $word
*
* @return string
*/
public function pluralize($word);
}
Negotiation/FormatNegotiator.php 0000666 00000012746 13052362416 0013041 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Negotiation;
use FOS\RestBundle\Util\StopFormatListenerException;
use Negotiation\Accept;
use Negotiation\Negotiator as BaseNegotiator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* @author Ener-Getick
*/
class FormatNegotiator extends BaseNegotiator
{
private $map = [];
private $requestStack;
public function __construct(RequestStack $requestStack, array $mimeTypes = array())
{
$this->requestStack = $requestStack;
$this->mimeTypes = $mimeTypes;
}
/**
* @param RequestMatcherInterface $requestMatcher
* @param array $options
*/
public function add(RequestMatcherInterface $requestMatcher, array $options = [])
{
$this->map[] = [$requestMatcher, $options];
}
/**
* {@inheritdoc}
* The best format is also determined in function of the bundle configuration.
*
* @throws StopFormatListenerException
*/
public function getBest($header, array $priorities = [])
{
$request = $this->getRequest();
$header = $header ?: $request->headers->get('Accept');
foreach ($this->map as $elements) {
// Check if the current RequestMatcherInterface matches the current request
if (!$elements[0]->matches($request)) {
continue;
}
$options = &$elements[1]; // Do not reallow memory for this variable
if (!empty($options['stop'])) {
throw new StopFormatListenerException('Stopped format listener');
}
if (empty($options['priorities']) && empty($priorities)) {
if (!empty($options['fallback_format'])) {
return new Accept($request->getMimeType($options['fallback_format']));
}
continue;
}
if (isset($options['prefer_extension']) && $options['prefer_extension'] && !isset($extensionHeader)) {
$extension = pathinfo($request->getPathInfo(), PATHINFO_EXTENSION);
if (!empty($extension)) {
// $extensionHeader will now be either a non empty string or an empty string
$extensionHeader = $request->getMimeType($extension);
if ($header && $extensionHeader) {
$header .= ',';
}
$header .= $extensionHeader.'; q='.$options['prefer_extension'];
}
}
if ($header) {
$mimeTypes = $this->normalizePriorities($request,
empty($priorities) ? $options['priorities'] : $priorities
);
$mimeType = parent::getBest($header, $mimeTypes);
if ($mimeType !== null) {
return $mimeType;
}
}
if (isset($options['fallback_format'])) {
// if false === fallback_format then we fail here instead of considering more rules
if (false === $options['fallback_format']) {
return;
}
// stop looking at rules since we have a fallback defined
return new Accept($request->getMimeType($options['fallback_format']));
}
}
}
/**
* @param array $values
*
* @return array
*/
private function sanitize(array $values)
{
return array_map(function ($value) {
return preg_replace('/\s+/', '', strtolower($value));
}, $values);
}
/**
* Transform the format (json, html, ...) to their mimeType form (application/json, text/html, ...).
*
* @param Request $request
* @param string[] $priorities
*
* @return string[] formatted priorities
*/
private function normalizePriorities(Request $request, array $priorities)
{
$priorities = $this->sanitize($priorities);
$mimeTypes = array();
foreach ($priorities as $priority) {
if (strpos($priority, '/')) {
$mimeTypes[] = $priority;
continue;
}
if (method_exists(Request::class, 'getMimeTypes')) {
$mimeTypes = array_merge($mimeTypes, Request::getMimeTypes($priority));
} elseif (null !== $request->getMimeType($priority)) {
$class = new \ReflectionClass(Request::class);
$properties = $class->getStaticProperties();
$mimeTypes = array_merge($mimeTypes, $properties['formats'][$priority]);
}
if (isset($this->mimeTypes[$priority])) {
foreach ($this->mimeTypes[$priority] as $mimeType) {
$mimeTypes[] = $mimeType;
}
}
}
return $mimeTypes;
}
/**
* @throws \RuntimeException
*
* @return Request
*/
private function getRequest()
{
$request = $this->requestStack->getCurrentRequest();
if ($request === null) {
throw new \RuntimeException('There is no current request.');
}
return $request;
}
}
Normalizer/ArrayNormalizerInterface.php 0000666 00000001241 13052362416 0014345 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Normalizer;
/**
* Normalizes arrays.
*
* @author Florian Voutzinos
*/
interface ArrayNormalizerInterface
{
/**
* Normalizes the array.
*
* @param array $data The array to normalize
*
* @throws Exception\NormalizationException
*
* @return array The normalized array
*/
public function normalize(array $data);
}
Normalizer/CamelKeysNormalizer.php 0000666 00000004057 13052362416 0013333 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Normalizer;
use FOS\RestBundle\Normalizer\Exception\NormalizationException;
/**
* Normalizes the array by changing its keys from underscore to camel case.
*
* @author Florian Voutzinos
*/
class CamelKeysNormalizer implements ArrayNormalizerInterface
{
/**
* {@inheritdoc}
*/
public function normalize(array $data)
{
$this->normalizeArray($data);
return $data;
}
/**
* Normalizes an array.
*
* @param array &$data
*
* @throws Exception\NormalizationException
*/
private function normalizeArray(array &$data)
{
$normalizedData = array();
foreach ($data as $key => $val) {
$normalizedKey = $this->normalizeString($key);
if ($normalizedKey !== $key) {
if (array_key_exists($normalizedKey, $normalizedData)) {
throw new NormalizationException(sprintf(
'The key "%s" is invalid as it will override the existing key "%s"',
$key,
$normalizedKey
));
}
}
$normalizedData[$normalizedKey] = $val;
$key = $normalizedKey;
if (is_array($val)) {
$this->normalizeArray($normalizedData[$key]);
}
}
$data = $normalizedData;
}
/**
* Normalizes a string.
*
* @param string $string
*
* @return string
*/
protected function normalizeString($string)
{
if (false === strpos($string, '_')) {
return $string;
}
return preg_replace_callback('/_([a-zA-Z0-9])/', function ($matches) {
return strtoupper($matches[1]);
}, $string);
}
}
Normalizer/CamelKeysNormalizerWithLeadingUnderscore.php 0000666 00000002216 13052362416 0017500 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Normalizer;
/**
* Normalizes the array by changing its keys from underscore to camel case, while
* leaving leading underscores unchanged.
*
* @author Lukas Kahwe Smith
*/
class CamelKeysNormalizerWithLeadingUnderscore extends CamelKeysNormalizer
{
/**
* Normalizes a string while leaving leading underscores unchanged.
*
* @param string $string
*
* @return string
*/
protected function normalizeString($string)
{
if (false === strpos($string, '_')) {
return $string;
}
$offset = strspn($string, '_');
if ($offset) {
$underscorePrefix = substr($string, 0, $offset);
$string = substr($string, $offset);
} else {
$underscorePrefix = '';
}
return $underscorePrefix.parent::normalizeString($string);
}
}
Normalizer/Exception/NormalizationException.php 0000666 00000000743 13052362416 0016054 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Normalizer\Exception;
/**
* Exception thrown when the normalization failed.
*
* @author Florian Voutzinos
*/
class NormalizationException extends \RuntimeException
{
}
README.md 0000666 00000004305 13052362416 0006033 0 ustar 00 FOSRestBundle
=============
This bundle provides various tools to rapidly develop RESTful API's &
applications with Symfony. Features include:
- A View layer to enable output and format agnostic Controllers
- A custom route loader to generate url's following REST conventions
- Accept header format negotiation including handling for custom mime types
- RESTful decoding of HTTP request body and Accept headers
- Exception controller for sending appropriate HTTP status codes
[![Build Status](https://travis-ci.org/FriendsOfSymfony/FOSRestBundle.svg?branch=master)](https://travis-ci.org/FriendsOfSymfony/FOSRestBundle)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSRestBundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSRestBundle/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSRestBundle/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSRestBundle/?branch=master)
[![Total Downloads](https://poser.pugx.org/FriendsOfSymfony/rest-bundle/downloads.svg)](https://packagist.org/packages/FriendsOfSymfony/rest-bundle)
[![Latest Stable Version](https://poser.pugx.org/FriendsOfSymfony/rest-bundle/v/stable.svg)](https://packagist.org/packages/FriendsOfSymfony/rest-bundle)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/0be23389-2e85-49cf-b333-caaa36d11c62/mini.png)](https://insight.sensiolabs.com/projects/0be23389-2e85-49cf-b333-caaa36d11c62)
Note
----
FOSRestBundle 1.x is no longer maintained, 1.8 only receives security fixes. Please upgrade to FOSRestBundle 2.x as soon as possible.
Documentation
-------------
[Read the Documentation](http://symfony.com/doc/master/bundles/FOSRestBundle/index.html)
Please see the [UPGRADING.md](https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/UPGRADING-2.0.md) for any
relevant instructions when upgrading to a newer version.
Installation
------------
All the installation instructions are located in the [documentation](http://symfony.com/doc/master/bundles/FOSRestBundle/1-setting_up_the_bundle.html).
License
-------
This bundle is under the MIT license. See the complete license in the bundle:
Resources/meta/LICENSE
Request/ParamFetcher.php 0000666 00000016331 13052362416 0011260 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Request;
use FOS\RestBundle\Controller\Annotations\ParamInterface;
use FOS\RestBundle\Exception\InvalidParameterException;
use FOS\RestBundle\Util\ResolverTrait;
use FOS\RestBundle\Validator\Constraints\ResolvableConstraintInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\Exception\ValidatorException;
use Symfony\Component\Validator\ConstraintViolation;
/**
* Helper to validate parameters of the active request.
*
* @author Alexander
* @author Lukas Kahwe Smith
* @author Jordi Boggiano
* @author Boris Guéry
*/
class ParamFetcher implements ParamFetcherInterface
{
use ResolverTrait;
private $container;
private $parameterBag;
private $requestStack;
private $validator;
/**
* Initializes fetcher.
*
* @param ContainerInterface $container
* @param ParamReaderInterface $paramReader
* @param RequestStack $requestStack
* @param ValidatorInterface $validator
*/
public function __construct(ContainerInterface $container, ParamReaderInterface $paramReader, RequestStack $requestStack, ValidatorInterface $validator = null)
{
$this->container = $container;
$this->requestStack = $requestStack;
$this->validator = $validator;
$this->parameterBag = new ParameterBag($paramReader);
}
/**
* {@inheritdoc}
*/
public function setController($controller)
{
$this->parameterBag->setController($this->getRequest(), $controller);
}
/**
* Add additional params to the ParamFetcher during runtime.
*
* Note that adding a param that has the same name as an existing param will override that param.
*
* @param ParamInterface $param
*/
public function addParam(ParamInterface $param)
{
$this->parameterBag->addParam($this->getRequest(), $param);
}
/**
* @return ParamInterface[]
*/
public function getParams()
{
return $this->parameterBag->getParams($this->getRequest());
}
/**
* {@inheritdoc}
*/
public function get($name, $strict = null)
{
$params = $this->getParams();
if (!array_key_exists($name, $params)) {
throw new \InvalidArgumentException(sprintf("No @ParamInterface configuration for parameter '%s'.", $name));
}
/** @var ParamInterface $param */
$param = $params[$name];
$default = $param->getDefault();
$default = $this->resolveValue($this->container, $default);
$strict = (null !== $strict ? $strict : $param->isStrict());
$paramValue = $param->getValue($this->getRequest(), $default);
return $this->cleanParamWithRequirements($param, $paramValue, $strict, $default);
}
/**
* @param ParamInterface $param
* @param mixed $paramValue
* @param bool $strict
* @param mixed $default
*
* @throws BadRequestHttpException
* @throws \RuntimeException
*
* @return mixed
*
* @internal
*/
protected function cleanParamWithRequirements(ParamInterface $param, $paramValue, $strict, $default)
{
$this->checkNotIncompatibleParams($param);
if ($default !== null && $default === $paramValue) {
return $paramValue;
}
$constraints = $param->getConstraints();
$this->resolveConstraints($constraints);
if (empty($constraints)) {
return $paramValue;
}
if (null === $this->validator) {
throw new \RuntimeException(
'The ParamFetcher requirements feature requires the symfony/validator component.'
);
}
try {
$errors = $this->validator->validate($paramValue, $constraints);
} catch (ValidatorException $e) {
$violation = new ConstraintViolation(
$e->getMessage(),
$e->getMessage(),
array(),
$paramValue,
'',
null,
null,
$e->getCode()
);
$errors = new ConstraintViolationList(array($violation));
}
if (0 < count($errors)) {
if ($strict) {
throw InvalidParameterException::withViolations($param, $errors);
}
return null === $default ? '' : $default;
}
return $paramValue;
}
/**
* {@inheritdoc}
*/
public function all($strict = null)
{
$configuredParams = $this->getParams();
$params = [];
foreach ($configuredParams as $name => $param) {
$params[$name] = $this->get($name, $strict);
}
return $params;
}
/**
* Check if current param is not in conflict with other parameters
* according to the "incompatibles" field.
*
* @param ParamInterface $param the configuration for the param fetcher
*
* @throws InvalidArgumentException
* @throws BadRequestHttpException
*
* @internal
*/
protected function checkNotIncompatibleParams(ParamInterface $param)
{
$params = $this->getParams();
foreach ($param->getIncompatibilities() as $incompatibleParamName) {
if (!array_key_exists($incompatibleParamName, $params)) {
throw new \InvalidArgumentException(sprintf("No @ParamInterface configuration for parameter '%s'.", $incompatibleParamName));
}
$incompatibleParam = $params[$incompatibleParamName];
if ($incompatibleParam->getValue($this->getRequest(), null) !== null) {
$exceptionMessage = sprintf(
"'%s' param is incompatible with %s param.",
$param->getName(),
$incompatibleParam->getName()
);
throw new BadRequestHttpException($exceptionMessage);
}
}
}
/**
* @param Constraint[] $constraints
*/
private function resolveConstraints(array $constraints)
{
foreach ($constraints as $constraint) {
if ($constraint instanceof ResolvableConstraintInterface) {
$constraint->resolve($this->container);
}
}
}
/**
* @throws \RuntimeException
*
* @return Request
*/
private function getRequest()
{
$request = $this->requestStack->getCurrentRequest();
if ($request === null) {
throw new \RuntimeException('There is no current request.');
}
return $request;
}
}
Request/ParamFetcherInterface.php 0000666 00000002241 13052362416 0013074 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Request;
/**
* Helper interface to validate query parameters from the active request.
*
* @author Alexander
* @author Lukas Kahwe Smith
*/
interface ParamFetcherInterface
{
/**
* Sets the controller.
*
* @param callable $controller
*/
public function setController($controller);
/**
* Gets a validated parameter.
*
* @param string $name Name of the parameter
* @param bool $strict Whether a requirement mismatch should cause an exception
*
* @return mixed Value of the parameter
*/
public function get($name, $strict = null);
/**
* Gets all validated parameter.
*
* @param bool $strict Whether a requirement mismatch should cause an exception
*
* @return array Values of all the parameters
*/
public function all($strict = false);
}
Request/ParamReader.php 0000666 00000004625 13052362416 0011105 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Request;
use Doctrine\Common\Annotations\Reader;
use FOS\RestBundle\Controller\Annotations\ParamInterface;
/**
* Class loading "@ParamInterface" annotations from methods.
*
* @author Alexander
* @author Lukas Kahwe Smith
* @author Boris Guéry
*/
class ParamReader implements ParamReaderInterface
{
private $annotationReader;
/**
* Initializes controller reader.
*
* @param Reader $annotationReader
*/
public function __construct(Reader $annotationReader)
{
$this->annotationReader = $annotationReader;
}
/**
* {@inheritdoc}
*/
public function read(\ReflectionClass $reflection, $method)
{
if (!$reflection->hasMethod($method)) {
throw new \InvalidArgumentException(sprintf("Class '%s' has no method '%s'.", $reflection->getName(), $method));
}
$methodParams = $this->getParamsFromMethod($reflection->getMethod($method));
$classParams = $this->getParamsFromClass($reflection);
return array_merge($methodParams, $classParams);
}
/**
* {@inheritdoc}
*/
public function getParamsFromMethod(\ReflectionMethod $method)
{
$annotations = $this->annotationReader->getMethodAnnotations($method);
return $this->getParamsFromAnnotationArray($annotations);
}
/**
* {@inheritdoc}
*/
public function getParamsFromClass(\ReflectionClass $class)
{
$annotations = $this->annotationReader->getClassAnnotations($class);
return $this->getParamsFromAnnotationArray($annotations);
}
/**
* Fetches parameters from provided annotation array (fetched from annotationReader).
*
* @param array $annotations
*
* @return ParamInterface[]
*/
private function getParamsFromAnnotationArray(array $annotations)
{
$params = array();
foreach ($annotations as $annotation) {
if ($annotation instanceof ParamInterface) {
$params[$annotation->getName()] = $annotation;
}
}
return $params;
}
}
Request/ParamReaderInterface.php 0000666 00000002623 13052362416 0012722 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Request;
use FOS\RestBundle\Controller\Annotations\ParamInterface;
/**
* Interface for loading query parameters for a method.
*
* @author Alexander
* @author Lukas Kahwe Smith
*/
interface ParamReaderInterface
{
/**
* Read annotations for a given method.
*
* @param \ReflectionClass $reflection Reflection class
* @param string $method Method name
*
* @return ParamInterface[] Param annotation objects of the method. Indexed by parameter name
*/
public function read(\ReflectionClass $reflection, $method);
/**
* Read annotations for a given method.
*
* @param \ReflectionMethod $method Reflection method
*
* @return ParamInterface[] Param annotation objects of the method. Indexed by parameter name
*/
public function getParamsFromMethod(\ReflectionMethod $method);
/**
* @param \ReflectionClass $class
*
* @return ParamInterface[] Param annotation objects of the class. Indexed by parameter name
*/
public function getParamsFromClass(\ReflectionClass $class);
}
Request/ParameterBag.php 0000666 00000004626 13052362416 0011255 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Request;
use Doctrine\Common\Util\ClassUtils;
use FOS\RestBundle\Controller\Annotations\ParamInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Contains the {@link ParamFetcher} params and links them to a request.
*
* @internal
*/
final class ParameterBag
{
private $paramReader;
private $params = array();
public function __construct(ParamReaderInterface $paramReader)
{
$this->paramReader = $paramReader;
}
public function getParams(Request $request)
{
$requestId = spl_object_hash($request);
if (!isset($this->params[$requestId]) || empty($this->params[$requestId]['controller'])) {
throw new \InvalidArgumentException('Controller and method needs to be set via setController.');
}
if ($this->params[$requestId]['params'] === null) {
return $this->initParams($requestId);
}
return $this->params[$requestId]['params'];
}
public function addParam(Request $request, ParamInterface $param)
{
$requestId = spl_object_hash($request);
$this->getParams($request);
$this->params[$requestId]['params'][$param->getName()] = $param;
}
public function setController(Request $request, $controller)
{
$requestId = spl_object_hash($request);
$this->params[$requestId] = array(
'controller' => $controller,
'params' => null,
);
}
/**
* Initialize the parameters.
*
* @param string $requestId
*
* @throws \InvalidArgumentException
*/
private function initParams($requestId)
{
$controller = $this->params[$requestId]['controller'];
if (!is_array($controller) || empty($controller[0]) || !is_object($controller[0])) {
throw new \InvalidArgumentException(
'Controller needs to be set as a class instance (closures/functions are not supported)'
);
}
return $this->params[$requestId]['params'] = $this->paramReader->read(
new \ReflectionClass(ClassUtils::getClass($controller[0])),
$controller[1]
);
}
}
Request/RequestBodyParamConverter.php 0000666 00000013473 13052362416 0014042 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Request;
use FOS\RestBundle\Context\Context;
use FOS\RestBundle\Serializer\Serializer;
use JMS\Serializer\Exception\Exception as JMSSerializerException;
use JMS\Serializer\Exception\UnsupportedFormatException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Serializer\Exception\ExceptionInterface as SymfonySerializerException;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @author Tyler Stroud
*/
class RequestBodyParamConverter implements ParamConverterInterface
{
private $serializer;
private $context = [];
private $validator;
/**
* The name of the argument on which the ConstraintViolationList will be set.
*
* @var null|string
*/
private $validationErrorsArgument;
/**
* @param Serializer $serializer
* @param array|null $groups An array of groups to be used in the serialization context
* @param string|null $version A version string to be used in the serialization context
* @param ValidatorInterface $validator
* @param string|null $validationErrorsArgument
*
* @throws \InvalidArgumentException
*/
public function __construct(
Serializer $serializer,
$groups = null,
$version = null,
ValidatorInterface $validator = null,
$validationErrorsArgument = null
) {
$this->serializer = $serializer;
if (!empty($groups)) {
$this->context['groups'] = (array) $groups;
}
if (!empty($version)) {
$this->context['version'] = $version;
}
if (null !== $validator && null === $validationErrorsArgument) {
throw new \InvalidArgumentException('"$validationErrorsArgument" cannot be null when using the validator');
}
$this->validator = $validator;
$this->validationErrorsArgument = $validationErrorsArgument;
}
/**
* {@inheritdoc}
*/
public function apply(Request $request, ParamConverter $configuration)
{
$options = (array) $configuration->getOptions();
if (isset($options['deserializationContext']) && is_array($options['deserializationContext'])) {
$arrayContext = array_merge($this->context, $options['deserializationContext']);
} else {
$arrayContext = $this->context;
}
$this->configureContext($context = new Context(), $arrayContext);
try {
$object = $this->serializer->deserialize(
$request->getContent(),
$configuration->getClass(),
$request->getContentType(),
$context
);
} catch (UnsupportedFormatException $e) {
return $this->throwException(new UnsupportedMediaTypeHttpException($e->getMessage(), $e), $configuration);
} catch (JMSSerializerException $e) {
return $this->throwException(new BadRequestHttpException($e->getMessage(), $e), $configuration);
} catch (SymfonySerializerException $e) {
return $this->throwException(new BadRequestHttpException($e->getMessage(), $e), $configuration);
}
$request->attributes->set($configuration->getName(), $object);
if (null !== $this->validator) {
$validatorOptions = $this->getValidatorOptions($options);
$errors = $this->validator->validate($object, null, $validatorOptions['groups']);
$request->attributes->set(
$this->validationErrorsArgument,
$errors
);
}
return true;
}
/**
* {@inheritdoc}
*/
public function supports(ParamConverter $configuration)
{
return null !== $configuration->getClass();
}
/**
* @param Context $context
* @param array $options
*/
protected function configureContext(Context $context, array $options)
{
foreach ($options as $key => $value) {
if ($key === 'groups') {
$context->addGroups($options['groups']);
} elseif ($key === 'version') {
$context->setVersion($options['version']);
} elseif ($key === 'maxDepth') {
$context->setMaxDepth($options['maxDepth']);
} elseif ($key === 'serializeNull') {
$context->setSerializeNull($options['serializeNull']);
} else {
$context->setAttribute($key, $value);
}
}
}
/**
* Throws an exception or return false if a ParamConverter is optional.
*/
private function throwException(\Exception $exception, ParamConverter $configuration)
{
if ($configuration->isOptional()) {
return false;
}
throw $exception;
}
/**
* @param array $options
*
* @return array
*/
private function getValidatorOptions(array $options)
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
'groups' => null,
'traverse' => false,
'deep' => false,
]);
return $resolver->resolve(isset($options['validator']) ? $options['validator'] : []);
}
}
Resources/config/access_denied_listener.xml 0000666 00000001234 13052362416 0015171 0 ustar 00
Resources/config/allowed_methods_listener.xml 0000666 00000001743 13052362416 0015577 0 ustar 00
%kernel.debug%
Resources/config/body_listener.xml 0000666 00000002704 13052362416 0013360 0 ustar 00
Resources/config/exception_listener.xml 0000666 00000004635 13052362416 0014426 0 ustar 00
fos_rest.exception.controller:showAction
Resources/config/format_listener.xml 0000666 00000001647 13052362416 0013720 0 ustar 00
Resources/config/forms.xml 0000666 00000001200 13052362416 0011632 0 ustar 00
Resources/config/mime_type_listener.xml 0000666 00000001113 13052362416 0014404 0 ustar 00
Resources/config/param_fetcher_listener.xml 0000666 00000001235 13052362416 0015221 0 ustar 00
false
Resources/config/request.xml 0000666 00000001606 13052362416 0012206 0 ustar 00
Resources/config/request_body_param_converter.xml 0000666 00000001650 13052362416 0016471 0 ustar 00
Resources/config/routing.xml 0000666 00000006166 13052362416 0012213 0 ustar 00
Resources/config/schema/routing-1.0.xsd 0000666 00000004461 13052362416 0013741 0 ustar 00
Resources/config/schema/routing/rest_routing-1.0.xsd 0000666 00000003546 13052362416 0016470 0 ustar 00
Resources/config/serializer.xml 0000666 00000002454 13052362416 0012671 0 ustar 00
FOS\RestBundle\Serializer\Normalizer\FormErrorHandler
Resources/config/versioning.xml 0000666 00000003100 13052362416 0012670 0 ustar 00
Resources/config/view.xml 0000666 00000002255 13052362416 0011471 0 ustar 00
Resources/config/view_response_listener.xml 0000666 00000001133 13052362416 0015306 0 ustar 00
Resources/config/zone_matcher_listener.xml 0000666 00000001264 13052362416 0015101 0 ustar 00
Resources/doc/1-setting_up_the_bundle.rst 0000666 00000003260 13052362416 0014534 0 ustar 00 Step 1: Setting up the bundle
=============================
A) Download the Bundle
----------------------
Open a command console, enter your project directory and execute the
following command to download the latest stable version of this bundle:
.. code-block:: bash
$ composer require friendsofsymfony/rest-bundle
This command requires you to have Composer installed globally, as explained
in the `installation chapter`_ of the Composer documentation.
B) Enable the Bundle
--------------------
Then, enable the bundle by adding the following line in the ``app/AppKernel.php``
file of your project:
.. code-block:: php
// app/AppKernel.php
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
// ...
new FOS\RestBundle\FOSRestBundle(),
);
// ...
}
}
C) Enable a Serializer
----------------------
This bundle needs a serializer to work correctly. In most cases,
you'll need to enable a serializer or install one. This bundle tries
the following (in the given order) to determine the serializer to use:
#. The one you configured using ``fos_rest.service.serializer`` (if you did).
#. The JMS serializer, if the `JMSSerializerBundle`_ is available (and registered).
#. The `Symfony Serializer`_ if it's enabled (or any service called ``serializer``).
That was it!
.. _`installation chapter`: https://getcomposer.org/doc/00-intro.md
.. _`JMSSerializer`: https://github.com/schmittjoh/serializer
.. _`JMSSerializerBundle`: https://github.com/schmittjoh/JMSSerializerBundle
.. _`Symfony Serializer`: http://symfony.com/doc/current/cookbook/serializer.html
Resources/doc/2-the-view-layer.rst 0000666 00000042730 13052362416 0013032 0 ustar 00 Step 2: The view layer
======================
Introduction
------------
The view layer makes it possible to write ``format`` (html, json, xml, etc)
agnostic controllers, by placing a layer between the Controller and the
generation of the final output via the templating or a serializer.
The bundle works both with the `Symfony Serializer Component`_ and the more
sophisticated `serializer`_ created by Johannes Schmitt and integrated via the
`JMSSerializerBundle`_.
In your controller action you will then need to create a ``View`` instance that
is then passed to the ``fos_rest.view_handler`` service for processing. The
``View`` is somewhat modeled after the ``Response`` class, but as just stated
it simply works as a container for all the data/configuration for the
``ViewHandler`` class for this particular action. So the ``View`` instance
must always be processed by a ``ViewHandler`` (see the below section on the
"view response listener" for how to get this processing applied automatically)
FOSRestBundle ships with a controller extending the default Symfony controller,
which adds several convenience methods:
.. code-block:: php
view($data, 200)
->setTemplate("MyBundle:Users:getUsers.html.twig")
->setTemplateVar('users')
;
return $this->handleView($view);
}
public function redirectAction()
{
$view = $this->redirectView($this->generateUrl('some_route'), 301);
// or
$view = $this->routeRedirectView('some_route', array(), 301);
return $this->handleView($view);
}
}
.. versionadded:: 1.6
The ``setTemplateData`` method was added in 1.6.
There is also a trait called ``ControllerTrait`` for anyone that prefers to not
inject the container into their controller. This requires using setter injection
to set a ``ViewHandlerInterface`` instance via the ``setViewHandler`` method.
.. versionadded:: 2.0
The ``ControllerTrait`` trait was added in 2.0.
If you need to pass more data in template, not for serialization, you can use ``setTemplateData`` method:
.. code-block:: php
get('category_manager')->getBySlug($categorySlug);
$products = ...; // get data, in this case list of products.
$templateData = array('category' => $category);
$view = $this->view($products, 200)
->setTemplate("MyBundle:Category:show.html.twig")
->setTemplateVar('products')
->setTemplateData($templateData)
;
return $this->handleView($view);
}
}
or it is possible to use lazy-loading:
.. code-block:: php
get('category_manager');
$view = $this->view($products, 200)
->setTemplate("MyBundle:Category:show.html.twig")
->setTemplateVar('products')
->setTemplateData(function (ViewHandlerInterface $viewHandler, ViewInterface $view) use ($categoryManager, $categorySlug) {
$category = $categoryManager->getBySlug($categorySlug);
return array(
'category' => $category,
);
})
;
return $this->handleView($view);
}
}
To simplify this even more: If you rely on the ``ViewResponseListener`` in
combination with SensioFrameworkExtraBundle you can even omit the calls to
``$this->handleView($view)`` and directly return the view objects. See chapter
3 on listeners for more details on the View Response Listener.
As the purpose is to create a format-agnostic controller, data assigned to the
``View`` instance should ideally be an object graph, though any data type is
acceptable. Note that when rendering templating formats, the ``ViewHandler``
will wrap data types other than associative arrays in an associative array with
a single key (default ``'data'``), which will become the variable name of the
object in the respective template. You can change this variable by calling
the ``setTemplateVar()`` method on the view object.
There are also two specialized methods for redirect in the ``View`` classes.
``View::createRedirect`` redirects to an URL called ``RedirectView`` and
``View::createRouteRedirect`` redirects to a route. Note that whether these
classes actually cause a redirect or not is determined by the ``force_redirects``
configuration option, which is only enabled for ``html`` by default (see below).
There are several more methods on the ``View`` class, here is a list of all
the important ones for configuring the view:
* ``setData($data)`` - Set the object graph or list of objects to serialize.
* ``setTemplateData($data)`` - Set the template data array or anonymous function. Closure should return array.
* ``setHeader($name, $value)`` - Set a header to put on the HTTP response.
* ``setHeaders(array $headers)`` - Set multiple headers to put on the HTTP response.
* ``setStatusCode($code)`` - Set the HTTP status code.
* ``getContext()`` - The serialization context to use.
* ``setTemplate($template)`` - Name of the template to use in case of HTML rendering.
* ``setTemplateVar($templateVar)`` - Name of the variable the data is in, when passed
to HTML template. Defaults to ``'data'``.
* ``setEngine($engine)`` - Name of the engine to render HTML template. Can be
autodetected.
* ``setFormat($format)`` - The format the response is supposed to be rendered in.
Can be autodetected using HTTP semantics.
* ``setLocation($location)`` - The location to redirect to with a response.
* ``setRoute($route)`` - The route to redirect to with a response.
* ``setRouteParameters($parameters)`` - Set the parameters for the route.
* ``setResponse(Response $response)`` - The response instance that is populated
by the ``ViewHandler``.
See `this example code`_ for more details.
Forms and Views
---------------
Symfony Forms have special handling inside the view layer. Whenever you:
- return a Form from the controller.
- Set the form as only data of the view.
- return an array with a 'form' key, containing a form.
- return a form with validation errors.
Then:
- If the form is bound and no status code is set explicitly, an invalid form
leads to a "validation failed" response.
- In a rendered template, the form is passed as 'form' and ``createView()``
is called automatically.
- ``$form->getData()`` is passed into the view template as ``'data'`` if the
form is the only view data.
- An invalid form will be wrapped into an exception.
A response example of an invalid form:
.. code-block:: javascript
{
"code": 400,
"message": "Validation Failed";
"errors": {
"children": {
"username": {
"errors": [
"This value should not be blank."
]
}
}
}
}
If you don't like the default exception structure, you can provide your own
normalizers.
You can look at `FOSRestBundle normalizers`_ for examples.
.. _`FOSRestBundle normalizers`: https://github.com/FriendsOfSymfony/FOSRestBundle/tree/master/Serializer/Normalizer
Data Transformation
-------------------
As we have seen in the section before, the FOSRestBundle relies on the form
component (http://symfony.com/doc/current/components/form/introduction.html) to
handle submission of view data. In fact, the form builder
(http://symfony.com/doc/current/book/forms.html#building-the-form) basically
defines the structure of the expected view data which shall be used for further
processing - which most of the time relates to a PUT or POST request. This
brings a lot of flexibility and allows to exactly define the structure of data
to be received by the api.
Most of the time the requirements regarding a PUT/POST request are, in
terms of data structure, fairly simple. The payload within a PUT or POST request
oftentimes will have the exact same structure as received by a previous GET
request, but only with modified value fields. Thus, the fields to be defined
within the form builder process will be the same as the fields marked to be
serialized within an entity.
However, there is a common use case where straightforward updating of data,
received by a serialized object (GET request), will not work out of the box using
the given implementation of the form component: Simple assignment of a reference
using an object.
Let's take an entity ``Task`` that holds a reference to a ``Person`` as
an example. The serialized Task object will looks as follows:
.. code-block:: json
{"task_form":{"name":"Task1", "person":{"id":1, "name":"Fabien"}}}
In a traditional Symfony application we simply define the property of the
related class and it would perfectly assign the person to our task - in this
case based on the id:
.. code-block:: php
$builder
->add('name', 'text')
...
->add('person', 'entity', array(
'class' => 'Acme\DemoBundle\Entity\Person',
'property' => 'id'
))
Unfortunately, this form builder does not accept our serialized object as it is
- even though it contains the necessary id. In fact, the object would have to
contain the id directly assigned to the person field to be accepted by the
form validation process:
.. code-block:: json
{"task_form":{"name":"Task1", "person":1}}
Well, this is somewhat useless since we not only want to display the name of the
person but also do not want to do some client side trick to extract the id
before updating the data, right? Instead, we rather update the data the same way
as we received it in our GET request and thus, extend the form builder with a
data transformer. Fortunately, the FOSRestBundle comes with an
``EntityToIdObjectTransformer``, which can be applied to any form builder:
.. code-block:: php
$personTransformer = new EntityToIdObjectTransformer($this->om, "AcmeDemoBundle:Person");
$builder
->add('name', 'text')
...
->add($builder->create('person', 'text')->addModelTransformer($personTransformer))
This way, the data structure remains untouched and the person can be assigned to
the task without any client modifications.
Configuration
-------------
The ``formats`` and ``templating_formats`` settings determine which formats are
respectively supported by the serializer and by the template layer. In other
words any format listed in ``templating_formats`` will require a template for
rendering using the ``templating`` service, while any format listed in
``formats`` will use the serializer for rendering. For both settings a
value of ``false`` means that the given format is disabled.
When using ``RouteRedirectView::create()`` the default behavior of forcing a
redirect to the route for html is enabled, but needs to be enabled for other
formats if needed.
Finally the HTTP response status code for failed validation defaults to
``400``. Note when changing the default you can use name constants of
``Symfony\Component\HttpFoundation\Response`` class or an integer status code.
You can also set the default templating engine to something different than the
default of ``twig``:
.. code-block:: yaml
# app/config/config.yml
fos_rest:
view:
formats:
rss: true
xml: false
templating_formats:
html: true
force_redirects:
html: true
failed_validation: HTTP_BAD_REQUEST
default_engine: twig
See `this example configuration`_ for more details.
Custom handler
--------------
While many things should be possible via the serializer in some cases
it might not be enough. For example you might need some custom logic to be
executed in the ``ViewHandler``. For these cases one might want to register a
custom handler for a specific format. The custom handler can either be
registered by defining a custom service, via a compiler pass or it can even be
registered from inside the controller action.
The callable will receive 3 parameters:
* the instance of the ``ViewHandler``
* the instance of the ``View``
* the instance of the ``Request``
Note there are several public methods on the ``ViewHandler`` which can be helpful:
* ``isFormatTemplating()``
* ``createResponse()``
* ``createRedirectResponse()``
* ``renderTemplate()``
There is an example inside LiipHelloBundle to show how to register a custom handler:
https://github.com/liip/LiipHelloBundle/blob/master/View/RSSViewHandler.php
https://github.com/liip/LiipHelloBundle/blob/master/Resources/config/config.yml
There is another example in ``Resources\doc\examples``:
https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/Resources/doc/examples/RssHandler.php
Here is an example using a closure registered inside a Controller action:
.. code-block:: php
get('fos_rest.view_handler');
if (!$handler->isFormatTemplating($view->getFormat())) {
$templatingHandler = function ($handler, $view, $request) {
// if a template is set, render it using the 'params'
// and place the content into the data
if ($view->getTemplate()) {
$data = $view->getData();
if (empty($data['params'])) {
$params = array();
} else {
$params = $data['params'];
unset($data['params']);
}
$view->setData($params);
$data['html'] = $handler->renderTemplate($view, 'html');
$view->setData($data);
}
return $handler->createResponse($view, $request, $format);
};
$handler->registerHandler($view->getFormat(), $templatingHandler);
}
return $handler->handle($view);
}
}
JSONP custom handler
~~~~~~~~~~~~~~~~~~~~
To enable the common use case of creating JSONP responses this Bundle provides an
easy solution to handle a custom handler for this use case. Enabling this setting
also automatically uses the mime type listener (see the next chapter) to register
a mime type for JSONP.
Simply add the following to your configuration
.. code-block:: yaml
# app/config/config.yml
fos_rest:
view:
jsonp_handler: ~
It is also possible to customize both the name of the GET parameter with the
callback, as well as the filter pattern that validates if the provided callback
is valid or not.
.. code-block:: yaml
# app/config/config.yml
fos_rest:
view:
jsonp_handler:
callback_param: mycallback
Finally the filter can also be disabled by setting it to false.
.. code-block:: yaml
# app/config/config.yml
fos_rest:
view:
jsonp_handler:
callback_param: false
When working with JSONP, be aware of `CVE-2014-4671`_ (full explanation can be
found here: `Abusing JSONP with Rosetta Flash`_. You SHOULD use `NelmioSecurityBundle`_
and `disable the content type sniffing for script resources`_.
CSRF validation
~~~~~~~~~~~~~~~
When building a single application that should handle forms both via HTML forms
as well as via a REST API, one runs into a problem with CSRF token validation.
In most cases it is necessary to enable them for HTML forms, but it makes no
sense to use them for a REST API. For this reason there is a form extension to
disable CSRF validation for users with a specific role. This of course requires
that REST API users authenticate themselves and get a special role assigned.
.. code-block:: yaml
fos_rest:
disable_csrf_role: ROLE_API
That was it!
.. _`Symfony Serializer Component`: http://symfony.com/doc/current/components/serializer.html
.. _`serializer`: https://github.com/schmittjoh/serializer
.. _`JMSSerializerBundle`: https://github.com/schmittjoh/JMSSerializerBundle
.. _`this example code`: https://github.com/liip/LiipHelloBundle/blob/master/Controller/HelloController.php
.. _`this example configuration`: https://github.com/liip-forks/symfony-standard/blob/techtalk/app/config/config.yml
.. _`CVE-2014-4671`: http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-4671
.. _`Abusing JSONP with Rosetta Flash`: http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
.. _`NelmioSecurityBundle`: https://github.com/nelmio/NelmioSecurityBundle
.. _`disable the content type sniffing for script resources`: https://github.com/nelmio/NelmioSecurityBundle#content-type-sniffing
Resources/doc/3-listener-support.rst 0000666 00000022442 13052362416 0013526 0 ustar 00 Step 3: Listener support
========================
`Listeners`_ are a way to hook into the request handling. This Bundle provides
various events from decoding the request content in the request (body listener),
determining the correct response format (format listener), reading parameters
from the request (parameter fetcher listener), to formatting the response either
with a template engine like twig or to e.g. xml or json using a serializer (view
response listener) as well as automatically setting the accepted HTTP methods
in the response (accept listener).
With this in mind we now turn to explain each one of them.
All listeners except the ``mime_type`` listener are disabled by default. You
can enable one or more of these listeners. For example, below you can see how
to enable a few additional listeners:
.. code-block:: yaml
# app/config/config.yml
fos_rest:
param_fetcher_listener: true
body_listener: true
format_listener:
enabled: true
rules:
- { path: '^/', priorities: ['json', 'xml'], fallback_format: 'html' }
versioning: true
view:
view_response_listener: 'force'
It is possible to replace the service used for each of the listener if needed.
In this case, the Bundle listener will still be configured, however it will
not be registered in the kernel. The custom service listener will however not
be registered in the kernel, so it is up to the user to register it for the
appropriate event:
.. code-block:: yaml
# app/config/config.yml
fos_rest:
body_listener:
service: my_body_listener
my_body_listener:
class: Acme\BodyListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 10 }
arguments: ['@fos_rest.decoder_provider', '%fos_rest.throw_exception_on_unsupported_content_type%']
calls:
- [setDefaultFormat, ['%fos_rest.body_default_format%']]
View Response Listener
----------------------
The view response listener makes it possible to simply return a ``View``
instance from action controllers. The final output will then automatically be
processed via the listener by the ``fos_rest.view_handler`` service.
This requires adding the `SensioFrameworkExtraBundle`_ to your vendors.
For details see :doc:`View Response Listener `.
Body Listener
-------------
The Request body listener makes it possible to decode the contents of a request
in order to populate the "request" parameter bag of the Request. This, for
example, allows to receive data that normally would be sent via POST as
``application/x-www-form-urlencoded`` in a different format (for example
``application/json``) in a PUT. Please note that this listener is supposed to
allow you to decode and normalize data. If you want to deserialize data,
meaning getting an object of your choice, you will be better off using the
request body converter listener, documented below.
For details see :doc:`Body Listener `.
Request Body Converter Listener
-------------------------------
`ParamConverters`_ are a way to populate objects and inject them as controller
method arguments. The Request body converter makes it possible to deserialize
the request body into an object.
This converter requires that you have installed `SensioFrameworkExtraBundle`_
and have the converters enabled.
For details see :doc:`Request Body Converter Listener `.
Format Listener
---------------
The Request format listener attempts to determine the best format for the
request based on the HTTP Accept header and the format priority
configuration. This way it becomes possible to leverage Accept-Headers to
determine the request format, rather than a file extension (like foo.json).
For details see :doc:`Format Listener `.
Versioning
----------
This listener attemps to determine the current api version from different parameters of the ``Request``:
* the uri ``/{version}/users``
* a query parameter ``/users?version=v1``
* an ``Accept`` header ``Accept: appication/json; version=1.0``
* a custom header ``X-Accept-Version: v1``
For details see :doc:`Versioning `.
Mime Type Listener
------------------
This listener allows registering additional mime types in the ``Request``
class. It works similar to the `mime type listener`_ available in Symfony
since 2.5.
.. code-block:: yaml
# app/config/config.yml
fos_rest:
view:
mime_types: {'jsonp': ['application/javascript+jsonp']}
Param Fetcher Listener
----------------------
The param fetcher listener simply sets the ParamFetcher instance as a request attribute
configured for the matched controller so that the user does not need to do this manually.
For details see :doc:`Param Fetcher Listener `.
Allowed Http Methods Listener
-----------------------------
This listener adds the ``Allow`` HTTP header to each request appending all
allowed methods for a given resource.
Let's say we have the following routes:
.. code-block:: text
api_get_users
api_post_users
api_get_user
A ``GET`` request to ``api_get_users`` will respond with:
.. code-block:: text
HTTP/1.0 200 OK
Date: Sat, 16 Jun 2012 15:17:22 GMT
Server: Apache/2.2.22 (Ubuntu)
Allow: GET, POST
You need to enable this listener as follows, as it is disabled by default:
.. code-block:: yaml
fos_rest:
allowed_methods_listener: true
Security Exception Listener
---------------------------
By default it is the responsibility of firewall access points to deal with
AccessDeniedExceptions. For example the ``form`` entry point will redirect to
the login page. However, for a RESTful application proper response HTTP status
codes should be provided. This listener is triggered before the normal exception
listener and firewall entry points and forces returning either a 403 or 401
status code for any of the formats configured.
It will return 401 for
``Symfony\Component\Security\Core\Exception\AuthenticationException`` or 403 for
``Symfony\Component\Security\Core\Exception\AccessDeniedException``.
As a 401-response requires an authentication-challenge, you can set one using
the configuration ``unauthorized_challenge`` or leave it blank if you don't want
to send a challenge in the ``WWW-Authenticate`` header to the client.
If you want to use an advanced value in this header, it's worth looking at this:
`Test Cases for HTTP Test Cases for the HTTP WWW-Authenticate header field`_.
You need to enable this listener as follows, as it is disabled by default:
.. code-block:: yaml
fos_rest:
unauthorized_challenge: "Basic realm=\"Restricted Area\""
access_denied_listener:
# all requests using the 'json' format will return a 403 on an access denied violation
json: true
Note: The access_denied_listener doesn't return a response itself and must be coupled with an exception listener returning a response (see the :doc:`FOSRestBundle exception controller <4-exception-controller-support>`. or the `twig exception controller`_).
Zone Listener
=============
As you can see, FOSRestBundle provides multiple event listeners to enable REST-related features.
By default, these listeners will be registered to all requests and may conflict with other parts of your application.
Using the ``zone`` configuration, you can specify where the event listeners will be enabled. The zone configuration
allows to configure multiple zones in which the above listeners will be active. If no zone is configured, it means
that the above listeners will not be limited. If at least one zone is configured then the above listeners will
be skipped for all requests that do not match at least one zone. For a single zone config entry can contain matching
rules on the request ``path``, ``host``, ``methods`` and ``ip``.
.. code-block:: yaml
# app/config/config.yml
fos_rest:
zone:
- { path: ^/api/* }
Priorities
----------
========================== ===================== ========
Listener Event Priority
========================== ===================== ========
``ZoneMatcherListener`` ``kernel.request`` 248
``MimeTypeListener`` ``kernel.request`` 200
``FormatListener`` ``kernel.request`` 34
``VersionListener`` ``kernel.request`` 33
``BodyListener`` ``kernel.request`` 10
``ParamFetcherListener`` ``kernel.controller`` 5
``ViewResponseListener`` ``kernel.controller`` -10
``ViewResponseListener`` ``kernel.view`` 100
``AllowedMethodsListener`` ``kernel.response`` 0
========================== ===================== ========
That was it!
.. _`Listeners`: http://symfony.com/doc/master/cookbook/service_container/event_listener.html
.. _`SensioFrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html
.. _`ParamConverters`: http://symfony.com/doc/master/bundles/SensioFrameworkExtraBundle/annotations/converters.html
.. _`mime type listener`: http://symfony.com/doc/current/cookbook/request/mime_type.html
.. _`Test Cases for HTTP Test Cases for the HTTP WWW-Authenticate header field`: http://greenbytes.de/tech/tc/httpauth/
.. _`twig exception controller`: https://symfony.com/doc/current/cookbook/controller/error_pages.html
Resources/doc/4-exception-controller-support.rst 0000666 00000006342 13052362416 0016062 0 ustar 00 Step 4: ExceptionController support
===================================
When implementing an API it is also necessary to handle exceptions in a RESTful
way, while ensuring that no security sensitive information leaks out. This
bundle provides an extra controller for that job. Using this custom
ExceptionController it is possible to leverage the View layer when building
responses for uncaught Exceptions.
The ExceptionController can be enabled via the FOSRestBundle
configuration:
.. code-block:: yaml
# app/config/config.yml
fos_rest:
exception:
enabled: true
exception_controller: 'Acme\DemoBundle\Controller\ExceptionController::showAction'
.. note::
The FOSRestBundle ExceptionController is executed before the one of the TwigBundle.
.. note::
FOSRestBundle defines two services for exception rendering, by default it
configures ``fos_rest.exception.controller`` which only supports rendering
via a serializer. In case no explicit controller is configured by the user
and TwigBundle is detected it will automatically configure
``fos_rest.exception.twig_controller`` which additionally also supports
rendering via Twig.
To map Exception classes to HTTP response status codes an *exception map* may
be configured, where the keys match a fully qualified class name and the values
are either an integer HTTP response status code or a string matching a class
constant of the ``Symfony\Component\HttpFoundation\Response`` class:
.. code-block:: yaml
# app/config/config.yml
fos_rest:
exception:
codes:
'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404
'Doctrine\ORM\OptimisticLockException': HTTP_CONFLICT
messages:
'Acme\HelloBundle\Exception\MyExceptionWithASafeMessage': true
If you want to display the message from the exception in the content of the
response, add the exception to the messages map as well. If not only the status
code will be returned.
If you know what status code you want to return you do not have to add a
mapping, you can do this in your controller:
.. code-block:: php
validate($slug)) {
throw new HttpException(400, "New comment is not valid.");
}
}
}
In order to make the serialization format of exceptions customizable it is possible to
use serializer normalizers.
See `how to create handlers`_ for the JMS serializer and `how to create normalizers`_ for the Symfony serializer.
That was it!
.. note::
If you are receiving a 500 error where you would expect a different response, the issue
is likely caused by an exception inside the ExceptionController. For example a template
is not found or the serializer failed. You should take a look at the logs of your app to see if an uncaught exception has been logged.
.. _`how to create handlers`: http://jmsyst.com/libs/serializer/master/handlers
.. _`how to create normalizers`: http://thomas.jarrand.fr/blog/serialization/
Resources/doc/5-automatic-route-generation_single-restful-controller.rst 0000666 00000036043 13052362416 0022652 0 ustar 00 Routing
=======
The RestBundle provides custom route loaders to help in defining REST friendly
routes as well as reducing the manual work of configuring routes and the given
requirements (like making sure that only GET may be used in certain routes
etc.).
You may specify a ``default_format`` that the routing loader will use for the
``_format`` parameter if none is specified.
.. code-block:: yaml
# app/config/config.yml
fos_rest:
routing_loader:
default_format: json
Many of the features explained below are used in the following example code:
https://github.com/liip/LiipHelloBundle/blob/master/Controller/RestController.php
Single RESTful controller routes
--------------------------------
In this section we are looking at controllers for resources without sub-resources.
Handling of sub-resources requires some additional considerations which
are explained in the next section.
.. code-block:: yaml
# app/config/routing.yml
users:
type: rest
resource: Acme\HelloBundle\Controller\UsersController
This will tell Symfony to automatically generate proper REST routes from your
``UsersController`` action names. Notice ``type: rest`` option. It's required so
that the RestBundle can find which routes are supported.
Define resource actions
-----------------------
.. code-block:: php
; rel="kind_of_relation"``
* **unlink** - this action accepts *UNLINK* requests to the url ``/resources/{id}``
and is supposed to return nothing but a status code indicating that the specified
resources were unlinked. It is used to declare that some resources are not
related anymore. When calling a UNLINK url you must provide in your header at
least one link header formatted as follow :
``; rel="kind_of_relation"``
Important note about **link** and **unlink**: The implementation of the request
listener extracting the resources as entities is not provided by this bundle. A
good implementation can be found here: `REST APIs with Symfony2: The Right Way`_
It also contains some examples on how to use it. **link** and **unlink** were
obsoleted by RFC 2616, RFC 5988 aims to define it in a more clear way. Using
these methods is not risky, but remains unclear (cf. issues 323 and 325).
Conventional Actions
--------------------
HATEOAS, or Hypermedia as the Engine of Application State, is an aspect of REST
which allows clients to interact with the REST service with hypertext - most
commonly through an HTML page. There are 3 Conventional Action routings that are
supported by this bundle:
* **new** - A hypermedia representation that acts as the engine to *POST*.
Typically this is a form that allows the client to *POST* a new resource.
Shown as ``UsersController::newUsersAction()`` above.
* **edit** - A hypermedia representation that acts as the engine to *PUT*.
Typically this is a form that allows the client to *PUT*, or update, an
existing resource. Shown as ``UsersController::editUserAction()`` above.
* **remove** - A hypermedia representation that acts as the engine to *DELETE*.
Typically this is a form that allows the client to *DELETE* an existing resource.
Commonly a confirmation form. Shown as ``UsersController::removeUserAction()``
above.
Custom PATCH Actions
--------------------
All actions that do not match the ones listed in the sections above will
register as a *PATCH* action. In the controller shown above, these actions are
``UsersController::lockUserAction()``, ``UsersController::banUserAction()`` and
``UsersController::voteUserCommentAction()``. You could just as easily create a
method called ``UsersController::promoteUserAction()`` which would take a
*PATCH* request to the url ``/users/{slug}/promote``. This allows for easy
updating of aspects of a resource, without having to deal with the resource as a
whole at the standard *PATCH* or *PUT* endpoint.
Sub-Resource Actions
--------------------
Of course it's possible and common to have sub or child resources. They are
easily defined within the same controller by following the naming convention
``ResourceController::actionResourceSubResource()`` - as seen in the example
above with ``UsersController::getUserCommentsAction()``. This is a good strategy
to follow when the child resource needs the parent resource's ID in order to
look up itself.
Optional {_format} in route
---------------------------
By default, routes are generated with ``{_format}`` string. If you want to get clean
urls (``/orders`` instead ``/orders.{_format}``) then all you have to do is add
some configuration:
.. code-block:: yaml
# app/config/config.yml
fos_rest:
routing_loader:
include_format: false
The ``{_format}`` route requirement is automatically positioned using the available
listeners. So by default, the requirement will be ``{json|xml|html}``. If you want
to limit or add a custom format, you can do so by overriding it with the
``@Route`` annotation (or another one extending it, like ``@Get``, ``@Post``, ...):
.. code-block:: php
Notice ``parent: users`` option in the second case. This option specifies that
the comments resource is child of the users resource.
It is also necessary to add ``type: rest`` to the ``routing.yml`` file:
.. code-block:: yaml
# app/config/routing.yml
acme_hello:
type: rest
resource: "@AcmeHelloBundle/Resources/config/users_routes.yml"
In this case, your ``UsersController`` MUST always have a single resource
``get...`` action:
.. code-block:: php
=2.4`` (or the full framework) you have access to
the expression language component and can add conditions to your routing
configuration with annotations (see `Routing Conditions`_).
Example syntax:
.. code-block:: php
use FOS\RestBundle\Controller\Annotations\Route;
/**
* @Route("", condition="context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'")
*/
.. _`@Route Symfony annotation`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/routing.html
.. _`Routing Conditions`: http://symfony.com/doc/current/book/routing.html#book-routing-conditions
Resources/doc/body_listener.rst 0000666 00000007613 13052362416 0012674 0 ustar 00 Body Listener
=============
The Request body listener makes it possible to decode the contents of a request
in order to populate the "request" parameter bag of the Request. This for
example allows to receive data that normally would be sent via POST as
``application/x-www-form-urlencoded`` in a different format (for example
``application/json``) in a PUT.
Decoders
~~~~~~~~
You can add a decoder for a custom format. You can also replace the default
decoder services provided by the bundle for the ``json`` and ``xml`` formats.
Below you can see how to override the decoder for the json format (the xml
decoder is explicitly kept to its default service):
.. code-block:: yaml
# app/config/config.yml
fos_rest:
body_listener:
decoders:
json: acme.decoder.json
xml: fos_rest.decoder.xml
Your custom decoder service must use a class that implements the
``FOS\RestBundle\Decoder\DecoderInterface``.
If you want to be able to use a checkbox within a form and have true and false
values (without any issue) you have to use: ``fos_rest.decoder.jsontoform``
(available since FosRestBundle 0.8.0)
If the listener receives content that it tries to decode but the decode fails
then a BadRequestHttpException will be thrown with the message: ``'Invalid ' .
$format . ' message received'``. When combined with the :doc:`exception controller
support <4-exception-controller-support>` this means your API will provide
useful error messages to your API users if they are making invalid requests.
Array Normalizer
~~~~~~~~~~~~~~~~
Array normalizers allow to transform the data after it has been decoded in order
to facilitate its processing.
For example, you may want your API's clients to be able to send requests with
underscored keys but if you use a decoder without a normalizer, you will receive
the data as it is and it can lead to incorrect mapping if you submit the request
directly to a form. If you wish the body listener to transform underscored keys
to camel cased ones, you can use the ``camel_keys`` array normalizer:
.. code-block:: yaml
# app/config/config.yml
fos_rest:
body_listener:
array_normalizer: fos_rest.normalizer.camel_keys
.. note::
If you want to ignore leading underscores, for example in ``_username`` you can
instead use the ``fos_rest.normalizer.camel_keys_with_leading_underscore`` service.
Sometimes an array contains a key, which once normalized, will override an
existing array key. For example ``foo_bar`` and ``foo_Bar`` will both lead to
``fooBar``. If the normalizer receives this data, the listener will throw a
BadRequestHttpException with the message ``The key "foo_Bar" is invalid as it
will override the existing key "fooBar"``.
.. note::
If you use the ``camel_keys`` normalizer, you must be careful when choosing
your form name.
You can also create your own array normalizer by implementing the
``FOS\RestBundle\Normalizer\ArrayNormalizerInterface``.
.. code-block:: yaml
# app/config/config.yml
fos_rest:
body_listener:
array_normalizer: acme.normalizer.custom
By default, the array normalizer is only applied to requests with a decodable format.
If you want form data to be normalized, you can use the ``forms`` flag:
.. code-block:: yaml
# app/config/config.yml
fos_rest:
body_listener:
array_normalizer:
service: fos_rest.normalizer.camel_keys
forms: true
Using the ArrayNormalizer with login forms
------------------------------------------
If you use the default configuration for the csrf token fieldname (``_csrf_token``)
the Array normalizer will mangle the field name. To make it work, use a name that
is camelcased, like this:
.. code-block:: yaml
security:
firewalls:
admin:
# ...
form_login:
# ...
csrf_parameter: _csrfToken
Resources/doc/configuration-reference.rst 0000666 00000010237 13052362416 0014631 0 ustar 00 Full default configuration
==========================
.. code-block:: yaml
# Default configuration for extension with alias: "fos_rest"
fos_rest:
disable_csrf_role: null
access_denied_listener:
enabled: false
service: null
formats:
# Prototype
name: ~
unauthorized_challenge: null
param_fetcher_listener:
enabled: false
force: false
service: null
cache_dir: '%kernel.cache_dir%/fos_rest'
allowed_methods_listener:
enabled: false
service: null
routing_loader:
default_format: null
include_format: true
body_converter:
enabled: false
validate: false
validation_errors_argument: validationErrors
service:
router: router
templating: templating
serializer: null
view_handler: fos_rest.view_handler.default
inflector: fos_rest.inflector.doctrine
validator: validator
serializer:
version: null
groups: []
serialize_null: false
view:
default_engine: twig
force_redirects:
# Prototype
name: ~
mime_types:
enabled: false
service: null
formats:
# Prototype
name: ~
formats:
# Prototype
name: ~
templating_formats:
# Prototype
name: ~
view_response_listener:
enabled: false
force: false
service: null
failed_validation: 400
empty_content: 204
serialize_null: false
jsonp_handler:
callback_param: callback
mime_type: application/javascript+jsonp
exception:
enabled: false
exception_controller: null
codes:
# Prototype
name: ~
messages:
# Prototype
name: ~
body_listener:
enabled: true
service: null
default_format: null
throw_exception_on_unsupported_content_type: false
decoders:
# Prototype
name: ~
array_normalizer:
service: null
forms: false
format_listener:
enabled: false
service: null
rules:
# URL path info
path: null
# URL host name
host: null
# Method for URL
methods: null
stop: false
prefer_extension: true
fallback_format: html
attributes: []
priorities: []
versioning:
enabled: false
default_version: ~
resolvers:
query:
enabled: true
parameter_name: version
custom_header:
enabled: true
header_name: X-Accept-Version
media_type:
enabled: true
regex: /(v|version)=(?P[0-9\.]+)/
guessing_order:
- query
- custom_header
- media_type
Resources/doc/empty-content-status-code.rst 0000666 00000001471 13052362416 0015065 0 ustar 00 Status code when responding with no content
===========================================
In some use cases the api should not send any content, especially when deleting (*DELETE*) or updating (*PUT* or *PATCH*) a resource.
By default, ``FOSRestBundle`` will send a *204* status if the response is empty.
If you want to use another status code for empty responses, you can update your configuration file:
.. code-block:: yaml
fos_rest:
view:
empty_content: 204
.. versionadded:: 2.0
Until FOSRestBundle 2.0 this code will be used even if another code is configured manually inside the view object!
If you don't want to use the default empty content status for a specific empty ``Response``, you just
have to set a status code manually thanks to the ``@View()`` annotation or the ``View`` class.
Resources/doc/examples/RssHandler.php 0000666 00000006532 13052362416 0013673 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Examples;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandler;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* This is an example RSS ViewHandler.
* It also shows how to handle exceptions within the ViewHandler so that the
* client can get a decent response.
*
* Please note that you will need to install the Zend library to use this
* handler.
*
* Configuration:
*
* services:
* my.rss_handler:
* class: FOS\RestBundle\Examples\RssHandler
* arguments:
* logger: "@?logger"
*
* my.view_handler:
* parent: fos_rest.view_handler.default
* calls:
* - ['registerHandler', [ 'rss', ["@my.rss_handler", 'createResponse'] ] ]
*
* fos_rest:
* service:
* view_handler: my.view_handler
*
* @author Tarjei Huse (tarjei - at scanmine.com)
*/
class RssHandler
{
private $logger;
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
/**
* Converts the viewdata to a RSS feed. Modify to suit your datastructure.
*
* @return Response
*/
public function createResponse(ViewHandler $handler, View $view, Request $request)
{
try {
$content = $this->createFeed($view->getData());
$code = Response::HTTP_OK;
} catch (\Exception $e) {
if ($this->logger) {
$this->logger->error($e);
}
$content = sprintf('%s:
%s
', $e->getMessage(), $e->getTraceAsString());
$code = Response::HTTP_BAD_REQUEST;
}
return new Response($content, $code, $view->getHeaders());
}
/**
* @param $data array
* @param format string, either rss or atom
*/
protected function createFeed($data, $format = 'rss')
{
$feed = new \Zend_Feed_Writer_Feed();
$feed->setTitle($data['title']);
$feed->setLink($data['link']);
$feed->setFeedLink($data['link'], 'rss');
$feed->addAuthor([
'name' => 'ZeroCMS',
'email' => 'email!',
]);
$feed->setDateModified(time());
$feed->setDescription('RSS feed from query');
// Add one or more entries. Note that entries must be manually added once created.
foreach ($data['documents'] as $document) {
$entry = $feed->createEntry();
$entry->setTitle($document['title']);
$entry->setLink($document['url']);
$entry->addAuthor([
'name' => $document['author'],
//'email' => '',
//'uri' => '',
]);
$entry->setDateModified($document['dateUpdated']->getTimestamp());
$entry->setDateCreated($document['dateCreated']->getTimestamp());
if (isset($document['summary'])) {
$entry->setDescription($document['summary']);
}
$entry->setContent($document['body']);
$feed->addEntry($entry);
}
return $feed->export($format);
}
}
Resources/doc/examples/chaplin_backbone.md 0000666 00000002004 13052362416 0014667 0 ustar 00 # Sample FOSRest application with Chaplin.js and Backbone.js
[DunglasTodoMVCBundle](https://github.com/dunglas/DunglasTodoMVCBundle) is a Symfony implementation of the popular [TodoMVC](http://todomvc.com/) project.
This example app includes:
* A REST API built with [FOSRestBundle](https://github.com/FriendsOfSymfony/FOSRestBundle) using a [body listener](https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/Resources/doc/3-listener-support.md#body-listener), a [format listener](https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/Resources/doc/3-listener-support.md#format-listener) and the `fos_rest.decoder.jsontoform` decoder
* JSON serialization of Doctrine entities through [JMSSerializerBundle](https://github.com/schmittjoh/JMSSerializerBundle)
* CSRF protection through [DunglasAngularCsrfBundle](https://github.com/dunglas/DunglasAngularCsrfBundle)
* A client built in [CoffeeScript](http://coffeescript.org/) with [Chaplin.js](http://chaplinjs.org/) and [Backbone.js](http://backbonejs.org/)
Resources/doc/format_listener.rst 0000666 00000011652 13052362416 0013225 0 ustar 00 Format Listener
===============
The Request format listener attempts to determine the best format for the
request based on the Request's Accept-Header and the format priority
configuration. This way it becomes possible to leverage Accept-Headers to
determine the request format, rather than a file extension (like foo.json).
The ``priorities`` define the order of media types as the application
prefers. Note that if a format is provided instead of a media type, the
format is converted into a list of media types matching the format.
The algorithm iteratively examines the provided Accept header first
looking at all the options with the highest ``q``. The first priority that
matches is returned. If none match the next lowest set of Accept headers with
equal ``q`` is examined and so on until there are no more Accept headers to
check. In this case ``fallback_format`` is used.
Note that if ``_format`` is matched inside the route, then a virtual Accept
header setting is added with a ``q`` setting one lower than the lowest Accept
header, meaning that format is checked for a match in the priorities last. If
``prefer_extension`` is set to ``true`` then the virtual Accept header will be
one higher than the highest ``q`` causing the extension to be checked first.
Setting ``priorities`` to a non-empty array enables Accept header negotiations.
.. code-block:: yaml
# app/config/config.yml
fos_rest:
format_listener:
enabled: true
rules:
# setting fallback_format to json means that instead of considering the next rule in case of a priority mismatch, json will be used
- { path: '^/', host: 'api.%domain%', priorities: ['json', 'xml'], fallback_format: json, prefer_extension: false }
# setting fallback_format to false means that instead of considering the next rule in case of a priority mismatch, a 406 will be caused
- { path: '^/image', priorities: ['jpeg', 'gif'], fallback_format: false, prefer_extension: true }
# setting fallback_format to null means that in case of a priority mismatch the next rule will be considered
- { path: '^/admin', methods: ['GET', 'POST'], priorities: ['xml', 'html'], fallback_format: ~, prefer_extension: false }
# you can specifically target the exception controller
- { path: '^/api', priorities: ['xml', 'json'], fallback_format: xml, attributes: { _controller: FOS\RestBundle\Controller\ExceptionController }, prefer_extension: false }
# setting a priority to */* basically means any format will be matched
- { path: '^/', priorities: ['text/html', '*/*'], fallback_format: html, prefer_extension: true }
For example using the above configuration and the following Accept header:
.. code-block:: text
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/json
And the following route:
.. code-block:: yaml
hello:
path: /foo.{_format}
defaults: { _controller: foo.controller:indexAction, _format: ~ }
When calling:
* ``/foo.json`` will lead to setting the request format to ``json``
* ``/foo`` will lead to setting the request format to ``html``
Furthermore the listener sets a ``media_type`` attribute on the request in
case the listener is configured with a ``MediaTypeNegotiatorInterface`` instance,
which is the case by default, with the matched media type.
.. code-block:: php
// f.e. text/html or application/vnd.custom_something+json etc.
$mediaType = $request->attributes->get('media_type');
The ``priorities`` should be configured carefully, especially when the
controller actions for specific routes only handle necessary security checks
for specific formats. In such cases it might make sense to hard code the format
in the controller action.
.. code-block:: php
public function getAction(Request $request)
{
$view = new View();
// hard code the output format of the controller action
$view->setFormat('html');
// ...
}
Note that if you use custom mime types, they need to be added using the :doc:`Mime Type Listener <3-listener-support>`.
Disabling the Format Listener via Rules
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Often when integrating this Bundle with existing applications, it might be
useful to disable the format listener for some routes. In this case it is
possible to define a rule that will stop the format listener from determining a
format by setting ``stop`` to ``true`` as a rule option. Any rule containing
this setting and any rule following will not be considered and the Request
format will remain unchanged.
.. code-block:: yaml
# app/config/config.yml
fos_rest:
format_listener:
enabled: true
rules:
- { path: '^/api', priorities: ['json', 'xml'], fallback_format: json, prefer_extension: false }
- { path: '^/', stop: true } # Available for version >= 1.5
Resources/doc/index.rst 0000666 00000005344 13052362416 0011140 0 ustar 00 Getting Started With FOSRestBundle
==================================
.. toctree::
:hidden:
1-setting_up_the_bundle
2-the-view-layer
empty-content-status-code
3-listener-support
view_response_listener
body_listener
request_body_converter_listener
format_listener
versioning
param_fetcher_listener
4-exception-controller-support
5-automatic-route-generation_single-restful-controller
6-automatic-route-generation_multiple-restful-controllers
7-manual-route-definition
annotations-reference
configuration-reference
Installation
------------
Installation is a quick (I promise!) one-step process:
1. :doc:`Setting up the bundle <1-setting_up_the_bundle>`
Bundle usage
------------
Before you start using the bundle it is advised you run a quick look over the
six sections listed below. This bundle contains many features that are loosely
coupled so you may or may not need to use all of them. This bundle is just a
tool to help you in the job of creating a REST API with Symfony.
FOSRestBundle provides several tools to assist in building REST applications:
- :doc:`The view layer <2-the-view-layer>`
- :doc:`Listener support <3-listener-support>`
- :doc:`ExceptionController support <4-exception-controller-support>`
- :doc:`Automatic route generation: single RESTful controller <5-automatic-route-generation_single-restful-controller>` (for simple resources)
- :doc:`Automatic route generation: multiple RESTful controllers <6-automatic-route-generation_multiple-restful-controllers>` (for resources with child/subresources)
- :doc:`Manual definition of routes <7-manual-route-definition>`
Config reference
----------------
- :doc:`Configuration reference ` for a reference on
the available configuration options
- :doc:`Annotations reference ` for a reference on
the available configurations through annotations
Example applications
--------------------
The following bundles/applications use the FOSRestBundle and can be used as a
guideline:
- The `LiipHelloBundle`_ provides several examples for the RestBundle.
- There is also `a fork of the Symfony2 Standard Edition`_ that is configured to
show the LiipHelloBundle examples.
- The `FOSCommentBundle`_ uses FOSRestBundle for its API.
- The `Symfony2 Rest Edition`_ provides a complete example of how to build a
controller that works for both HTML as well as JSON/XML.
.. _`LiipHelloBundle`: https://github.com/liip/LiipHelloBundle
.. _`a fork of the Symfony2 Standard Edition`: https://github.com/liip-forks/symfony-standard/tree/techtalk
.. _`FOSCommentBundle`: https://github.com/FriendsOfSymfony/FOSCommentBundle
.. _`Symfony2 Rest Edition`: https://github.com/gimler/symfony-rest-edition
Resources/doc/param_fetcher_listener.rst 0000666 00000021026 13052362416 0014531 0 ustar 00 Param Fetcher Listener
======================
The param fetcher listener simply sets the ParamFetcher instance as a request attribute
configured for the matched controller so that the user does not need to do this manually.
.. code-block:: yaml
# app/config/config.yml
fos_rest:
param_fetcher_listener: true
.. code-block:: php
name = "dynamic_request";
$dynamicRequestParam->requirements = "\d+";
$paramFetcher->addParam($dynamicRequestParam);
$dynamicQueryParam = new QueryParam();
$dynamicQueryParam->name = "dynamic_query";
$dynamicQueryParam->requirements="[a-z]+";
$paramFetcher->addParam($dynamicQueryParam);
$page = $paramFetcher->get('page');
$articles = array('bim', 'bam', 'bingo');
return array('articles' => $articles, 'page' => $page);
}
.. note::
There is also ``$paramFetcher->all()`` to fetch all configured query
parameters at once. And also both ``$paramFetcher->get()`` and
``$paramFetcher->all()`` support and optional ``$strict`` parameter to throw
a ``\RuntimeException`` on a validation error.
.. note::
The ParamFetcher requirements feature requires the symfony/validator
component.
Optionally the listener can also already set all configured query parameters as
request attributes
.. code-block:: yaml
# app/config/config.yml
fos_rest:
param_fetcher_listener: force
.. code-block:: php
$articles, 'page' => $page);
}
Container parameters can be used in requirements and default field.
.. note::
The percent sign (%) in ``requirements`` and ``default`` field, must be
escaped with another percent sign
.. code-block:: php
0) {
// Handle validation errors
}
// ...
}
You can configure the validation groups used by the validator
via the ``validator`` option:
.. code-block:: php
/**
* @ParamConverter("post", converter="fos_rest.request_body", options={"validator"={"groups"={"foo", "bar"}}})
*/
public function putPostAction(Post $post, ConstraintViolationListInterface $validationErrors)
{
if (count($validationErrors) > 0) {
// Handle validation errors
}
// ...
}
.. _`ParamConverters`: http://symfony.com/doc/master/bundles/SensioFrameworkExtraBundle/annotations/converters.html
.. _`SensioFrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html
Resources/doc/versioning.rst 0000666 00000012200 13052362416 0012201 0 ustar 00 API versioning
==============
Think about versioning your API
-------------------------------
If you introduce changes to your current API, your users might not upgrade their applications right away, so versioning is important to prevent existing applications to break when such changes are made.
How to version your API ?
-------------------------
There are several ways, none is standard:
* Use an uri parameter ``/v1/users``
* Use a query parameter ``/users?version=v1``
* Use a custom mime-type with an ``Accept`` header ``Accept: application/json; version=1.0``
* Use a custom header ``X-Accept-Version: v1``
The ``FOSRestBundle`` allows you to use several of them at the same time or to choose one of them.
URI API versioning
------------------
If you want to version your api with the uri, you can simply use the symfony router:
.. code-block:: yaml
# app/config/routing.yml
my_route:
# ...
path: /{version}/foo/route
Note: this will override the ``version`` attribute of the request if you use the ``FOSRestBundle`` versioning.
Configure ``FOSRestBundle`` to use the api versioning
-----------------------------------------------------
You should activate the versioning in your config.yml:
.. code-block:: yaml
# app/config/config.yml
fos_rest:
versioning: true
If you do not want to allow all the methods described above, you should choose which version resolver to enable:
.. code-block:: yaml
#app/config/config.yml
fos_rest:
versioning:
enabled: true
resolvers:
query: true # Query parameter: /users?version=v1
custom_header: true # X-Accept-Version header
media_type: # Accept header
enabled: true
regex: '/(v|version)=(?P[0-9\.]+)/'
You can also choose the guessing order:
.. code-block:: yaml
# app/config/config.yml
fos_rest:
versioning:
enabled: true
guessing_order:
- query
- custom_header
- media_type
The matched version is set as a Request attribute with the name ``version``,
and when using JMS serializer it is also set as an exclusion strategy
automatically in the ``ViewHandler``.
If you want to version by Accept header, you will need to do the following:
#. The format listener must be enabled
See :doc:`Format Listener `
#. The client must pass the requested version in his header like this :
.. code-block:: yaml
Accept:application/json;version=1.0
#. You must configure the possible mime types for all supported versions:
.. code-block:: yaml
fos_rest:
view:
mime_types:
json: ['application/json', 'application/json;version=1.0', 'application/json;version=1.1']
Note: If you have to handle huge versions and mime types, you can simplify the configuration with a php script:
.. code-block:: php
// app/config/fos_rest_mime_types.php
$versions = array(
'1.0',
'1.1',
'2.0',
);
$mimeTypes = array(
'json' => array(
'application/json',
),
'yml' => array(
'application/yaml',
'text/yaml',
),
);
array_walk($mimeTypes, function (&$mimeTypes, $format, $versions) {
$versionMimeTypes = array();
foreach ($mimeTypes as $mimeType) {
foreach ($versions as $version) {
array_push($versionMimeTypes, sprintf('%s;version=%s', $mimeType, $version));
array_push($versionMimeTypes, sprintf('%s;v=%s', $mimeType, $version));
}
}
$mimeTypes = array_merge($mimeTypes, $versionMimeTypes);
}, $versions);
$container->loadFromExtension('fos_rest', array(
'view' => array(
'mime_types' => $mimeTypes,
),
));
And then, import it from your config.yml file:
.. code-block:: yaml
imports:
- { resource: assets_version.php }
Use the ``JMSSerializer`` with the API versioning
-------------------------------------------------
You should have tagged your entities with version information (@Since, @Until ...)
See `this JMS Serializer article`_ for details about versioning objects.
.. _`this JMS Serializer article`: http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies#versioning-objects
That's it, it should work now.
How to match a specific version in my routing ?
-----------------------------------------------
You can use conditions on your request to check for the version that was determined:
.. code-block:: yaml
my_route:
# ...
condition: "request.attributes.get('version') == 'v2'"
When using the :doc:`automatic route generation <5-automatic-route-generation_single-restful-controller>`,
you can also use the ``@Version`` annotation to set the above condition automatically on all methods
in the given controller.
.. code-block:: php
use FOS\RestBundle\Controller\Annotations\Version;
/**
* @Version("v2")
*
* or if you support multiple versions in this controller
* @Version({"v1", "v2"})
*/
class MyController
{
}
Resources/doc/view_response_listener.rst 0000666 00000011503 13052362416 0014620 0 ustar 00 View Response listener
======================
The view response listener makes it possible to simply return a ``View``
instance from action controllers. The final output will then automatically be
processed via the listener by the ``fos_rest.view_handler`` service.
This requires adding the `SensioFrameworkExtraBundle`_ to your vendors.
Now inside a controller it's possible to simply return a ``View`` instance.
.. code-block:: php
setData($data);
return $view;
}
}
As this feature is heavily based on the `SensioFrameworkExtraBundle`_, the
example can further be simplified by using the various annotations supported by
that bundle. There is also one additional annotation called ``@View()`` which
extends from the ``@Template()`` annotation.
Note: `SensioFrameworkExtraBundle`_ must be in your kernel if you want to use the annotations and ``sensio_framework_extra.view.annotations`` must be set to true.
The ``@View()`` and ``@Template()`` annotations behave essentially the same with
a minor difference. When ``view_response_listener`` is set to ``true`` instead
of ``force`` and ``@View()`` is not used, then rendering will be delegated to
`SensioFrameworkExtraBundle`_ (you must enable the view annotations in
`SensioFrameworkExtraBundle`_ for that case, use the default configuration).
.. code-block:: php
setData($data)
->setTemplateData($templateData)
;
return $view;
}
If ``@View()`` is used, the template variable name used to render templating
formats can be configured (default ``'data'``):
.. code-block:: php
setVersion('1.0');
$context->addGroup('user');
$view->setSerializationContext($context);
// ...
$view
->setData($data)
->setTemplateData($templateData)
;
return $view;
}
See `this example code`_ for more details.
The ViewResponse listener will automatically populate your view with request
attributes if you do not provide any data when returning a view object. This
behaviour comes from `SensioFrameworkExtraBundle`_ and will automatically add
any variables listed in the ``_template_default_vars`` request attribute when no
data is supplied. In some cases, this is not desirable and can be disabled by
either supplying the data you want or disabling the automatic population of data
with the ``@View`` annotation:
.. code-block:: php
/**
* $user will no longer end up in the View's data.
*
* @View(populateDefaultVars=false)
*/
public function getUserDetails(User $user)
{
}
.. _`SensioFrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html
.. _`this example code`: https://github.com/liip/LiipHelloBundle/blob/master/Controller/ExtraController.php
Resources/meta/LICENSE 0000666 00000002105 13052362416 0010455 0 ustar 00 Copyright (c) FriendsOfSymfony
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Response/AllowedMethodsLoader/AllowedMethodsLoaderInterface.php 0000666 00000001304 13052362416 0021004 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Response\AllowedMethodsLoader;
/**
* AllowedMethodsLoaderInterface.
*
* @author Boris Guéry
*/
interface AllowedMethodsLoaderInterface
{
/**
* Returns the allowed http methods.
*
* array(
* 'some_route' => array('GET', 'POST'),
* 'another_route' => array('DELETE', 'PUT'),
* );
*
* @return array
*/
public function getAllowedMethods();
}
Response/AllowedMethodsLoader/AllowedMethodsRouterLoader.php 0000666 00000005266 13052362416 0020377 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Response\AllowedMethodsLoader;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\Routing\RouterInterface;
/**
* AllowedMethodsRouterLoader implementation using RouterInterface to fetch
* allowed http methods.
*
* @author Boris Guéry
*/
class AllowedMethodsRouterLoader implements AllowedMethodsLoaderInterface, CacheWarmerInterface
{
private $router;
private $cache;
/**
* Constructor.
*
* @param RouterInterface $router
* @param string $cacheDir
* @param bool $isDebug Kernel debug flag
*/
public function __construct(RouterInterface $router, $cacheDir, $isDebug)
{
$this->router = $router;
$this->cache = new ConfigCache(sprintf('%s/allowed_methods.cache.php', $cacheDir), $isDebug);
}
/**
* {@inheritdoc}
*/
public function getAllowedMethods()
{
if (!$this->cache->isFresh()) {
$this->warmUp(null);
}
return require $this->cache->getPath();
}
/**
* {@inheritdoc}
*/
public function isOptional()
{
return true;
}
/**
* {@inheritdoc}
*/
public function warmUp($cacheDir)
{
$processedRoutes = [];
$routeCollection = $this->router->getRouteCollection();
foreach ($routeCollection->all() as $name => $route) {
if (!isset($processedRoutes[$route->getPath()])) {
$processedRoutes[$route->getPath()] = [
'methods' => [],
'names' => [],
];
}
$processedRoutes[$route->getPath()]['names'][] = $name;
$processedRoutes[$route->getPath()]['methods'] = array_merge(
$processedRoutes[$route->getPath()]['methods'],
$route->getMethods()
);
}
$allowedMethods = [];
foreach ($processedRoutes as $processedRoute) {
if (count($processedRoute['methods']) > 0) {
foreach ($processedRoute['names'] as $name) {
$allowedMethods[$name] = array_unique($processedRoute['methods']);
}
}
}
$this->cache->write(
sprintf('getResources()
);
}
}
Routing/ClassResourceInterface.php 0000666 00000001012 13052362416 0013302 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Routing;
/**
* Implement interface to define that missing resources in the methods should
* use the class name to identify the resource.
*
* @author Lukas Kahwe Smith
*/
interface ClassResourceInterface
{
}
Routing/Loader/ClassUtils.php 0000666 00000003063 13052362416 0012210 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Routing\Loader;
/**
* @internal
*/
class ClassUtils
{
/**
* Returns the full class name for the first class in the file.
*
* @param string $file A PHP file path
*
* @return string|false Full class name if found, false otherwise
*/
public static function findClassInFile($file)
{
$class = false;
$namespace = false;
$tokens = token_get_all(file_get_contents($file));
for ($i = 0, $count = count($tokens); $i < $count; ++$i) {
$token = $tokens[$i];
if (!is_array($token)) {
continue;
}
if (true === $class && T_STRING === $token[0]) {
return $namespace.'\\'.$token[1];
}
if (true === $namespace && T_STRING === $token[0]) {
$namespace = '';
do {
$namespace .= $token[1];
$token = $tokens[++$i];
} while ($i < $count && is_array($token) && in_array($token[0], array(T_NS_SEPARATOR, T_STRING)));
}
if (T_CLASS === $token[0]) {
$class = true;
}
if (T_NAMESPACE === $token[0]) {
$namespace = true;
}
}
return false;
}
}
Routing/Loader/DirectoryRouteLoader.php 0000666 00000004072 13052362416 0014235 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Routing\Loader;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Routing\RouteCollection;
/**
* Parse annotated controller classes from all files of a directory.
*
* @author Christian Flothmann
*/
class DirectoryRouteLoader extends Loader
{
private $fileLocator;
private $processor;
public function __construct(FileLocatorInterface $fileLocator, RestRouteProcessor $processor)
{
$this->fileLocator = $fileLocator;
$this->processor = $processor;
}
/**
* {@inheritdoc}
*/
public function load($resource, $type = null)
{
if (isset($resource[0]) && '@' === $resource[0]) {
$resource = $this->fileLocator->locate($resource);
}
if (!is_dir($resource)) {
throw new \InvalidArgumentException(sprintf('Given resource of type "%s" is no directory.', $resource));
}
$collection = new RouteCollection();
$finder = new Finder();
foreach ($finder->in($resource)->name('*.php')->files() as $file) {
if($class = ClassUtils::findClassInFile($file)) {
$imported = $this->processor->importResource($this, $class, array(), null, null, 'rest');
$collection->addCollection($imported);
}
}
return $collection;
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
if ('rest' !== $type || !is_string($resource)) {
return false;
}
if (isset($resource[0]) && '@' === $resource[0]) {
$resource = $this->fileLocator->locate($resource);
}
return is_dir($resource);
}
}
Routing/Loader/Reader/RestActionReader.php 0000666 00000051137 13052362416 0014527 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Routing\Loader\Reader;
use Doctrine\Common\Annotations\Reader;
use FOS\RestBundle\Controller\Annotations\Route as RouteAnnotation;
use FOS\RestBundle\Inflector\InflectorInterface;
use FOS\RestBundle\Request\ParamReaderInterface;
use FOS\RestBundle\Routing\RestRouteCollection;
use Psr\Http\Message\MessageInterface;
use Symfony\Component\Routing\Route;
/**
* REST controller actions reader.
*
* @author Konstantin Kudryashov
*/
class RestActionReader
{
const COLLECTION_ROUTE_PREFIX = 'c';
/**
* @var Reader
*/
private $annotationReader;
/**
* @var ParamReaderInterface
*/
private $paramReader;
/**
* @var InflectorInterface
*/
private $inflector;
/**
* @var array
*/
private $formats;
/**
* @var bool
*/
private $includeFormat;
/**
* @var string|null
*/
private $routePrefix;
/**
* @var string|null
*/
private $namePrefix;
/**
* @var array|string|null
*/
private $versions;
/**
* @var bool|null
*/
private $pluralize;
/**
* @var array
*/
private $parents = [];
/**
* @var array
*/
private $availableHTTPMethods = [
'get',
'post',
'put',
'patch',
'delete',
'link',
'unlink',
'head',
'options',
'mkcol',
'propfind',
'proppatch',
'move',
'copy',
'lock',
'unlock',
];
/**
* @var array
*/
private $availableConventionalActions = ['new', 'edit', 'remove'];
/**
* Initializes controller reader.
*
* @param Reader $annotationReader
* @param ParamReaderInterface $paramReader
* @param InflectorInterface $inflector
* @param bool $includeFormat
* @param array $formats
*/
public function __construct(Reader $annotationReader, ParamReaderInterface $paramReader, InflectorInterface $inflector, $includeFormat, array $formats = [])
{
$this->annotationReader = $annotationReader;
$this->paramReader = $paramReader;
$this->inflector = $inflector;
$this->includeFormat = $includeFormat;
$this->formats = $formats;
}
/**
* Sets routes prefix.
*
* @param string $prefix Routes prefix
*/
public function setRoutePrefix($prefix = null)
{
$this->routePrefix = $prefix;
}
/**
* Returns route prefix.
*
* @return string
*/
public function getRoutePrefix()
{
return $this->routePrefix;
}
/**
* Sets route names prefix.
*
* @param string $prefix Route names prefix
*/
public function setNamePrefix($prefix = null)
{
$this->namePrefix = $prefix;
}
/**
* Returns name prefix.
*
* @return string
*/
public function getNamePrefix()
{
return $this->namePrefix;
}
/**
* Sets route names versions.
*
* @param array|string|null $versions Route names versions
*/
public function setVersions($versions = null)
{
$this->versions = (array) $versions;
}
/**
* Returns versions.
*
* @return array|null
*/
public function getVersions()
{
return $this->versions;
}
/**
* Sets pluralize.
*
* @param bool|null $pluralize Specify if resource name must be pluralized
*/
public function setPluralize($pluralize)
{
$this->pluralize = $pluralize;
}
/**
* Returns pluralize.
*
* @return bool|null
*/
public function getPluralize()
{
return $this->pluralize;
}
/**
* Set parent routes.
*
* @param array $parents Array of parent resources names
*/
public function setParents(array $parents)
{
$this->parents = $parents;
}
/**
* Returns parents.
*
* @return array
*/
public function getParents()
{
return $this->parents;
}
/**
* Reads action route.
*
* @param RestRouteCollection $collection
* @param \ReflectionMethod $method
* @param string[] $resource
*
* @throws \InvalidArgumentException
*
* @return Route
*/
public function read(RestRouteCollection $collection, \ReflectionMethod $method, $resource)
{
// check that every route parent has non-empty singular name
foreach ($this->parents as $parent) {
if (empty($parent) || '/' === substr($parent, -1)) {
throw new \InvalidArgumentException(
"Every parent controller must have `get{SINGULAR}Action(\$id)` method\n".
'where {SINGULAR} is a singular form of associated object'
);
}
}
// if method is not readable - skip
if (!$this->isMethodReadable($method)) {
return;
}
// if we can't get http-method and resources from method name - skip
$httpMethodAndResources = $this->getHttpMethodAndResourcesFromMethod($method, $resource);
if (!$httpMethodAndResources) {
return;
}
list($httpMethod, $resources, $isCollection, $isInflectable) = $httpMethodAndResources;
$arguments = $this->getMethodArguments($method);
// if we have only 1 resource & 1 argument passed, then it's object call, so
// we can set collection singular name
if (1 === count($resources) && 1 === count($arguments) - count($this->parents)) {
$collection->setSingularName($resources[0]);
}
// if we have parents passed - merge them with own resource names
if (count($this->parents)) {
$resources = array_merge($this->parents, $resources);
}
if (empty($resources)) {
$resources[] = null;
}
$routeName = $httpMethod.$this->generateRouteName($resources);
$urlParts = $this->generateUrlParts($resources, $arguments, $httpMethod);
// if passed method is not valid HTTP method then it's either
// a hypertext driver, a custom object (PUT) or collection (GET)
// method
if (!in_array($httpMethod, $this->availableHTTPMethods)) {
$urlParts[] = $httpMethod;
$httpMethod = $this->getCustomHttpMethod($httpMethod, $resources, $arguments);
}
// generated parameters
$routeName = strtolower($routeName);
$path = implode('/', $urlParts);
$defaults = ['_controller' => $method->getName()];
$requirements = [];
$options = [];
$host = '';
$versionCondition = $this->getVersionCondition();
$annotations = $this->readRouteAnnotation($method);
if (!empty($annotations)) {
foreach ($annotations as $annotation) {
$path = implode('/', $urlParts);
$defaults = ['_controller' => $method->getName()];
$requirements = [];
$options = [];
$methods = explode('|', $httpMethod);
$annoRequirements = $annotation->getRequirements();
$annoMethods = $annotation->getMethods();
if (!empty($annoMethods)) {
$methods = $annoMethods;
}
$path = $annotation->getPath() !== null ? $this->routePrefix.$annotation->getPath() : $path;
$requirements = array_merge($requirements, $annoRequirements);
$options = array_merge($options, $annotation->getOptions());
$defaults = array_merge($defaults, $annotation->getDefaults());
$host = $annotation->getHost();
$schemes = $annotation->getSchemes();
$combinedCondition = $this->combineConditions($versionCondition, $annotation->getCondition());
$this->includeFormatIfNeeded($path, $requirements);
// add route to collection
$route = new Route(
$path, $defaults, $requirements, $options, $host, $schemes, $methods, $combinedCondition
);
$this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable, $annotation);
}
} else {
$this->includeFormatIfNeeded($path, $requirements);
$methods = explode('|', strtoupper($httpMethod));
// add route to collection
$route = new Route(
$path, $defaults, $requirements, $options, $host, [], $methods, $versionCondition
);
$this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable);
}
}
/**
* @return string|null
*/
private function getVersionCondition()
{
if (empty($this->versions)) {
return;
}
return sprintf("request.attributes.get('version') in ['%s']", implode("', '", $this->versions));
}
/**
* @param string|null $conditionOne
* @param string|null $conditionTwo
*
* @return string|null
*/
private function combineConditions($conditionOne, $conditionTwo)
{
if (null === $conditionOne) {
return $conditionTwo;
}
if (null === $conditionTwo) {
return $conditionOne;
}
return sprintf('(%s) and (%s)', $conditionOne, $conditionTwo);
}
/**
* Include the format in the path and requirements if its enabled.
*
* @param string $path
* @param array $requirements
*/
private function includeFormatIfNeeded(&$path, &$requirements)
{
if ($this->includeFormat === true) {
$path .= '.{_format}';
if (!isset($requirements['_format']) && !empty($this->formats)) {
$requirements['_format'] = implode('|', array_keys($this->formats));
}
}
}
/**
* Checks whether provided method is readable.
*
* @param \ReflectionMethod $method
*
* @return bool
*/
private function isMethodReadable(\ReflectionMethod $method)
{
// if method starts with _ - skip
if ('_' === substr($method->getName(), 0, 1)) {
return false;
}
$hasNoRouteMethod = (bool) $this->readMethodAnnotation($method, 'NoRoute');
$hasNoRouteClass = (bool) $this->readClassAnnotation($method->getDeclaringClass(), 'NoRoute');
$hasNoRoute = $hasNoRouteMethod || $hasNoRouteClass;
// since NoRoute extends Route we need to exclude all the method NoRoute annotations
$hasRoute = (bool) $this->readMethodAnnotation($method, 'Route') && !$hasNoRouteMethod;
// if method has NoRoute annotation and does not have Route annotation - skip
if ($hasNoRoute && !$hasRoute) {
return false;
}
return true;
}
/**
* Returns HTTP method and resources list from method signature.
*
* @param \ReflectionMethod $method
* @param string[] $resource
*
* @return bool|array
*/
private function getHttpMethodAndResourcesFromMethod(\ReflectionMethod $method, $resource)
{
// if method doesn't match regex - skip
if (!preg_match('/([a-z][_a-z0-9]+)(.*)Action/', $method->getName(), $matches)) {
return false;
}
$httpMethod = strtolower($matches[1]);
$resources = preg_split(
'/([A-Z][^A-Z]*)/', $matches[2], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
);
$isCollection = false;
$isInflectable = true;
if (0 === strpos($httpMethod, self::COLLECTION_ROUTE_PREFIX)
&& in_array(substr($httpMethod, 1), $this->availableHTTPMethods)
) {
$isCollection = true;
$httpMethod = substr($httpMethod, 1);
} elseif ('options' === $httpMethod) {
$isCollection = true;
}
if ($isCollection && !empty($resource)) {
$resourcePluralized = $this->generateResourceName(end($resource));
$isInflectable = ($resourcePluralized != $resource[count($resource) - 1]);
$resource[count($resource) - 1] = $resourcePluralized;
}
$resources = array_merge($resource, $resources);
return [$httpMethod, $resources, $isCollection, $isInflectable];
}
/**
* Returns readable arguments from method.
*
* @param \ReflectionMethod $method
*
* @return \ReflectionParameter[]
*/
private function getMethodArguments(\ReflectionMethod $method)
{
// ignore all query params
$params = $this->paramReader->getParamsFromMethod($method);
// ignore several type hinted arguments
$ignoreClasses = [
\Symfony\Component\HttpFoundation\Request::class,
\FOS\RestBundle\Request\ParamFetcherInterface::class,
\Symfony\Component\Validator\ConstraintViolationListInterface::class,
\Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter::class,
MessageInterface::class,
];
$arguments = [];
foreach ($method->getParameters() as $argument) {
if (isset($params[$argument->getName()])) {
continue;
}
$argumentClass = $argument->getClass();
if ($argumentClass) {
$className = $argumentClass->getName();
foreach ($ignoreClasses as $class) {
if ($className === $class || is_subclass_of($className, $class)) {
continue 2;
}
}
}
$arguments[] = $argument;
}
return $arguments;
}
/**
* Generates final resource name.
*
* @param string|bool $resource
*
* @return string
*/
private function generateResourceName($resource)
{
if (false === $this->pluralize) {
return $resource;
}
return $this->inflector->pluralize($resource);
}
/**
* Generates route name from resources list.
*
* @param string[] $resources
*
* @return string
*/
private function generateRouteName(array $resources)
{
$routeName = '';
foreach ($resources as $resource) {
if (null !== $resource) {
$routeName .= '_'.basename($resource);
}
}
return $routeName;
}
/**
* Generates URL parts for route from resources list.
*
* @param string[] $resources
* @param \ReflectionParameter[] $arguments
* @param string $httpMethod
*
* @return array
*/
private function generateUrlParts(array $resources, array $arguments, $httpMethod)
{
$urlParts = [];
foreach ($resources as $i => $resource) {
// if we already added all parent routes paths to URL & we have
// prefix - add it
if (!empty($this->routePrefix) && $i === count($this->parents)) {
$urlParts[] = $this->routePrefix;
}
// if we have argument for current resource, then it's object.
// otherwise - it's collection
if (isset($arguments[$i])) {
if (null !== $resource) {
$urlParts[] =
strtolower($this->generateResourceName($resource))
.'/{'.$arguments[$i]->getName().'}';
} else {
$urlParts[] = '{'.$arguments[$i]->getName().'}';
}
} elseif (null !== $resource) {
if ((0 === count($arguments) && !in_array($httpMethod, $this->availableHTTPMethods))
|| 'new' === $httpMethod
|| 'post' === $httpMethod
) {
$urlParts[] = $this->generateResourceName(strtolower($resource));
} else {
$urlParts[] = strtolower($resource);
}
}
}
return $urlParts;
}
/**
* Returns custom HTTP method for provided list of resources, arguments, method.
*
* @param string $httpMethod current HTTP method
* @param string[] $resources resources list
* @param \ReflectionParameter[] $arguments list of method arguments
*
* @return string
*/
private function getCustomHttpMethod($httpMethod, array $resources, array $arguments)
{
if (in_array($httpMethod, $this->availableConventionalActions)) {
// allow hypertext as the engine of application state
// through conventional GET actions
return 'get';
}
if (count($arguments) < count($resources)) {
// resource collection
return 'get';
}
// custom object
return 'patch';
}
/**
* Returns first route annotation for method.
*
* @param \ReflectionMethod $reflectionMethod
*
* @return RouteAnnotation[]
*/
private function readRouteAnnotation(\ReflectionMethod $reflectionMethod)
{
$annotations = [];
if ($newAnnotations = $this->readMethodAnnotations($reflectionMethod, 'Route')) {
$annotations = array_merge($annotations, $newAnnotations);
}
return $annotations;
}
/**
* Reads class annotations.
*
* @param \ReflectionClass $reflectionClass
* @param string $annotationName
*
* @return RouteAnnotation|null
*/
private function readClassAnnotation(\ReflectionClass $reflectionClass, $annotationName)
{
$annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, $annotationClass)) {
return $annotation;
}
}
/**
* Reads method annotations.
*
* @param \ReflectionMethod $reflectionMethod
* @param string $annotationName
*
* @return RouteAnnotation|null
*/
private function readMethodAnnotation(\ReflectionMethod $reflectionMethod, $annotationName)
{
$annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
if ($annotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, $annotationClass)) {
return $annotation;
}
}
/**
* Reads method annotations.
*
* @param \ReflectionMethod $reflectionMethod
* @param string $annotationName
*
* @return RouteAnnotation[]
*/
private function readMethodAnnotations(\ReflectionMethod $reflectionMethod, $annotationName)
{
$annotations = [];
$annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
if ($annotations_new = $this->annotationReader->getMethodAnnotations($reflectionMethod)) {
foreach ($annotations_new as $annotation) {
if ($annotation instanceof $annotationClass) {
$annotations[] = $annotation;
}
}
}
return $annotations;
}
/**
* @param RestRouteCollection $collection
* @param string $routeName
* @param Route $route
* @param bool $isCollection
* @param bool $isInflectable
* @param RouteAnnotation $annotation
*/
private function addRoute(RestRouteCollection $collection, $routeName, $route, $isCollection, $isInflectable, RouteAnnotation $annotation = null)
{
if ($annotation && null !== $annotation->getName()) {
$options = $annotation->getOptions();
if (isset($options['method_prefix']) && false === $options['method_prefix']) {
$routeName = $annotation->getName();
} else {
$routeName = $routeName.$annotation->getName();
}
}
$fullRouteName = $this->namePrefix.$routeName;
if ($isCollection && !$isInflectable) {
$collection->add($this->namePrefix.self::COLLECTION_ROUTE_PREFIX.$routeName, $route);
if (!$collection->get($fullRouteName)) {
$collection->add($fullRouteName, clone $route);
}
} else {
$collection->add($fullRouteName, $route);
}
}
}
Routing/Loader/Reader/RestControllerReader.php 0000666 00000007444 13052362416 0015437 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Routing\Loader\Reader;
use Doctrine\Common\Annotations\Reader;
use FOS\RestBundle\Controller\Annotations;
use FOS\RestBundle\Routing\ClassResourceInterface;
use FOS\RestBundle\Routing\RestRouteCollection;
use Symfony\Component\Config\Resource\FileResource;
/**
* REST controller reader.
*
* @author Konstantin Kudryashov
*/
class RestControllerReader
{
private $actionReader;
private $annotationReader;
/**
* Initializes controller reader.
*
* @param RestActionReader $actionReader action reader
* @param Reader $annotationReader annotation reader
*/
public function __construct(RestActionReader $actionReader, Reader $annotationReader)
{
$this->actionReader = $actionReader;
$this->annotationReader = $annotationReader;
}
/**
* Returns action reader.
*
* @return RestActionReader
*/
public function getActionReader()
{
return $this->actionReader;
}
/**
* Reads controller routes.
*
* @param \ReflectionClass $reflectionClass
*
* @throws \InvalidArgumentException
*
* @return RestRouteCollection
*/
public function read(\ReflectionClass $reflectionClass)
{
$collection = new RestRouteCollection();
$collection->addResource(new FileResource($reflectionClass->getFileName()));
// read prefix annotation
if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, Annotations\Prefix::class)) {
$this->actionReader->setRoutePrefix($annotation->value);
}
// read name-prefix annotation
if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, Annotations\NamePrefix::class)) {
$this->actionReader->setNamePrefix($annotation->value);
}
// read version annotation
if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, Annotations\Version::class)) {
$this->actionReader->setVersions($annotation->value);
}
$resource = [];
// read route-resource annotation
if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, Annotations\RouteResource::class)) {
$resource = explode('_', $annotation->resource);
$this->actionReader->setPluralize($annotation->pluralize);
} elseif ($reflectionClass->implementsInterface(ClassResourceInterface::class)) {
$resource = preg_split(
'/([A-Z][^A-Z]*)Controller/', $reflectionClass->getShortName(), -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
);
if (empty($resource)) {
throw new \InvalidArgumentException("Controller '{$reflectionClass->name}' does not identify a resource");
}
}
// trim '/' at the start of the prefix
if ('/' === substr($prefix = $this->actionReader->getRoutePrefix(), 0, 1)) {
$this->actionReader->setRoutePrefix(substr($prefix, 1));
}
// read action routes into collection
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
$this->actionReader->read($collection, $method, $resource);
}
$this->actionReader->setRoutePrefix(null);
$this->actionReader->setNamePrefix(null);
$this->actionReader->setVersions(null);
$this->actionReader->setPluralize(null);
$this->actionReader->setParents([]);
return $collection;
}
}
Routing/Loader/RestRouteLoader.php 0000666 00000011265 13052362416 0013210 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Routing\Loader;
use FOS\RestBundle\Routing\Loader\Reader\RestControllerReader;
use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* RestRouteLoader REST-enabled controller router loader.
*
* @author Konstantin Kudryashov
* @author Bulat Shakirzyanov
*/
class RestRouteLoader extends Loader
{
protected $container;
protected $controllerParser;
protected $controllerReader;
protected $defaultFormat;
protected $locator;
/**
* Initializes loader.
*
* @param ContainerInterface $container
* @param FileLocatorInterface $locator
* @param ControllerNameParser $controllerParser
* @param RestControllerReader $controllerReader
* @param string $defaultFormat
*/
public function __construct(
ContainerInterface $container,
FileLocatorInterface $locator,
ControllerNameParser $controllerParser,
RestControllerReader $controllerReader, $defaultFormat = 'html'
) {
$this->container = $container;
$this->locator = $locator;
$this->controllerParser = $controllerParser;
$this->controllerReader = $controllerReader;
$this->defaultFormat = $defaultFormat;
}
/**
* Returns controller reader.
*
* @return RestControllerReader
*/
public function getControllerReader()
{
return $this->controllerReader;
}
/**
* {@inheritdoc}
*/
public function load($controller, $type = null)
{
list($prefix, $class) = $this->getControllerLocator($controller);
$collection = $this->controllerReader->read(new \ReflectionClass($class));
$collection->prependRouteControllersWithPrefix($prefix);
$collection->setDefaultFormat($this->defaultFormat);
return $collection;
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
return is_string($resource)
&& 'rest' === $type
&& !in_array(pathinfo($resource, PATHINFO_EXTENSION), ['xml', 'yml']
);
}
/**
* Returns controller locator by it's id.
*
* @param string $controller
*
* @throws \InvalidArgumentException
*
* @return array
*/
private function getControllerLocator($controller)
{
$class = null;
$prefix = null;
if (0 === strpos($controller, '@')) {
$file = $this->locator->locate($controller);
$controllerClass = ClassUtils::findClassInFile($file);
if (false === $controllerClass) {
throw new \InvalidArgumentException(sprintf('Can\'t find class for controller "%s"', $controller));
}
$controller = $controllerClass;
}
if ($this->container->has($controller)) {
// service_id
$prefix = $controller.':';
$useScope = method_exists($this->container, 'enterScope') && $this->container->hasScope('request');
if ($useScope) {
$this->container->enterScope('request');
$this->container->set('request', new Request());
}
$class = get_class($this->container->get($controller));
if ($useScope) {
$this->container->leaveScope('request');
}
} elseif (class_exists($controller)) {
// full class name
$class = $controller;
$prefix = $class.'::';
} elseif (false !== strpos($controller, ':')) {
// bundle:controller notation
try {
$notation = $this->controllerParser->parse($controller.':method');
list($class) = explode('::', $notation);
$prefix = $class.'::';
} catch (\Exception $e) {
throw new \InvalidArgumentException(
sprintf('Can\'t locate "%s" controller.', $controller)
);
}
}
if (empty($class)) {
throw new \InvalidArgumentException(sprintf(
'Class could not be determined for Controller identified by "%s".', $controller
));
}
return [$prefix, $class];
}
}
Routing/Loader/RestRouteProcessor.php 0000666 00000004003 13052362416 0013751 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Routing\Loader;
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Routing\RouteCollection;
/**
* Processes resource in provided loader.
*
* @author Donald Tyler
* @author Konstantin Kudryashov
*/
class RestRouteProcessor
{
/**
* Import & return routes collection from a resource.
*
* @param LoaderInterface $loader The Loader
* @param mixed $resource A Resource
* @param array $parents Array of parent resources names
* @param string $routePrefix Current routes prefix
* @param string $namePrefix Routes names prefix
* @param string $type The resource type
* @param string $currentDir Current directory of the loader
*
* @return RouteCollection A RouteCollection instance
*/
public function importResource(
LoaderInterface $loader,
$resource,
array $parents = [],
$routePrefix = null,
$namePrefix = null,
$type = null,
$currentDir = null
) {
$loader = $loader->resolve($resource, $type);
if ($loader instanceof FileLoader && null !== $currentDir) {
$resource = $loader->getLocator()->locate($resource, $currentDir);
} elseif ($loader instanceof RestRouteLoader) {
$loader->getControllerReader()->getActionReader()->setParents($parents);
$loader->getControllerReader()->getActionReader()->setRoutePrefix($routePrefix);
$loader->getControllerReader()->getActionReader()->setNamePrefix($namePrefix);
}
return $loader->load($resource, $type);
}
}
Routing/Loader/RestXmlCollectionLoader.php 0000666 00000022525 13052362416 0014667 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Routing\Loader;
use FOS\RestBundle\Routing\RestRouteCollection;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Routing\Loader\XmlFileLoader;
use Symfony\Component\Routing\RouteCollection;
/**
* RestXmlCollectionLoader XML file collections loader.
*
* @author Donald Tyler
*/
class RestXmlCollectionLoader extends XmlFileLoader
{
protected $collectionParents = [];
private $processor;
private $includeFormat;
private $formats;
private $defaultFormat;
/**
* Initializes xml loader.
*
* @param FileLocatorInterface $locator
* @param RestRouteProcessor $processor
* @param bool $includeFormat
* @param string[] $formats
* @param string $defaultFormat
*/
public function __construct(
FileLocatorInterface $locator,
RestRouteProcessor $processor,
$includeFormat = true,
array $formats = [],
$defaultFormat = null
) {
parent::__construct($locator);
$this->processor = $processor;
$this->includeFormat = $includeFormat;
$this->formats = $formats;
$this->defaultFormat = $defaultFormat;
}
/**
* {@inheritdoc}
*/
protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file)
{
switch ($node->tagName) {
case 'route':
$this->parseRoute($collection, $node, $path);
break;
case 'import':
$name = (string) $node->getAttribute('id');
$resource = (string) $node->getAttribute('resource');
$prefix = (string) $node->getAttribute('prefix');
$namePrefix = (string) $node->getAttribute('name-prefix');
$parent = (string) $node->getAttribute('parent');
$type = (string) $node->getAttribute('type');
$currentDir = dirname($path);
$parents = [];
if (!empty($parent)) {
if (!isset($this->collectionParents[$parent])) {
throw new \InvalidArgumentException(sprintf('Cannot find parent resource with name %s', $parent));
}
$parents = $this->collectionParents[$parent];
}
$imported = $this->processor->importResource($this, $resource, $parents, $prefix, $namePrefix, $type, $currentDir);
if (!empty($name) && $imported instanceof RestRouteCollection) {
$parents[] = (!empty($prefix) ? $prefix.'/' : '').$imported->getSingularName();
$prefix = null;
$this->collectionParents[$name] = $parents;
}
$imported->addPrefix($prefix);
$collection->addCollection($imported);
break;
default:
throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName));
}
}
/**
* {@inheritdoc}
*/
protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path)
{
if ($this->includeFormat) {
$path = $node->getAttribute('path');
// append format placeholder if not present
if (false === strpos($path, '{_format}')) {
$node->setAttribute('path', $path.'.{_format}');
}
// set format requirement if configured globally
$requirements = $node->getElementsByTagNameNS(self::NAMESPACE_URI, 'requirement');
$format = null;
for ($i = 0; $i < $requirements->length; ++$i) {
$item = $requirements->item($i);
if ($item instanceof \DOMElement && $item->hasAttribute('_format')) {
$format = $item->getAttribute('_format');
break;
}
}
if (null === $format && !empty($this->formats)) {
$requirement = $node->ownerDocument->createElementNs(
self::NAMESPACE_URI,
'requirement',
implode('|', array_keys($this->formats))
);
$requirement->setAttribute('key', '_format');
$node->appendChild($requirement);
}
}
// set the default format if configured
if (null !== $this->defaultFormat) {
$defaultFormatNode = $node->ownerDocument->createElementNS(
self::NAMESPACE_URI,
'default',
$this->defaultFormat
);
$defaultFormatNode->setAttribute('key', '_format');
$node->appendChild($defaultFormatNode);
}
$options = $this->getOptions($node);
foreach ($options as $option) {
$node->appendChild($option);
}
$length = $node->childNodes->length;
for ($i = 0; $i < $length; ++$i) {
$loopNode = $node->childNodes->item($i);
if ($loopNode->nodeType === XML_TEXT_NODE) {
continue;
}
$newNode = $node->ownerDocument->createElementNS(
self::NAMESPACE_URI,
$loopNode->nodeName,
$loopNode->nodeValue
);
foreach ($loopNode->attributes as $value) {
$newNode->setAttribute($value->name, $value->value);
}
$node->appendChild($newNode);
}
parent::parseRoute($collection, $node, $path);
}
private function getOptions(\DOMElement $node)
{
$options = [];
foreach ($node->childNodes as $child) {
if ($child instanceof \DOMElement && $child->tagName === 'option') {
$option = $node->ownerDocument->createElementNs(
self::NAMESPACE_URI,
'option',
$child->nodeValue
);
$option->setAttribute('key', $child->getAttribute('key'));
$options[] = $option;
}
}
return $options;
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
return is_string($resource) &&
'xml' === pathinfo($resource, PATHINFO_EXTENSION) &&
'rest' === $type;
}
/**
* @param \DOMDocument $dom
*
* @throws \InvalidArgumentException When xml doesn't validate its xsd schema
*/
protected function validate(\DOMDocument $dom)
{
$restRoutinglocation = realpath(__DIR__.'/../../Resources/config/schema/routing/rest_routing-1.0.xsd');
$restRoutinglocation = rawurlencode(str_replace('\\', '/', $restRoutinglocation));
$routinglocation = realpath(__DIR__.'/../../Resources/config/schema/routing-1.0.xsd');
$routinglocation = rawurlencode(str_replace('\\', '/', $routinglocation));
$source = <<
EOF;
$current = libxml_use_internal_errors(true);
libxml_clear_errors();
if (!$dom->schemaValidateSource($source)) {
throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors_($current)));
}
libxml_use_internal_errors($current);
}
/**
* {@inheritdoc}
*
* @internal
*/
protected function loadFile($file)
{
if (class_exists('Symfony\Component\Config\Util\XmlUtils')) {
$dom = XmlUtils::loadFile($file);
$this->validate($dom);
return $dom;
}
return parent::loadFile($file);
}
/**
* Retrieves libxml errors and clears them.
*
* Note: The underscore postfix on the method name is to ensure compatibility with versions
* before 2.0.16 while working around a bug in PHP https://bugs.php.net/bug.php?id=62956
*
* @param bool $internalErrors The previous state of internal errors to reset it
*
* @return array An array of libxml error strings
*/
private function getXmlErrors_($internalErrors)
{
$errors = [];
foreach (libxml_get_errors() as $error) {
$errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
LIBXML_ERR_WARNING === $error->level ? 'WARNING' : 'ERROR',
$error->code,
trim($error->message),
$error->file ? $error->file : 'n/a',
$error->line,
$error->column
);
}
libxml_clear_errors();
libxml_use_internal_errors($internalErrors);
return $errors;
}
}
Routing/Loader/RestYamlCollectionLoader.php 0000666 00000015141 13052362416 0015025 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Routing\Loader;
use FOS\RestBundle\Routing\RestRouteCollection;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Routing\Loader\YamlFileLoader;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
/**
* RestYamlCollectionLoader YAML file collections loader.
*/
class RestYamlCollectionLoader extends YamlFileLoader
{
protected $collectionParents = [];
private $processor;
private $includeFormat;
private $formats;
private $defaultFormat;
/**
* Initializes yaml loader.
*
* @param FileLocatorInterface $locator
* @param RestRouteProcessor $processor
* @param bool $includeFormat
* @param string[] $formats
* @param string $defaultFormat
*/
public function __construct(
FileLocatorInterface $locator,
RestRouteProcessor $processor,
$includeFormat = true,
array $formats = [],
$defaultFormat = null
) {
parent::__construct($locator);
$this->processor = $processor;
$this->includeFormat = $includeFormat;
$this->formats = $formats;
$this->defaultFormat = $defaultFormat;
}
/**
* {@inheritdoc}
*/
public function load($file, $type = null)
{
$path = $this->locator->locate($file);
try {
$config = Yaml::parse(file_get_contents($path));
} catch (ParseException $e) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e);
}
$collection = new RouteCollection();
$collection->addResource(new FileResource($path));
// empty file
if (null === $config) {
return $collection;
}
// not an array
if (!is_array($config)) {
throw new \InvalidArgumentException(sprintf('The file "%s" must contain a Yaml mapping (an array).', $path));
}
// process routes and imports
foreach ($config as $name => $config) {
if (isset($config['resource'])) {
$resource = $config['resource'];
$prefix = isset($config['prefix']) ? $config['prefix'] : null;
$namePrefix = isset($config['name_prefix']) ? $config['name_prefix'] : null;
$parent = isset($config['parent']) ? $config['parent'] : null;
$type = isset($config['type']) ? $config['type'] : null;
$requirements = isset($config['requirements']) ? $config['requirements'] : [];
$defaults = isset($config['defaults']) ? $config['defaults'] : [];
$options = isset($config['options']) ? $config['options'] : [];
$currentDir = dirname($path);
$parents = [];
if (!empty($parent)) {
if (!isset($this->collectionParents[$parent])) {
throw new \InvalidArgumentException(sprintf('Cannot find parent resource with name %s', $parent));
}
$parents = $this->collectionParents[$parent];
}
$imported = $this->processor->importResource($this, $resource, $parents, $prefix, $namePrefix, $type, $currentDir);
if ($imported instanceof RestRouteCollection) {
$parents[] = ($prefix ? $prefix.'/' : '').$imported->getSingularName();
$prefix = null;
$namePrefix = null;
$this->collectionParents[$name] = $parents;
}
$imported->addRequirements($requirements);
$imported->addDefaults($defaults);
$imported->addOptions($options);
$imported->addPrefix($prefix);
// Add name prefix from parent config files
$imported = $this->addParentNamePrefix($imported, $namePrefix);
$collection->addCollection($imported);
} elseif (isset($config['pattern']) || isset($config['path'])) {
// the YamlFileLoader of the Routing component only checks for
// the path option
if (!isset($config['path'])) {
$config['path'] = $config['pattern'];
}
if ($this->includeFormat) {
// append format placeholder if not present
if (false === strpos($config['path'], '{_format}')) {
$config['path'] .= '.{_format}';
}
// set format requirement if configured globally
if (!isset($config['requirements']['_format']) && !empty($this->formats)) {
$config['requirements']['_format'] = implode('|', array_keys($this->formats));
}
}
// set the default format if configured
if (null !== $this->defaultFormat) {
$config['defaults']['_format'] = $this->defaultFormat;
}
$this->parseRoute($collection, $name, $config, $path);
} else {
throw new \InvalidArgumentException(sprintf('Unable to parse the "%s" route.', $name));
}
}
return $collection;
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
return is_string($resource) &&
'yml' === pathinfo($resource, PATHINFO_EXTENSION) &&
'rest' === $type;
}
/**
* Adds a name prefix to the route name of all collection routes.
*
* @param RouteCollection $collection Route collection
* @param array $namePrefix NamePrefix to add in each route name of the route collection
*
* @return RouteCollection
*/
public function addParentNamePrefix(RouteCollection $collection, $namePrefix)
{
if (!isset($namePrefix) || ($namePrefix = trim($namePrefix)) === '') {
return $collection;
}
$iterator = $collection->getIterator();
foreach ($iterator as $key1 => $route1) {
$collection->add($namePrefix.$key1, $route1);
$collection->remove($key1);
}
return $collection;
}
}
Routing/RestRouteCollection.php 0000666 00000004356 13052362416 0012672 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Routing;
use Symfony\Component\Routing\RouteCollection;
/**
* Restful route collection.
*
* @author Konstantin Kudryashov
*/
class RestRouteCollection extends RouteCollection
{
private $singularName;
/**
* Sets collection singular name.
*
* @param string $name Singular name
*/
public function setSingularName($name)
{
$this->singularName = $name;
}
/**
* Returns collection singular name.
*
* @return string
*/
public function getSingularName()
{
return $this->singularName;
}
/**
* Adds controller prefix to all collection routes.
*
* @param string $prefix
*/
public function prependRouteControllersWithPrefix($prefix)
{
foreach (parent::all() as $route) {
$route->setDefault('_controller', $prefix.$route->getDefault('_controller'));
}
}
/**
* Sets default format of routes.
*
* @param string $format
*/
public function setDefaultFormat($format)
{
foreach (parent::all() as $route) {
// Set default format only if not set already (could be defined in annotation)
if (!$route->getDefault('_format')) {
$route->setDefault('_format', $format);
}
}
}
/**
* Returns routes sorted by custom HTTP methods first.
*
* @return array
*/
public function all()
{
$regex = '/
(_|^)
(get|post|put|delete|patch|head|options|mkcol|propfind|proppatch|lock|unlock|move|copy|link|unlink)_ # allowed http methods
/i';
$routes = parent::all();
$customMethodRoutes = [];
foreach ($routes as $routeName => $route) {
if (!preg_match($regex, $routeName)) {
$customMethodRoutes[$routeName] = $route;
unset($routes[$routeName]);
}
}
return $customMethodRoutes + $routes;
}
}
Serializer/JMSHandlerRegistry.php 0000666 00000003026 13052362416 0013055 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Serializer;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\Handler\HandlerRegistryInterface;
/**
* Search in the class parents to find an adapted handler.
*
* @author Ener-Getick
*
* @internal do not depend on this class directly
*/
class JMSHandlerRegistry implements HandlerRegistryInterface
{
private $registry;
public function __construct(HandlerRegistryInterface $registry)
{
$this->registry = $registry;
}
/**
* {@inheritdoc}
*/
public function registerSubscribingHandler(SubscribingHandlerInterface $handler)
{
return $this->registry->registerSubscribingHandler($handler);
}
/**
* {@inheritdoc}
*/
public function registerHandler($direction, $typeName, $format, $handler)
{
return $this->registry->registerHandler($direction, $typeName, $format, $handler);
}
/**
* {@inheritdoc}
*/
public function getHandler($direction, $typeName, $format)
{
do {
$handler = $this->registry->getHandler($direction, $typeName, $format);
if (null !== $handler) {
return $handler;
}
} while ($typeName = get_parent_class($typeName));
}
}
Serializer/JMSSerializerAdapter.php 0000666 00000005467 13052362416 0013374 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Serializer;
use FOS\RestBundle\Context\Context;
use JMS\Serializer\Context as JMSContext;
use JMS\Serializer\DeserializationContext as JMSDeserializationContext;
use JMS\Serializer\SerializationContext as JMSSerializationContext;
use JMS\Serializer\SerializerInterface;
/**
* Adapter to plug the JMS serializer into the FOSRestBundle Serializer API.
*
* @author Christian Flothmann
*/
class JMSSerializerAdapter implements Serializer
{
/**
* @internal
*/
const SERIALIZATION = 0;
/**
* @internal
*/
const DESERIALIZATION = 1;
private $serializer;
public function __construct(SerializerInterface $serializer)
{
$this->serializer = $serializer;
}
/**
* {@inheritdoc}
*/
public function serialize($data, $format, Context $context)
{
$context = $this->convertContext($context, self::SERIALIZATION);
return $this->serializer->serialize($data, $format, $context);
}
/**
* {@inheritdoc}
*/
public function deserialize($data, $type, $format, Context $context)
{
$context = $this->convertContext($context, self::DESERIALIZATION);
return $this->serializer->deserialize($data, $type, $format, $context);
}
/**
* @param Context $context
* @param int $direction {@see self} constants
*
* @return JMSContext
*/
private function convertContext(Context $context, $direction)
{
if ($direction === self::SERIALIZATION) {
$jmsContext = JMSSerializationContext::create();
} else {
$jmsContext = JMSDeserializationContext::create();
$maxDepth = $context->getMaxDepth();
if (null !== $maxDepth) {
for ($i = 0; $i < $maxDepth; ++$i) {
$jmsContext->increaseDepth();
}
}
}
foreach ($context->getAttributes() as $key => $value) {
$jmsContext->attributes->set($key, $value);
}
if (null !== $context->getVersion()) {
$jmsContext->setVersion($context->getVersion());
}
$groups = $context->getGroups();
if (!empty($groups)) {
$jmsContext->setGroups($context->getGroups());
}
if (null !== $context->getMaxDepth()) {
$jmsContext->enableMaxDepthChecks();
}
if (null !== $context->getSerializeNull()) {
$jmsContext->setSerializeNull($context->getSerializeNull());
}
return $jmsContext;
}
}
Serializer/Normalizer/AbstractExceptionNormalizer.php 0000666 00000002661 13052362416 0017210 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Serializer\Normalizer;
use FOS\RestBundle\Util\ExceptionValueMap;
use Symfony\Component\HttpFoundation\Response;
/**
* @author Ener-Getick
*
* @internal do not use this class in your code
*/
class AbstractExceptionNormalizer
{
/**
* @var ExceptionValueMap
*/
private $messagesMap;
/**
* @var bool
*/
private $debug;
/**
* @param array $messagesMap
* @param bool $debug
*/
public function __construct(ExceptionValueMap $messagesMap, $debug)
{
$this->messagesMap = $messagesMap;
$this->debug = $debug;
}
/**
* Extracts the exception message.
*
* @param \Exception $exception
* @param int|null $statusCode
*
* @return string
*/
protected function getExceptionMessage(\Exception $exception, $statusCode = null)
{
$showMessage = $this->messagesMap->resolveException($exception);
if ($showMessage || $this->debug) {
return $exception->getMessage();
}
return array_key_exists($statusCode, Response::$statusTexts) ? Response::$statusTexts[$statusCode] : 'error';
}
}
Serializer/Normalizer/ExceptionHandler.php 0000666 00000006337 13052362416 0014763 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Serializer\Normalizer;
use JMS\Serializer\Context;
use JMS\Serializer\GraphNavigator;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\JsonSerializationVisitor;
use JMS\Serializer\XmlSerializationVisitor;
class ExceptionHandler extends AbstractExceptionNormalizer implements SubscribingHandlerInterface
{
/**
* @return array
*/
public static function getSubscribingMethods()
{
return [
[
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => 'json',
'type' => \Exception::class,
'method' => 'serializeToJson',
],
[
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => 'xml',
'type' => \Exception::class,
'method' => 'serializeToXml',
],
];
}
/**
* @param JsonSerializationVisitor $visitor
* @param Exception $exception
* @param array $type
* @param Context $context
*
* @return array
*/
public function serializeToJson(
JsonSerializationVisitor $visitor,
\Exception $exception,
array $type,
Context $context
) {
$data = $this->convertToArray($exception, $context);
return $visitor->visitArray($data, $type, $context);
}
/**
* @param XmlSerializationVisitor $visitor
* @param Exception $exception
* @param array $type
* @param Context $context
*/
public function serializeToXml(
XmlSerializationVisitor $visitor,
\Exception $exception,
array $type,
Context $context
) {
$data = $this->convertToArray($exception, $context);
if (null === $visitor->document) {
$visitor->document = $visitor->createDocument(null, null, true);
}
foreach ($data as $key => $value) {
$entryNode = $visitor->document->createElement($key);
$visitor->getCurrentNode()->appendChild($entryNode);
$visitor->setCurrentNode($entryNode);
$node = $context->getNavigator()->accept($value, null, $context);
if (null !== $node) {
$visitor->getCurrentNode()->appendChild($node);
}
$visitor->revertCurrentNode();
}
}
/**
* @param \Exception $exception
*
* @return array
*/
protected function convertToArray(\Exception $exception, Context $context)
{
$data = [];
$templateData = $context->attributes->get('template_data');
if ($templateData->isDefined()) {
$data['code'] = $statusCode = $templateData->get()['status_code'];
}
$data['message'] = $this->getExceptionMessage($exception, isset($statusCode) ? $statusCode : null);
return $data;
}
}
Serializer/Normalizer/ExceptionNormalizer.php 0000666 00000002201 13052362416 0015512 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Serializer\Normalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Normalizes Exception instances.
*
* @author Ener-Getick
*/
class ExceptionNormalizer extends AbstractExceptionNormalizer implements NormalizerInterface
{
/**
* {@inheritdoc}
*/
public function normalize($object, $format = null, array $context = [])
{
$data = [];
if (isset($context['template_data']['status_code'])) {
$data['code'] = $statusCode = $context['template_data']['status_code'];
}
$data['message'] = $this->getExceptionMessage($object, isset($statusCode) ? $statusCode : null);
return $data;
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof \Exception;
}
}
Serializer/Normalizer/FormErrorHandler.php 0000666 00000007347 13052362416 0014744 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Serializer\Normalizer;
use JMS\Serializer\Context;
use JMS\Serializer\Handler\FormErrorHandler as JMSFormErrorHandler;
use JMS\Serializer\JsonSerializationVisitor;
use JMS\Serializer\XmlSerializationVisitor;
use Symfony\Component\Form\Form;
use JMS\Serializer\YamlSerializationVisitor;
use Symfony\Component\Translation\TranslatorInterface;
/**
* Extend the JMS FormErrorHandler to include more informations when using the ViewHandler.
*/
class FormErrorHandler extends JMSFormErrorHandler
{
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
parent::__construct($translator);
}
public function serializeFormToXml(XmlSerializationVisitor $visitor, Form $form, array $type, Context $context = null)
{
if ($context) {
$statusCode = $context->attributes->get('status_code');
if ($statusCode->isDefined()) {
if (null === $visitor->document) {
$visitor->document = $visitor->createDocument(null, null, true);
}
$codeNode = $visitor->document->createElement('code');
$visitor->getCurrentNode()->appendChild($codeNode);
$codeNode->appendChild($context->getNavigator()->accept($statusCode->get(), null, $context));
$messageNode = $visitor->document->createElement('message');
$visitor->getCurrentNode()->appendChild($messageNode);
$messageNode->appendChild($context->getNavigator()->accept('Validation Failed', null, $context));
$errorsNode = $visitor->document->createElement('errors');
$visitor->getCurrentNode()->appendChild($errorsNode);
$visitor->setCurrentNode($errorsNode);
parent::serializeFormToXml($visitor, $form, $type);
$visitor->revertCurrentNode();
return;
}
}
return parent::serializeFormToXml($visitor, $form, $type);
}
public function serializeFormToJson(JsonSerializationVisitor $visitor, Form $form, array $type, Context $context = null)
{
$isRoot = null === $visitor->getRoot();
$result = $this->adaptFormArray(parent::serializeFormToJson($visitor, $form, $type), $context);
if ($isRoot) {
$visitor->setRoot($result);
}
return $result;
}
public function serializeFormToYml(YamlSerializationVisitor $visitor, Form $form, array $type, Context $context = null)
{
$isRoot = null === $visitor->getRoot();
$result = $this->adaptFormArray(parent::serializeFormToYml($visitor, $form, $type), $context);
if ($isRoot) {
$visitor->setRoot($result);
}
return $result;
}
private function adaptFormArray(\ArrayObject $serializedForm, Context $context = null)
{
$statusCode = $this->getStatusCode($context);
if (null !== $statusCode) {
return [
'code' => $statusCode,
'message' => 'Validation Failed',
'errors' => $serializedForm,
];
}
return $serializedForm;
}
private function getStatusCode(Context $context = null)
{
if (null === $context) {
return;
}
$statusCode = $context->attributes->get('status_code');
if ($statusCode->isDefined()) {
return $statusCode->get();
}
}
}
Serializer/Normalizer/FormErrorNormalizer.php 0000666 00000004642 13052362416 0015504 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Serializer\Normalizer;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* Normalizes invalid Form instances.
*
* @author Ener-Getick
*/
class FormErrorNormalizer implements NormalizerInterface
{
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* {@inheritdoc}
*/
public function normalize($object, $format = null, array $context = [])
{
return [
'code' => isset($context['status_code']) ? $context['status_code'] : null,
'message' => 'Validation Failed',
'errors' => $this->convertFormToArray($object),
];
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof FormInterface && $data->isSubmitted() && !$data->isValid();
}
/**
* This code has been taken from JMSSerializer.
*/
private function convertFormToArray(FormInterface $data)
{
$form = $errors = [];
foreach ($data->getErrors() as $error) {
$errors[] = $this->getErrorMessage($error);
}
if ($errors) {
$form['errors'] = $errors;
}
$children = [];
foreach ($data->all() as $child) {
if ($child instanceof FormInterface) {
$children[$child->getName()] = $this->convertFormToArray($child);
}
}
if ($children) {
$form['children'] = $children;
}
return $form;
}
private function getErrorMessage(FormError $error)
{
if (null !== $error->getMessagePluralization()) {
return $this->translator->transChoice($error->getMessageTemplate(), $error->getMessagePluralization(), $error->getMessageParameters(), 'validators');
}
return $this->translator->trans($error->getMessageTemplate(), $error->getMessageParameters(), 'validators');
}
}
Serializer/Serializer.php 0000666 00000001543 13052362416 0011510 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Serializer;
use FOS\RestBundle\Context\Context;
/**
* @author Christian Flothmann
*/
interface Serializer
{
/**
* @param mixed $data
* @param string $format
* @param Context $context
*
* @return string
*/
public function serialize($data, $format, Context $context);
/**
* @param string $data
* @param string $type
* @param string $format
* @param Context $context
*
* @return mixed
*/
public function deserialize($data, $type, $format, Context $context);
}
Serializer/SymfonySerializerAdapter.php 0000666 00000003365 13052362416 0014402 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Serializer;
use FOS\RestBundle\Context\Context;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Adapter to plug the Symfony serializer into the FOSRestBundle Serializer API.
*
* @author Christian Flothmann
*/
class SymfonySerializerAdapter implements Serializer
{
private $serializer;
public function __construct(SerializerInterface $serializer)
{
$this->serializer = $serializer;
}
/**
* {@inheritdoc}
*/
public function serialize($data, $format, Context $context)
{
$newContext = $this->convertContext($context);
$newContext['serializeNull'] = $context->getSerializeNull();
return $this->serializer->serialize($data, $format, $newContext);
}
/**
* {@inheritdoc}
*/
public function deserialize($data, $type, $format, Context $context)
{
$newContext = $this->convertContext($context);
return $this->serializer->deserialize($data, $type, $format, $newContext);
}
/**
* @param Context $context
*/
private function convertContext(Context $context)
{
$newContext = array();
foreach ($context->getAttributes() as $key => $value) {
$newContext[$key] = $value;
}
$newContext['groups'] = $context->getGroups();
$newContext['version'] = $context->getVersion();
$newContext['maxDepth'] = $context->getMaxDepth();
return $newContext;
}
}
Tests/Context/ContextTest.php 0000666 00000005407 13052362416 0012303 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Context;
use FOS\RestBundle\Context\Context;
/**
* @author Ener-Getick
*/
class ContextTest extends \PHPUnit_Framework_TestCase
{
protected $context;
public function setUp()
{
$this->context = new Context();
}
public function testDefaultValues()
{
$this->assertEquals([], $this->context->getAttributes());
$this->assertEquals([], $this->context->getGroups());
}
public function testAttributes()
{
// Define attributes and check if that's the good return value.
$this->assertEquals($this->context, $this->context->setAttribute('foo', 'bar'));
$this->assertEquals($this->context, $this->context->setAttribute('foobar', 'foo'));
$this->assertTrue($this->context->hasAttribute('foo'));
$this->assertTrue($this->context->hasAttribute('foobar'));
$this->assertFalse($this->context->hasAttribute('bar'));
$this->assertEquals('bar', $this->context->getAttribute('foo'));
$this->assertEquals('foo', $this->context->getAttribute('foobar'));
$this->assertEquals(['foo' => 'bar', 'foobar' => 'foo'], $this->context->getAttributes());
}
public function testGroupAddition()
{
$this->context->addGroups(array('quz', 'foo'));
$this->context->addGroup('foo');
$this->context->addGroup('bar');
$this->assertEquals(['quz', 'foo', 'bar'], $this->context->getGroups());
}
public function testSetGroups()
{
$this->context->setGroups(array('quz', 'foo'));
$this->assertEquals(array('quz', 'foo'), $this->context->getGroups());
$this->context->setGroups(array('foo'));
$this->assertEquals(array('foo'), $this->context->getGroups());
}
public function testAlreadyExistentGroupAddition()
{
$this->context->addGroup('foo');
$this->context->addGroup('foo');
$this->context->addGroup('bar');
$this->assertEquals(array('foo', 'bar'), $this->context->getGroups());
}
public function testVersion()
{
$this->context->setVersion('1.3.2');
$this->assertEquals('1.3.2', $this->context->getVersion());
}
public function testMaxDepth()
{
$this->context->setMaxDepth(10);
$this->assertEquals(10, $this->context->getMaxDepth());
}
public function testSerializeNull()
{
$this->context->setSerializeNull(true);
$this->assertEquals(true, $this->context->getSerializeNull());
}
}
Tests/Controller/Annotations/AbstractParamTest.php 0000666 00000004654 13052362416 0016402 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Controller\Annotations;
use FOS\RestBundle\Controller\Annotations;
use Symfony\Component\Validator\Constraints;
/**
* AbstractParamTest.
*
* @author Ener-Getick
*/
class AbstractParamTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->param = $this->getMockForAbstractClass(Annotations\AbstractParam::class);
}
public function testInterface()
{
$this->assertInstanceOf(Annotations\ParamInterface::class, $this->param);
}
public function testDefaultValues()
{
$this->assertEquals(null, $this->param->name);
$this->assertEquals(null, $this->param->key);
$this->assertEquals(null, $this->param->default);
$this->assertEquals(null, $this->param->description);
$this->assertEquals(false, $this->param->strict);
$this->assertEquals(false, $this->param->nullable);
$this->assertEquals(array(), $this->param->incompatibles);
}
public function testNameGetter()
{
$this->param->name = 'Foo';
$this->assertEquals('Foo', $this->param->getName());
}
public function testDefaultGetter()
{
$this->param->default = 'Bar';
$this->assertEquals('Bar', $this->param->getDefault());
$this->param->default = 'foo %parameter%';
$this->assertEquals('foo %parameter%', $this->param->getDefault());
}
public function testDescriptionGetter()
{
$this->param->description = 'Bar';
$this->assertEquals('Bar', $this->param->getDescription());
}
public function testIncompatiblesGetter()
{
$this->param->incompatibles = array('c', 'd');
$this->assertEquals(array('c', 'd'), $this->param->getIncompatibilities());
}
public function testStrictGetter()
{
$this->param->strict = true;
$this->assertTrue($this->param->isStrict());
}
public function testNotNullConstraint()
{
$this->assertEquals(array(new Constraints\NotNull()), $this->param->getConstraints(''));
$this->param->nullable = true;
$this->assertEquals(array(), $this->param->getConstraints());
}
}
Tests/Controller/Annotations/AbstractScalarParamTest.php 0000666 00000006335 13052362416 0017526 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Controller\Annotations;
use FOS\RestBundle\Validator\Constraints\Regex;
use Symfony\Component\Validator\Constraints;
/**
* AbstractScalarParamTest.
*
* @author Ener-Getick
*/
class AbstractScalarParamTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->param = $this->getMockForAbstractClass('FOS\RestBundle\Controller\Annotations\AbstractScalarParam');
}
public function testInterface()
{
$this->assertInstanceOf('FOS\RestBundle\Controller\Annotations\AbstractParam', $this->param);
}
public function testDefaultValues()
{
$this->assertEquals(null, $this->param->requirements);
$this->assertFalse($this->param->map);
$this->assertTrue($this->param->allowBlank);
}
public function testScalarConstraint()
{
$this->assertEquals(array(
new Constraints\NotNull(),
), $this->param->getConstraints());
}
public function testComplexRequirements()
{
$this->param->requirements = $requirement = $this->getMockBuilder('Symfony\Component\Validator\Constraint')->getMock();
$this->assertEquals(array(
new Constraints\NotNull(),
$requirement,
), $this->param->getConstraints());
}
public function testScalarRequirements()
{
$this->param->name = 'bar';
$this->param->requirements = 'foo %bar% %%';
$this->assertEquals(array(
new Constraints\NotNull(),
new Regex(array(
'pattern' => '#^(?:foo %bar% %%)$#xsu',
'message' => "Parameter 'bar' value, does not match requirements 'foo %bar% %%'",
)),
), $this->param->getConstraints());
}
public function testArrayRequirements()
{
$this->param->requirements = array(
'rule' => 'foo',
'error_message' => 'bar',
);
$this->assertEquals(array(
new Constraints\NotNull(),
new Regex(array(
'pattern' => '#^(?:foo)$#xsu',
'message' => 'bar',
)),
), $this->param->getConstraints());
}
public function testAllowBlank()
{
$this->param->allowBlank = false;
$this->assertEquals(array(
new Constraints\NotNull(),
new Constraints\NotBlank(),
), $this->param->getConstraints());
}
public function testConstraintsTransformWhenParamIsAnArray()
{
$this->param->map = true;
$this->assertEquals(array(new Constraints\All(array(
new Constraints\NotNull(),
))), $this->param->getConstraints());
}
public function testArrayWithNoConstraintsDoesNotCreateInvalidConstraint()
{
$this->param->nullable = true;
$this->param->map = true;
$this->assertEquals(array(new Constraints\All(array(
'constraints' => [],
))), $this->param->getConstraints());
}
}
Tests/Controller/Annotations/FileParamTest.php 0000666 00000004741 13052362416 0015513 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Controller\Annotations;
use Symfony\Component\Validator\Constraints;
/**
* FileParamTest.
*
* @author Ener-Getick
*/
class FileParamTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->param = $this->getMockBuilder('FOS\RestBundle\Controller\Annotations\FileParam')
->setMethods(array('getKey'))
->getMock();
}
public function testInterface()
{
$this->assertInstanceOf('FOS\RestBundle\Controller\Annotations\AbstractParam', $this->param);
}
public function testValueGetter()
{
$this->param
->expects($this->once())
->method('getKey')
->willReturn('foo');
$request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock();
$parameterBag = $this->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag')->getMock();
$parameterBag
->expects($this->once())
->method('get')
->with('foo', 'bar')
->willReturn('foobar');
$request->files = $parameterBag;
$this->assertEquals('foobar', $this->param->getValue($request, 'bar'));
}
public function testComplexRequirements()
{
$this->param->requirements = $requirement = $this->getMockBuilder('Symfony\Component\Validator\Constraint')->getMock();
$this->assertEquals(array(
new Constraints\NotNull(),
$requirement,
new Constraints\File(),
), $this->param->getConstraints());
}
public function testFileRequirements()
{
$this->param->nullable = true;
$this->param->requirements = $requirements = ['mimeTypes' => 'application/json'];
$this->assertEquals(array(
new Constraints\File($requirements),
), $this->param->getConstraints());
}
public function testImageRequirements()
{
$this->param->image = true;
$this->param->requirements = $requirements = ['mimeTypes' => 'image/gif'];
$this->assertEquals(array(
new Constraints\NotNull(),
new Constraints\Image($requirements),
), $this->param->getConstraints());
}
}
Tests/Controller/Annotations/QueryParamTest.php 0000666 00000002755 13052362416 0015744 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Controller\Annotations;
/**
* QueryParamTest.
*
* @author Eduardo Oliveira
* @author Ener-Getick
*/
class QueryParamTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->param = $this->getMockBuilder('FOS\RestBundle\Controller\Annotations\QueryParam')
->setMethods(array('getKey'))
->getMock();
}
public function testInterface()
{
$this->assertInstanceOf('FOS\RestBundle\Controller\Annotations\AbstractScalarParam', $this->param);
}
public function testValueGetter()
{
$this->param
->expects($this->once())
->method('getKey')
->willReturn('foo');
$request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock();
$parameterBag = $this->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag')->getMock();
$parameterBag
->expects($this->once())
->method('get')
->with('foo', 'bar')
->willReturn('foobar');
$request->query = $parameterBag;
$this->assertEquals('foobar', $this->param->getValue($request, 'bar'));
}
}
Tests/Controller/Annotations/RequestParamTest.php 0000666 00000002765 13052362416 0016270 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Controller\Annotations;
/**
* RequestParamTest.
*
* @author Eduardo Oliveira
* @author Ener-Getick
*/
class RequestParamTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->param = $this->getMockBuilder('FOS\RestBundle\Controller\Annotations\RequestParam')
->setMethods(array('getKey'))
->getMock();
}
public function testInterface()
{
$this->assertInstanceOf('FOS\RestBundle\Controller\Annotations\AbstractScalarParam', $this->param);
}
public function testValueGetter()
{
$this->param
->expects($this->once())
->method('getKey')
->willReturn('foo');
$request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock();
$parameterBag = $this->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag')->getMock();
$parameterBag
->expects($this->once())
->method('get')
->with('foo', 'bar')
->willReturn('foobar');
$request->request = $parameterBag;
$this->assertEquals('foobar', $this->param->getValue($request, 'bar'));
}
}
Tests/Decoder/JsonToFormDecoderTest.php 0000666 00000002726 13052362416 0014127 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Decoder;
use FOS\RestBundle\Decoder\JsonToFormDecoder;
/**
* Tests the form-like encoder.
*
* @author Kévin Dunglas
*/
class JsonToFormDecoderTest extends \PHPUnit_Framework_TestCase
{
public function testDecodeWithRemovingFalseData()
{
$data = [
'arrayKey' => [
'falseKey' => false,
'stringKey' => 'foo',
],
'falseKey' => false,
'trueKey' => true,
'intKey' => 69,
'floatKey' => 3.14,
'stringKey' => 'bar',
];
$decoder = new JsonToFormDecoder();
$decoded = $decoder->decode(json_encode($data));
$this->assertTrue(is_array($decoded));
$this->assertTrue(is_array($decoded['arrayKey']));
$this->assertNull($decoded['arrayKey']['falseKey']);
$this->assertEquals('foo', $decoded['arrayKey']['stringKey']);
$this->assertNull($decoded['falseKey']);
$this->assertEquals('1', $decoded['trueKey']);
$this->assertEquals('69', $decoded['intKey']);
$this->assertEquals('3.14', $decoded['floatKey']);
$this->assertEquals('bar', $decoded['stringKey']);
}
}
Tests/DependencyInjection/Compiler/ConfigurationCheckPassTest.php 0000666 00000003226 13052362416 0021317 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\DependencyInjection\Compiler;
use FOS\RestBundle\DependencyInjection\Compiler\ConfigurationCheckPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* ConfigurationCheckPass test.
*
* @author Eriksen Costa
*/
class ConfigurationCheckPassTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException RuntimeException
* @expectedExceptionMessage You need to enable the parameter converter listeners in SensioFrameworkExtraBundle when using the FOSRestBundle RequestBodyParamConverter
*/
public function testShouldThrowRuntimeExceptionWhenBodyConverterIsEnabledButParamConvertersAreNotEnabled()
{
$container = new ContainerBuilder();
$container->register('fos_rest.converter.request_body');
$compiler = new ConfigurationCheckPass();
$compiler->process($container);
}
/**
* @expectedException RuntimeException
* @expectedExceptionMessage SensioFrameworkExtraBundle view annotations
*/
public function testExceptionWhenViewAnnotationsAreNotEnabled()
{
$container = new ContainerBuilder();
$container->register('fos_rest.view_response_listener');
$container->setParameter('kernel.bundles', ['SensioFrameworkExtraBundle' => '']);
$compiler = new ConfigurationCheckPass();
$compiler->process($container);
}
}
Tests/DependencyInjection/Compiler/FormatListenerRulesPassTest.php 0000666 00000007440 13052362416 0021525 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\DependencyInjection\Compiler;
use FOS\RestBundle\DependencyInjection\Compiler\FormatListenerRulesPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Eduardo Gulias Davis
*/
class FormatListenerRulesPassTest extends \PHPUnit_Framework_TestCase
{
public function testRulesAreAddedWhenFormatListenerAndProfilerToolbarAreEnabled()
{
$definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')
->setMethods(array('addMethod'))
->getMock();
$container = $this->getMockBuilder(ContainerBuilder::class)
->setMethods(['hasDefinition', 'getDefinition', 'hasParameter', 'getParameter'])
->getMock();
$container->expects($this->exactly(3))
->method('hasDefinition')
->will($this->returnValue(true));
$container->expects($this->exactly(1))
->method('hasParameter')
->with('web_profiler.debug_toolbar.mode')
->will($this->returnValue(true));
$container->expects($this->exactly(2))
->method('getParameter')
->will($this->onConsecutiveCalls(
2,
[
[
'host' => null,
'methods' => null,
'path' => '^/',
'priorities' => ['html', 'json'],
'fallback_format' => 'html',
'attributes' => [],
'prefer_extension' => true,
],
]
)
);
$container->expects($this->exactly(2))
->method('getDefinition')
->with($this->equalTo('fos_rest.format_negotiator'))
->willReturn($definition);
$compiler = new FormatListenerRulesPass();
$compiler->process($container);
}
public function testNoRulesAreAddedWhenProfilerToolbarAreDisabled()
{
$definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')
->setMethods(array('addMethod'))
->getMock();
$container = $this->getMockBuilder(ContainerBuilder::class)
->setMethods(['hasDefinition', 'getDefinition', 'hasParameter', 'getParameter'])
->getMock();
$container->expects($this->exactly(2))
->method('hasDefinition')
->will($this->returnValue(true));
$container->expects($this->exactly(1))
->method('hasParameter')
->with('web_profiler.debug_toolbar.mode')
->will($this->returnValue(false));
$container->expects($this->once())
->method('getParameter')
->with('fos_rest.format_listener.rules')
->will($this->returnValue(
[
[
'host' => null,
'methods' => null,
'path' => '^/',
'priorities' => ['html', 'json'],
'fallback_format' => 'html',
'attributes' => [],
'prefer_extension' => true,
],
])
);
$container->expects($this->once())
->method('getDefinition')
->with($this->equalTo('fos_rest.format_negotiator'))
->will($this->returnValue($definition));
$compiler = new FormatListenerRulesPass();
$compiler->process($container);
}
}
Tests/DependencyInjection/Compiler/SerializerConfigurationPassTest.php 0000666 00000006535 13052362416 0022421 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\DependencyInjection\Compiler;
use FOS\RestBundle\DependencyInjection\Compiler\SerializerConfigurationPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* SerializerConfigurationPassTest test.
*/
class SerializerConfigurationPassTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ContainerBuilder
*/
private $container;
protected function setUp()
{
$this->container = new ContainerBuilder();
}
public function testShouldDoNothingIfSerializerIsFound()
{
$serializer = $this->getMockBuilder('FOS\RestBundle\Serializer\Serializer')->getMock();
$this->container->register('fos_rest.serializer', get_class($serializer));
$compiler = new SerializerConfigurationPass();
$compiler->process($this->container);
$this->assertSame(get_class($serializer), $this->container->getDefinition('fos_rest.serializer')->getClass());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testShouldThrowInvalidArgumentExceptionWhenNoSerializerIsFound()
{
$compiler = new SerializerConfigurationPass();
$compiler->process($this->container);
}
public function testShouldConfigureJMSSerializer()
{
$this->container->register('jms_serializer.serializer', 'JMS\Serializer\Serializer');
$compiler = new SerializerConfigurationPass();
$compiler->process($this->container);
$this->assertSame('fos_rest.serializer.jms', (string) $this->container->getAlias('fos_rest.serializer'));
}
public function testShouldConfigureCoreSerializer()
{
$this->container->register('serializer', 'Symfony\Component\Serializer\Serializer');
$this->container->register('fos_rest.serializer.exception_wrapper_serialize_handler');
$compiler = new SerializerConfigurationPass();
$compiler->process($this->container);
$this->assertSame('fos_rest.serializer.symfony', (string) $this->container->getAlias('fos_rest.serializer'));
$this->assertTrue(!$this->container->has('fos_rest.serializer.exception_wrapper_serialize_handler'));
}
public function testSerializerServiceSupersedesJmsSerializerService()
{
$this->container->register('jms_serializer.serializer', 'JMS\Serializer\Serializer');
$this->container->register('serializer', 'Symfony\Component\Serializer\Serializer');
$compiler = new SerializerConfigurationPass();
$compiler->process($this->container);
$this->assertSame('fos_rest.serializer.symfony', (string) $this->container->getAlias('fos_rest.serializer'));
}
public function testSerializerServiceCanBeJmsSerializer()
{
$this->container->register('jms_serializer.serializer', 'JMS\Serializer\Serializer');
$this->container->register('serializer', 'JMS\Serializer\Serializer');
$compiler = new SerializerConfigurationPass();
$compiler->process($this->container);
$this->assertSame('fos_rest.serializer.jms', (string) $this->container->getAlias('fos_rest.serializer'));
}
}
Tests/DependencyInjection/FOSRestExtensionTest.php 0000666 00000061104 13052362416 0016332 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\DependencyInjection;
use FOS\RestBundle\DependencyInjection\FOSRestExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;
/**
* FOSRestExtension test.
*
* @author Bulat Shakirzyanov
* @author Konstantin Kudryashov
*/
class FOSRestExtensionTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ContainerBuilder
*/
private $container;
/**
* @var FOSRestExtension
*/
private $extension;
/**
* @var bool
*/
private $includeFormat;
/**
* @var array
*/
private $formats;
/**
* @var string
*/
private $defaultFormat;
public function setUp()
{
$this->container = new ContainerBuilder();
$this->container->setParameter('kernel.bundles', array('JMSSerializerBundle' => true));
$this->container->setParameter('kernel.debug', false);
$this->extension = new FOSRestExtension();
$this->includeFormat = true;
$this->formats = [
'json' => false,
'xml' => false,
'html' => true,
];
$this->defaultFormat = null;
}
public function tearDown()
{
unset($this->container, $this->extension);
}
public function testDisableBodyListener()
{
$config = [
'fos_rest' => ['body_listener' => false],
];
$this->extension->load($config, $this->container);
$this->assertFalse($this->container->hasDefinition('fos_rest.body_listener'));
}
public function testLoadBodyListenerWithDefaults()
{
$this->extension->load([], $this->container);
$decoders = [
'json' => 'fos_rest.decoder.json',
'xml' => 'fos_rest.decoder.xml',
];
$this->assertTrue($this->container->hasDefinition('fos_rest.body_listener'));
$this->assertEquals($decoders, $this->container->getDefinition('fos_rest.decoder_provider')->getArgument(1));
$this->assertFalse($this->container->getDefinition('fos_rest.body_listener')->getArgument(1));
$this->assertCount(2, $this->container->getDefinition('fos_rest.body_listener')->getArguments());
}
public function testLoadBodyListenerWithNormalizerString()
{
$config = [
'fos_rest' => ['body_listener' => [
'array_normalizer' => 'fos_rest.normalizer.camel_keys',
]],
];
$this->extension->load($config, $this->container);
$normalizerArgument = $this->container->getDefinition('fos_rest.body_listener')->getArgument(2);
$this->assertInstanceOf(Reference::class, $normalizerArgument);
$this->assertEquals('fos_rest.normalizer.camel_keys', (string) $normalizerArgument);
}
public function testLoadBodyListenerWithNormalizerArray()
{
$config = [
'fos_rest' => ['body_listener' => [
'array_normalizer' => [
'service' => 'fos_rest.normalizer.camel_keys',
],
]],
];
$this->extension->load($config, $this->container);
$bodyListener = $this->container->getDefinition('fos_rest.body_listener');
$normalizerArgument = $bodyListener->getArgument(2);
$normalizeForms = $bodyListener->getArgument(3);
$this->assertCount(4, $bodyListener->getArguments());
$this->assertInstanceOf(Reference::class, $normalizerArgument);
$this->assertEquals('fos_rest.normalizer.camel_keys', (string) $normalizerArgument);
$this->assertEquals(false, $normalizeForms);
}
public function testLoadBodyListenerWithNormalizerArrayAndForms()
{
$config = [
'fos_rest' => ['body_listener' => [
'array_normalizer' => [
'service' => 'fos_rest.normalizer.camel_keys',
'forms' => true,
],
]],
];
$this->extension->load($config, $this->container);
$bodyListener = $this->container->getDefinition('fos_rest.body_listener');
$normalizerArgument = $bodyListener->getArgument(2);
$normalizeForms = $bodyListener->getArgument(3);
$this->assertCount(4, $bodyListener->getArguments());
$this->assertInstanceOf(Reference::class, $normalizerArgument);
$this->assertEquals('fos_rest.normalizer.camel_keys', (string) $normalizerArgument);
$this->assertEquals(true, $normalizeForms);
}
public function testDisableFormatListener()
{
$config = [
'fos_rest' => ['format_listener' => false],
];
$this->extension->load($config, $this->container);
$this->assertFalse($this->container->hasDefinition('fos_rest.format_listener'));
}
public function testLoadFormatListenerWithDefaults()
{
$this->extension->load([], $this->container);
$this->assertFalse($this->container->hasDefinition('fos_rest.format_listener'));
}
public function testLoadFormatListenerWithSingleRule()
{
$config = [
'fos_rest' => ['format_listener' => [
'rules' => ['path' => '/'],
]],
];
$this->extension->load($config, $this->container);
$this->assertTrue($this->container->hasDefinition('fos_rest.format_listener'));
}
public function testLoadParamFetcherListener()
{
$config = [
'fos_rest' => ['param_fetcher_listener' => true],
];
$this->extension->load($config, $this->container);
$this->assertTrue($this->container->hasDefinition('fos_rest.param_fetcher_listener'));
$this->assertFalse($this->container->getDefinition('fos_rest.param_fetcher_listener')->getArgument(1));
}
public function testLoadParamFetcherListenerForce()
{
$config = [
'fos_rest' => ['param_fetcher_listener' => 'force'],
];
$this->extension->load($config, $this->container);
$this->assertTrue($this->container->hasDefinition('fos_rest.param_fetcher_listener'));
$this->assertTrue($this->container->getDefinition('fos_rest.param_fetcher_listener')->getArgument(1));
}
public function testLoadFormatListenerWithMultipleRule()
{
$config = [
'fos_rest' => ['format_listener' => [
'rules' => [
['path' => '/foo'],
['path' => '/'],
],
]],
];
$this->extension->load($config, $this->container);
$this->assertTrue($this->container->hasDefinition('fos_rest.format_listener'));
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
public function testLoadFormatListenerMediaTypeNoRules()
{
$config = [
'fos_rest' => ['format_listener' => [
'media_type' => true,
]],
];
$this->extension->load($config, $this->container);
}
public function testLoadServicesWithDefaults()
{
$this->extension->load([], $this->container);
$this->assertAlias('fos_rest.view_handler.default', 'fos_rest.view_handler');
}
public function testDisableViewResponseListener()
{
$config = [
'fos_rest' => ['view' => ['view_response_listener' => false]],
];
$this->extension->load($config, $this->container);
$this->assertFalse($this->container->hasDefinition('fos_rest.view_response_listener'));
}
public function testLoadViewResponseListener()
{
$config = [
'fos_rest' => ['view' => ['view_response_listener' => true]],
];
$this->extension->load($config, $this->container);
$this->assertTrue($this->container->hasDefinition('fos_rest.view_response_listener'));
$this->assertFalse($this->container->getDefinition('fos_rest.view_response_listener')->getArgument(1));
}
public function testLoadViewResponseListenerForce()
{
$config = [
'fos_rest' => ['view' => ['view_response_listener' => 'force']],
];
$this->extension->load($config, $this->container);
$this->assertTrue($this->container->hasDefinition('fos_rest.view_response_listener'));
$this->assertTrue($this->container->getDefinition('fos_rest.view_response_listener')->getArgument(1));
}
public function testForceEmptyContentDefault()
{
$this->extension->load([], $this->container);
$this->assertEquals(204, $this->container->getDefinition('fos_rest.view_handler.default')->getArgument(6));
}
public function testForceEmptyContentIs200()
{
$config = ['fos_rest' => ['view' => ['empty_content' => 200]]];
$this->extension->load($config, $this->container);
$this->assertEquals(200, $this->container->getDefinition('fos_rest.view_handler.default')->getArgument(6));
}
public function testViewSerializeNullDefault()
{
$this->extension->load([], $this->container);
$this->assertFalse($this->container->getDefinition('fos_rest.view_handler.default')->getArgument(7));
}
public function testViewSerializeNullIsTrue()
{
$config = ['fos_rest' => ['view' => ['serialize_null' => true]]];
$this->extension->load($config, $this->container);
$this->assertTrue($this->container->getDefinition('fos_rest.view_handler.default')->getArgument(7));
}
public function testValidatorAliasWhenEnabled()
{
$config = ['fos_rest' => ['body_converter' => ['validate' => true]]];
$this->extension->load($config, $this->container);
$this->assertAlias('validator', 'fos_rest.validator');
}
public function testValidatorAliasWhenDisabled()
{
$config = ['fos_rest' => ['body_converter' => ['validate' => false]]];
$this->extension->load($config, $this->container);
$this->assertFalse($this->container->has('fos_rest.validator'));
}
public function testBodyConvertorDisabledAndSerializerVersionGiven()
{
$config = ['fos_rest' => ['body_converter' => ['enabled' => false], 'serializer' => ['version' => '1.0']]];
$this->extension->load($config, $this->container);
}
public function testBodyConvertorDisabledAndSerializerGroupsGiven()
{
$config = ['fos_rest' => ['body_converter' => ['enabled' => false], 'serializer' => ['groups' => ['Default']]]];
$this->extension->load($config, $this->container);
}
/**
* Test that extension loads properly.
*/
public function testConfigLoad()
{
$controllerLoaderDefinitionName = 'fos_rest.routing.loader.controller';
$controllerLoaderClass = 'FOS\RestBundle\Routing\Loader\RestRouteLoader';
$yamlCollectionLoaderDefinitionName = 'fos_rest.routing.loader.yaml_collection';
$yamlCollectionLoaderClass = 'FOS\RestBundle\Routing\Loader\RestYamlCollectionLoader';
$xmlCollectionLoaderDefinitionName = 'fos_rest.routing.loader.xml_collection';
$xmlCollectionLoaderClass = 'FOS\RestBundle\Routing\Loader\RestXmlCollectionLoader';
$this->extension->load([], $this->container);
$this->assertTrue($this->container->hasDefinition($controllerLoaderDefinitionName));
$this->assertValidRestRouteLoader($this->container->getDefinition($controllerLoaderDefinitionName));
$this->assertTrue($this->container->hasDefinition($yamlCollectionLoaderDefinitionName));
$this->assertValidRestFileLoader(
$this->container->getDefinition($yamlCollectionLoaderDefinitionName),
$this->includeFormat,
$this->formats,
$this->defaultFormat
);
$this->assertTrue($this->container->hasDefinition($xmlCollectionLoaderDefinitionName));
$this->assertValidRestFileLoader(
$this->container->getDefinition($xmlCollectionLoaderDefinitionName),
$this->includeFormat,
$this->formats,
$this->defaultFormat
);
}
public function testIncludeFormatDisabled()
{
$this->extension->load(
[
'fos_rest' => [
'routing_loader' => [
'include_format' => false,
],
],
],
$this->container
);
$yamlCollectionLoaderDefinitionName = 'fos_rest.routing.loader.yaml_collection';
$this->assertValidRestFileLoader(
$this->container->getDefinition($yamlCollectionLoaderDefinitionName),
false,
$this->formats,
$this->defaultFormat
);
$xmlCollectionLoaderDefinitionName = 'fos_rest.routing.loader.xml_collection';
$this->assertValidRestFileLoader(
$this->container->getDefinition($xmlCollectionLoaderDefinitionName),
false,
$this->formats,
$this->defaultFormat
);
}
public function testDefaultFormat()
{
$this->extension->load(
[
'fos_rest' => [
'routing_loader' => [
'default_format' => 'xml',
],
],
],
$this->container
);
$yamlCollectionLoaderDefinitionName = 'fos_rest.routing.loader.yaml_collection';
$this->assertValidRestFileLoader(
$this->container->getDefinition($yamlCollectionLoaderDefinitionName),
$this->includeFormat,
$this->formats,
'xml'
);
$xmlCollectionLoaderDefinitionName = 'fos_rest.routing.loader.xml_collection';
$this->assertValidRestFileLoader(
$this->container->getDefinition($xmlCollectionLoaderDefinitionName),
$this->includeFormat,
$this->formats,
'xml'
);
}
public function testFormats()
{
$this->extension->load(
[
'fos_rest' => [
'view' => [
'formats' => [
'json' => false,
'xml' => true,
],
],
],
],
$this->container
);
$yamlCollectionLoaderDefinitionName = 'fos_rest.routing.loader.yaml_collection';
$this->assertValidRestFileLoader(
$this->container->getDefinition($yamlCollectionLoaderDefinitionName),
$this->includeFormat,
[
'xml' => false,
'html' => true,
],
$this->defaultFormat
);
$xmlCollectionLoaderDefinitionName = 'fos_rest.routing.loader.xml_collection';
$this->assertValidRestFileLoader(
$this->container->getDefinition($xmlCollectionLoaderDefinitionName),
$this->includeFormat,
[
'xml' => false,
'html' => true,
],
$this->defaultFormat
);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testLoadBadClassThrowsException()
{
$this->extension->load([
'fos_rest' => [
'exception' => [
'messages' => [
'UnknownException' => true,
],
],
],
], $this->container);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Could not load class 'UnknownException' or the class does not extend from '\Exception'
*/
public function testLoadBadMessagesClassThrowsException()
{
$this->extension->load([
'fos_rest' => [
'exception' => [
'codes' => [
'UnknownException' => 404,
],
],
],
], $this->container);
}
public function testLoadOkMessagesClass()
{
$this->extension->load([
'fos_rest' => [
'exception' => [
'codes' => [
'Exception' => 404,
],
],
],
], $this->container);
$this->assertFalse($this->container->hasDefinition('fos_rest.exception.codes'));
}
/**
* @dataProvider getLoadBadCodeValueThrowsExceptionData
*
* @expectedException Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid HTTP code in fos_rest.exception.codes
*/
public function testLoadBadCodeValueThrowsException($value)
{
$this->extension->load([
'fos_rest' => [
'exception' => [
'codes' => [
'Exception' => $value,
],
],
],
], $this->container);
}
public function getLoadBadCodeValueThrowsExceptionData()
{
$data = [
null,
'HTTP_NOT_EXISTS',
'some random string',
true,
];
return array_map(function ($i) {
return [$i];
}, $data);
}
/**
* Test exception.debug config value uses kernel.debug value by default or provided value.
*
* @dataProvider getShowExceptionData
*
* @param bool $kernelDebug kernel.debug param value
* @param array $exceptionConfig Exception config
* @param bool|string $expectedValue Expected value of show_exception argument
*/
public function testExceptionDebug($kernelDebug, $exceptionConfig, $expectedValue)
{
$this->container->setParameter('kernel.debug', $kernelDebug);
$extension = new FOSRestExtension();
$extension->load(array(
'fos_rest' => array(
'exception' => $exceptionConfig,
),
), $this->container);
$definition = $this->container->getDefinition('fos_rest.exception.controller');
$this->assertSame($expectedValue, $definition->getArgument(2));
$definition = $this->container->getDefinition('fos_rest.serializer.exception_normalizer.jms');
$this->assertSame($expectedValue, $definition->getArgument(1));
$definition = $this->container->getDefinition('fos_rest.serializer.exception_normalizer.symfony');
$this->assertSame($expectedValue, $definition->getArgument(1));
}
public static function getShowExceptionData()
{
return array(
'empty config, kernel.debug is true' => array(
true,
array(),
true,
),
'empty config, kernel.debug is false' => array(
false,
array(),
false,
),
'config debug true' => array(
false,
array('debug' => true),
true,
),
'config debug false' => array(
true,
array('debug' => false),
false,
),
'config debug null, kernel.debug true' => array(
false,
array('debug' => null),
true,
),
'config debug null, kernel.debug false' => array(
false,
array('debug' => null),
true,
),
);
}
public function testGetConfiguration()
{
$configuration = $this->extension->getConfiguration(array(), $this->container);
$this->assertInstanceOf('FOS\RestBundle\DependencyInjection\Configuration', $configuration);
}
/**
* Assert that loader definition described properly.
*
* @param Definition $loader loader definition
*/
private function assertValidRestRouteLoader(Definition $loader)
{
$arguments = $loader->getArguments();
$this->assertEquals(5, count($arguments));
$this->assertEquals('service_container', (string) $arguments[0]);
$this->assertEquals('file_locator', (string) $arguments[1]);
$this->assertEquals('controller_name_converter', (string) $arguments[2]);
$this->assertEquals('fos_rest.routing.loader.reader.controller', (string) $arguments[3]);
$this->assertNull($arguments[4]);
$this->assertArrayHasKey('routing.loader', $loader->getTags());
}
/**
* Assert that loader definition described properly.
*
* @param Definition $loader loader definition
* @param bool $includeFormat whether or not the requested view format must be included in the route path
* @param string[] $formats supported view formats
* @param string $defaultFormat default view format
*/
private function assertValidRestFileLoader(
Definition $loader,
$includeFormat,
array $formats,
$defaultFormat
) {
$locatorRef = new Reference('file_locator');
$processorRef = new Reference('fos_rest.routing.loader.processor');
$arguments = $loader->getArguments();
$this->assertEquals(5, count($arguments));
$this->assertEquals($locatorRef, $arguments[0]);
$this->assertEquals($processorRef, $arguments[1]);
$this->assertSame($includeFormat, $arguments[2]);
$this->assertEquals($formats, $arguments[3]);
$this->assertSame($defaultFormat, $arguments[4]);
$this->assertArrayHasKey('routing.loader', $loader->getTags());
}
private function assertAlias($value, $key)
{
$this->assertEquals($value, (string) $this->container->getAlias($key), sprintf('%s alias is correct', $key));
}
public function testCheckViewHandlerWithJsonp()
{
$this->extension->load(['fos_rest' => ['view' => ['jsonp_handler' => null]]], $this->container);
$this->assertTrue($this->container->has('fos_rest.view_handler'));
$viewHandler = $this->container->getDefinition('fos_rest.view_handler');
$this->assertInstanceOf(DefinitionDecorator::class, $viewHandler);
}
public function testSerializerExceptionNormalizer()
{
$this->extension->load(['fos_rest' => ['exception' => true]], $this->container);
$this->assertTrue($this->container->has('fos_rest.serializer.exception_normalizer.symfony'));
}
public function testZoneMatcherListenerDefault()
{
$this->extension->load(array('fos_rest' => array()), $this->container);
$this->assertFalse($this->container->has('fos_rest.zone_matcher_listener'));
}
public function testZoneMatcherListener()
{
$config = array('fos_rest' => array(
'zone' => array(
'first' => array('path' => '/api/*'),
'second' => array('path' => '/^second', 'ips' => '127.0.0.1'),
),
));
$this->extension->load($config, $this->container);
$zoneMatcherListener = $this->container->getDefinition('fos_rest.zone_matcher_listener');
$addRequestMatcherCalls = $zoneMatcherListener->getMethodCalls();
$this->assertTrue($this->container->has('fos_rest.zone_matcher_listener'));
$this->assertEquals('FOS\RestBundle\EventListener\ZoneMatcherListener', $zoneMatcherListener->getClass());
$this->assertCount(2, $addRequestMatcherCalls);
// First zone
$this->assertEquals('addRequestMatcher', $addRequestMatcherCalls[0][0]);
$requestMatcherFirstId = (string) $addRequestMatcherCalls[0][1][0];
$requestMatcherFirst = $this->container->getDefinition($requestMatcherFirstId);
$this->assertInstanceOf('Symfony\Component\DependencyInjection\DefinitionDecorator', $requestMatcherFirst);
$this->assertEquals('/api/*', $requestMatcherFirst->getArgument(0));
// Second zone
$this->assertEquals('addRequestMatcher', $addRequestMatcherCalls[1][0]);
$requestMatcherSecondId = (string) $addRequestMatcherCalls[1][1][0];
$requestMatcherSecond = $this->container->getDefinition($requestMatcherSecondId);
$this->assertInstanceOf('Symfony\Component\DependencyInjection\DefinitionDecorator', $requestMatcherSecond);
$this->assertEquals('/^second', $requestMatcherSecond->getArgument(0));
$this->assertEquals(array('127.0.0.1'), $requestMatcherSecond->getArgument(3));
}
}
Tests/EventListener/AccessDeniedListenerTest.php 0000666 00000016624 13052362416 0016045 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\EventListener;
use FOS\RestBundle\EventListener\AccessDeniedListener;
use FOS\RestBundle\FOSRestBundle;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
/**
* AccessDeniedListenerTest.
*
* @author Boris Guéry
*/
class AccessDeniedListenerTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getFormatsDataProvider
*
* @param array $formats
* @param string $format
*/
public function testAccessDeniedExceptionIsConvertedToAnAccessDeniedHttpExceptionForFormat(array $formats, $format)
{
$request = new Request();
$request->setRequestFormat($format);
$this->doTestAccessDeniedExceptionIsConvertedToAnAccessDeniedHttpExceptionForRequest($request, $formats);
}
/**
* @dataProvider getFormatsDataProvider
*
* @param array $formats
* @param string $format
*/
public function testAccessDeniedExceptionIsConvertedToAnAccessDeniedHttpExceptionForFormatNoZone(array $formats, $format)
{
$request = new Request();
$request->attributes->set(FOSRestBundle::ZONE_ATTRIBUTE, false);
$request->setRequestFormat($format);
$this->doTestAccessDeniedExceptionIsConvertedToAnAccessDeniedHttpExceptionForRequest($request, $formats, 'Symfony\Component\Security\Core\Exception\AccessDeniedException');
}
/**
* @dataProvider getContentTypesDataProvider
*
* @param array $formats
* @param string $contentType
*/
public function testAccessDeniedExceptionIsConvertedToAnAccessDeniedHttpExceptionForContentType(array $formats, $contentType)
{
$request = new Request();
$request->headers->set('Content-Type', $contentType);
$this->doTestAccessDeniedExceptionIsConvertedToAnAccessDeniedHttpExceptionForRequest($request, $formats);
}
/**
* @param Request $request
* @param array $formats
*/
private function doTestAccessDeniedExceptionIsConvertedToAnAccessDeniedHttpExceptionForRequest(Request $request, array $formats, $exceptionClass = 'Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException')
{
$exception = new AccessDeniedException();
$event = new GetResponseForExceptionEvent(new TestKernel(), $request, 'foo', $exception);
$listener = new AccessDeniedListener($formats, null, 'foo');
// store the current error_log, and disable it temporarily
$errorLog = ini_set('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul');
$listener->onKernelException($event);
// restore the old error_log
ini_set('error_log', $errorLog);
$this->assertInstanceOf($exceptionClass, $event->getException());
}
/**
* @dataProvider getFormatsDataProvider
*
* @param array $formats
*/
public function testCommonExceptionsAreBypassed($formats)
{
$request = new Request();
$request->setRequestFormat(key($formats));
$exception = new \Exception('foo');
$event = new GetResponseForExceptionEvent(new TestKernel(), $request, 'foo', $exception);
$listener = new AccessDeniedListener($formats, null, 'foo');
$listener->onKernelException($event);
$this->assertSame($exception, $event->getException());
}
/**
* @dataProvider getFormatsDataProvider
*
* @param array $formats
* @param string $format
*/
public function testAuthenticationExceptionIsConvertedToAnAccessDeniedHttpExceptionForFormat(array $formats, $format)
{
$request = new Request();
$request->setRequestFormat($format);
$this->doTestAuthenticationExceptionIsConvertedToAnHttpExceptionForRequest($request, $formats);
}
/**
* @dataProvider getContentTypesDataProvider
*
* @param array $formats
* @param string $contentType
*/
public function testAuthenticationExceptionIsConvertedToAnAccessDeniedHttpExceptionForContentType(array $formats, $contentType)
{
$request = new Request();
$request->headers->set('Content-Type', $contentType);
$this->doTestAuthenticationExceptionIsConvertedToAnHttpExceptionForRequest($request, $formats);
}
/**
* @param Request $request
* @param array $formats
*/
private function doTestAuthenticationExceptionIsConvertedToAnHttpExceptionForRequest(Request $request, array $formats)
{
$exception = new AuthenticationException();
$event = new GetResponseForExceptionEvent(new TestKernel(), $request, 'foo', $exception);
$listener = new AccessDeniedListener($formats, null, 'foo');
// store the current error_log, and disable it temporarily
$errorLog = ini_set('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul');
$listener->onKernelException($event);
// restore the old error_log
ini_set('error_log', $errorLog);
$this->assertInstanceOf('Symfony\Component\HttpKernel\Exception\HttpException', $event->getException());
$this->assertEquals(401, $event->getException()->getStatusCode());
$this->assertEquals('You are not authenticated', $event->getException()->getMessage());
$this->assertArrayNotHasKey('WWW-Authenticate', $event->getException()->getHeaders());
}
/**
* @param Request $request
* @param array $formats
*/
private function doTestUnauthorizedHttpExceptionHasCorrectChallenge(Request $request, array $formats)
{
$exception = new AuthenticationException();
$event = new GetResponseForExceptionEvent(new TestKernel(), $request, 'foo', $exception);
$listener = new AccessDeniedListener($formats, 'Basic realm="Restricted Area"', 'foo');
// store the current error_log, and disable it temporarily
$errorLog = ini_set('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul');
$listener->onKernelException($event);
// restore the old error_log
ini_set('error_log', $errorLog);
$this->assertInstanceOf('Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException', $event->getException());
$this->assertEquals(401, $event->getException()->getStatusCode());
$this->assertEquals('You are not authenticated', $event->getException()->getMessage());
$headers = $event->getException()->getHeaders();
$this->assertEquals('Basic realm="Restricted Area"', $headers['WWW-Authenticate']);
}
public static function getFormatsDataProvider()
{
return [
[['json' => true], 'json'],
];
}
public static function getContentTypesDataProvider()
{
return [
[['json' => true], 'application/json'],
];
}
}
class TestKernel implements HttpKernelInterface
{
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
{
return new Response('foo');
}
}
Tests/EventListener/BodyListenerTest.php 0000666 00000027626 13052362416 0014434 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\EventListener;
use FOS\RestBundle\Decoder\ContainerDecoderProvider;
use FOS\RestBundle\EventListener\BodyListener;
use FOS\RestBundle\FOSRestBundle;
use FOS\RestBundle\Normalizer\Exception\NormalizationException;
use Symfony\Component\HttpFoundation\HeaderBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
/**
* Request listener test.
*
* @author Alain Horner
* @author Stefan Paschke
*/
class BodyListenerTest extends \PHPUnit_Framework_TestCase
{
/**
* @param bool $decode use decoder provider
* @param Request $request the original request
* @param string $method a http method (e.g. POST, GET, PUT, ...)
* @param array $expectedParameters the http parameters of the updated request
* @param string $contentType the request header content type
* @param bool $throwExceptionOnUnsupportedContentType
*
* @dataProvider testOnKernelRequestDataProvider
*/
public function testOnKernelRequest($decode, Request $request, $method, $expectedParameters, $contentType = null, $throwExceptionOnUnsupportedContentType = false)
{
$decoder = $this->getMockBuilder('FOS\RestBundle\Decoder\DecoderInterface')->getMock();
$decoder->expects($this->any())
->method('decode')
->will($this->returnValue($request->getContent()));
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
$decoderProvider = new ContainerDecoderProvider($container, ['json' => 'foo']);
$listener = new BodyListener($decoderProvider, $throwExceptionOnUnsupportedContentType);
if ($decode) {
$container
->expects($this->once())
->method('get')
->with('foo')
->will($this->returnValue($decoder));
}
$request->setMethod($method);
if ($contentType) {
$request->headers = new HeaderBag(['Content-Type' => $contentType]);
}
$event = $this->getMockBuilder(GetResponseEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
$listener->onKernelRequest($event);
$this->assertEquals($request->request->all(), $expectedParameters);
}
public static function testOnKernelRequestDataProvider()
{
return [
'Empty POST request' => [true, new Request([], [], [], [], [], [], ['foo']), 'POST', ['foo'], 'application/json'],
'Empty PUT request' => [true, new Request([], [], [], [], [], [], ['foo']), 'PUT', ['foo'], 'application/json'],
'Empty PATCH request' => [true, new Request([], [], [], [], [], [], ['foo']), 'PATCH', ['foo'], 'application/json'],
'Empty DELETE request' => [true, new Request([], [], [], [], [], [], ['foo']), 'DELETE', ['foo'], 'application/json'],
'Empty GET request' => [false, new Request([], [], [], [], [], [], ['foo']), 'GET', [], 'application/json'],
'POST request with parameters' => [false, new Request([], ['bar'], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded'], ['foo']), 'POST', ['bar'], 'application/x-www-form-urlencoded'],
'POST request with unallowed format' => [false, new Request([], [], [], [], [], [], ['foo']), 'POST', [], 'application/fooformat'],
'POST request with no Content-Type' => [true, new Request([], [], ['_format' => 'json'], [], [], [], ['foo']), 'POST', ['foo']],
];
}
public function testOnKernelRequestNoZone()
{
$data = array('foo_bar' => 'foo_bar');
$normalizedData = array('fooBar' => 'foo_bar');
$decoder = $this->getMockBuilder('FOS\RestBundle\Decoder\DecoderInterface')->getMock();
$decoder
->expects($this->never())
->method('decode')
->will($this->returnValue($data));
$decoderProvider = $this->getMockBuilder('FOS\RestBundle\Decoder\DecoderProviderInterface')->getMock();
$decoderProvider
->expects($this->never())
->method('getDecoder')
->will($this->returnValue($decoder));
$normalizer = $this->getMockBuilder('FOS\RestBundle\Normalizer\ArrayNormalizerInterface')->getMock();
$normalizer
->expects($this->never())
->method('normalize')
->with($data)
->will($this->returnValue($normalizedData));
$request = new Request(array(), array(), array(), array(), array(), array(), 'foo');
$request->attributes->set(FOSRestBundle::ZONE_ATTRIBUTE, false);
$request->setMethod('POST');
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
$listener = new BodyListener($decoderProvider, false, $normalizer);
$listener->onKernelRequest($event);
$this->assertEquals(array(), $request->request->all());
}
public function testOnKernelRequestWithNormalizer()
{
$data = ['foo_bar' => 'foo_bar'];
$normalizedData = ['fooBar' => 'foo_bar'];
$decoder = $this->getMockBuilder('FOS\RestBundle\Decoder\DecoderInterface')->getMock();
$decoder
->expects($this->any())
->method('decode')
->will($this->returnValue($data));
$decoderProvider = $this->getMockBuilder('FOS\RestBundle\Decoder\DecoderProviderInterface')->getMock();
$decoderProvider
->expects($this->any())
->method('getDecoder')
->will($this->returnValue($decoder));
$decoderProvider
->expects($this->any())
->method('supports')
->will($this->returnValue(true));
$normalizer = $this->getMockBuilder('FOS\RestBundle\Normalizer\ArrayNormalizerInterface')->getMock();
$normalizer
->expects($this->once())
->method('normalize')
->with($data)
->will($this->returnValue($normalizedData));
$request = new Request([], [], [], [], [], [], 'foo');
$request->setMethod('POST');
$event = $this->getMockBuilder(GetResponseEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
$listener = new BodyListener($decoderProvider, false, $normalizer);
$listener->onKernelRequest($event);
$this->assertEquals($normalizedData, $request->request->all());
}
/**
* @dataProvider formNormalizationProvider
*/
public function testOnKernelRequestNormalizationWithForms($method, $contentType, $mustBeNormalized)
{
$data = array('foo_bar' => 'foo_bar');
$normalizedData = array('fooBar' => 'foo_bar');
$decoderProvider = $this->getMockBuilder('FOS\RestBundle\Decoder\DecoderProviderInterface')->getMock();
$normalizer = $this->getMockBuilder('FOS\RestBundle\Normalizer\ArrayNormalizerInterface')->getMock();
if ($mustBeNormalized) {
$normalizer
->expects($this->once())
->method('normalize')
->with($data)
->will($this->returnValue($normalizedData));
} else {
$normalizer
->expects($this->never())
->method('normalize');
}
$request = new Request([], $data, [], [], [], [], 'foo');
$request->headers->set('Content-Type', $contentType);
$request->setMethod($method);
$event = $this->getMockBuilder(GetResponseEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
$listener = new BodyListener($decoderProvider, false, $normalizer, true);
$listener->onKernelRequest($event);
if ($mustBeNormalized) {
$this->assertEquals($normalizedData, $request->request->all());
} else {
$this->assertEquals($data, $request->request->all());
}
}
public function formNormalizationProvider()
{
$cases = [];
foreach (['POST', 'PUT', 'PATCH', 'DELETE'] as $method) {
$cases[] = [$method, 'multipart/form-data', true];
$cases[] = [$method, 'multipart/form-data; boundary=AaB03x', true];
$cases[] = [$method, 'application/x-www-form-urlencoded', true];
$cases[] = [$method, 'application/x-www-form-urlencoded; charset=utf-8', true];
$cases[] = [$method, 'unknown', false];
}
return $cases;
}
/**
* @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
*/
public function testOnKernelRequestNormalizationException()
{
$decoder = $this->getMockBuilder('FOS\RestBundle\Decoder\DecoderInterface')->getMock();
$decoder
->expects($this->any())
->method('decode')
->will($this->returnValue([]));
$decoderProvider = $this->getMockBuilder('FOS\RestBundle\Decoder\DecoderProviderInterface')->getMock();
$decoderProvider
->expects($this->any())
->method('getDecoder')
->will($this->returnValue($decoder));
$decoderProvider
->expects($this->any())
->method('supports')
->will($this->returnValue(true));
$normalizer = $this->getMockBuilder('FOS\RestBundle\Normalizer\ArrayNormalizerInterface')->getMock();
$normalizer
->expects($this->once())
->method('normalize')
->will($this->throwException(new NormalizationException()));
$request = new Request([], [], [], [], [], [], 'foo');
$request->setMethod('POST');
$event = $this->getMockBuilder(GetResponseEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
$listener = new BodyListener($decoderProvider, false, $normalizer);
$listener->onKernelRequest($event);
}
/**
* Test that a malformed request will cause a BadRequestHttpException to be thrown.
*/
public function testBadRequestExceptionOnMalformedContent()
{
$this->setExpectedException('\Symfony\Component\HttpKernel\Exception\BadRequestHttpException');
$this->testOnKernelRequest(true, new Request([], [], [], [], [], [], 'foo'), 'POST', [], 'application/json');
}
/**
* Test that a unallowed format will cause a UnsupportedMediaTypeHttpException to be thrown.
*/
public function testUnsupportedMediaTypeHttpExceptionOnUnsupportedMediaType()
{
$this->setExpectedException('\Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException');
$this->testOnKernelRequest(false, new Request([], [], [], [], [], [], 'foo'), 'POST', [], 'application/foo', true);
}
public function testShouldNotThrowUnsupportedMediaTypeHttpExceptionWhenIsAnEmptyDeleteRequest()
{
$this->testOnKernelRequest(false, new Request(), 'DELETE', [], null, true);
}
}
Tests/EventListener/FormatListenerTest.php 0000666 00000015764 13052362416 0014767 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\EventListener;
use FOS\RestBundle\EventListener\FormatListener;
use FOS\RestBundle\FOSRestBundle;
use FOS\RestBundle\Negotiation\FormatNegotiator;
use Negotiation\Accept;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestMatcher;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Request listener test.
*
* @author Lukas Kahwe Smith
*/
class FormatListenerTest extends \PHPUnit_Framework_TestCase
{
public function testOnKernelControllerNegotiation()
{
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()
->getMock();
$request = new Request();
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
$formatNegotiator = $this->getMockBuilder('FOS\RestBundle\Negotiation\FormatNegotiator')
->disableOriginalConstructor()
->setMethods(['getBest'])
->getMock();
$formatNegotiator->expects($this->once())
->method('getBest')
->willReturn(new Accept('text/xml; q=1'));
$listener = new FormatListener($formatNegotiator);
$listener->onKernelRequest($event);
$this->assertEquals('xml', $request->getRequestFormat());
}
public function testOnKernelControllerNoZone()
{
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()
->getMock();
$request = new Request();
$request->attributes->set(FOSRestBundle::ZONE_ATTRIBUTE, false);
$requestStack = new RequestStack();
$requestStack->push($request);
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
$formatNegotiator = new FormatNegotiator($requestStack);
$formatNegotiator->add(new RequestMatcher('/'), ['fallback_format' => 'json']);
$listener = new FormatListener($formatNegotiator);
$listener->onKernelRequest($event);
$this->assertEquals($request->getRequestFormat(), 'html');
}
public function testOnKernelControllerNegotiationStopped()
{
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()
->getMock();
$request = new Request();
$request->setRequestFormat('xml');
$requestStack = new RequestStack();
$requestStack->push($request);
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
$formatNegotiator = new FormatNegotiator($requestStack);
$formatNegotiator->add(new RequestMatcher('/'), ['stop' => true]);
$formatNegotiator->add(new RequestMatcher('/'), ['fallback_format' => 'json']);
$listener = new FormatListener($formatNegotiator);
$listener->onKernelRequest($event);
$this->assertEquals($request->getRequestFormat(), 'xml');
}
/**
* @expectedException \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function testOnKernelControllerException()
{
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()
->getMock();
$request = new Request();
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
$event->expects($this->once())
->method('getRequestType')
->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST));
$formatNegotiator = $this->getMockBuilder('FOS\RestBundle\Negotiation\FormatNegotiator')
->disableOriginalConstructor()
->getMock();
$listener = new FormatListener($formatNegotiator);
$listener->onKernelRequest($event);
}
/**
* Test FormatListener won't overwrite request format when it was already specified.
*
* @dataProvider useSpecifiedFormatDataProvider
*/
public function testUseSpecifiedFormat($format, $result)
{
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()
->getMock();
$request = new Request();
if ($format) {
$request->setRequestFormat($format);
}
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
$formatNegotiator = $this->getMockBuilder('FOS\RestBundle\Negotiation\FormatNegotiator')
->disableOriginalConstructor()
->setMethods(['getBest'])
->getMock();
$formatNegotiator->expects($this->any())
->method('getBest')
->willReturn(new Accept('text/xml; q=1'));
$listener = new FormatListener($formatNegotiator);
$listener->onKernelRequest($event);
$this->assertEquals($request->getRequestFormat(), $result);
}
public function useSpecifiedFormatDataProvider()
{
return [
[null, 'xml'],
['json', 'json'],
];
}
/**
* Generates a request like a symfony fragment listener does.
* Set request type to master.
*/
public function testSfFragmentFormat()
{
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()
->getMock();
$request = new Request();
$attributes = ['_locale' => 'en', '_format' => 'json', '_controller' => 'FooBundle:Index:featured'];
$request->attributes->add($attributes);
$request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', []), $attributes));
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
$event->expects($this->any())
->method('getRequestType')
->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST));
$formatNegotiator = $this->getMockBuilder('FOS\RestBundle\Negotiation\FormatNegotiator')
->disableOriginalConstructor()
->getMock();
$formatNegotiator->expects($this->any())
->method('getBest')
->will($this->returnValue('application/json'));
$listener = new FormatListener($formatNegotiator);
$listener->onKernelRequest($event);
$this->assertEquals($request->getRequestFormat(), 'json');
}
}
Tests/EventListener/MimeTypeListenerTest.php 0000666 00000006126 13052362416 0015260 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\EventListener;
use FOS\RestBundle\EventListener\MimeTypeListener;
use FOS\RestBundle\FOSRestBundle;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Request listener test.
*
* @author Lukas Kahwe Smith
*/
class MimeTypeListenerTest extends \PHPUnit_Framework_TestCase
{
public function testOnKernelRequest()
{
$listener = new MimeTypeListener(['jsonp' => ['application/javascript+jsonp']]);
$request = new Request();
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()->getMock();
$event->expects($this->any())
->method('getRequest')
->will($this->returnValue($request));
$this->assertNull($request->getMimeType('jsonp'));
$listener->onKernelRequest($event);
$this->assertNull($request->getMimeType('jsonp'));
$event->expects($this->once())
->method('getRequestType')
->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST));
$listener->onKernelRequest($event);
$this->assertEquals('application/javascript+jsonp', $request->getMimeType('jsonp'));
}
public function testOnKernelRequestNoZone()
{
$listener = new MimeTypeListener(['soap' => ['application/soap+xml']]);
$request = new Request();
$request->attributes->set(FOSRestBundle::ZONE_ATTRIBUTE, false);
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()->getMock();
$event->expects($this->any())
->method('getRequest')
->will($this->returnValue($request));
$event->expects($this->never())
->method('getRequestType')
->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST));
$listener->onKernelRequest($event);
$this->assertNull($request->getMimeType('soap'));
}
public function testOnKernelRequestWithZone()
{
$listener = new MimeTypeListener(['soap' => ['application/soap+xml']]);
$request = new Request();
$request->attributes->set(FOSRestBundle::ZONE_ATTRIBUTE, true);
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()->getMock();
$event->expects($this->any())
->method('getRequest')
->will($this->returnValue($request));
$event->expects($this->once())
->method('getRequestType')
->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST));
$listener->onKernelRequest($event);
$this->assertEquals('application/soap+xml', $request->getMimeType('soap'));
}
}
Tests/EventListener/ParamFetcherListenerTest.php 0000666 00000014100 13052362416 0016057 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\EventListener;
use FOS\RestBundle\EventListener\ParamFetcherListener;
use FOS\RestBundle\FOSRestBundle;
use FOS\RestBundle\Tests\Fixtures\Controller\ParamFetcherController;
use Symfony\Component\HttpFoundation\Request;
/**
* Param Fetcher Listener Tests.
*/
class ParamFetcherListenerTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $paramFetcher;
/**
* @var \FOS\RestBundle\EventListener\ParamFetcherListener
*/
private $paramFetcherListener;
/**
* Tests the ParamFetcher being able to set an attribute on the request
* when configured to do so and the attribute is specified as a null
* default value.
*/
public function testSettingAttributes()
{
$request = new Request();
$request->attributes->set('customer', null);
$event = $this->getEvent($request);
$this->paramFetcher->expects($this->once())
->method('all')
->will($this->returnValue([
'customer' => 5,
]));
$this->paramFetcherListener->onKernelController($event);
$this->assertEquals(5, $request->attributes->get('customer'), 'Listener set attribute as expected');
}
/**
* Tests the ParamFetcher being able to set an attribute on the request
* when configured to do so and the attribute is specified as a null
* default value.
*/
public function testSettingParamFetcherOnRequest()
{
$request = new Request();
$event = $this->getEvent($request);
$this->paramFetcher->expects($this->once())
->method('all')
->will($this->returnValue([]));
$this->paramFetcherListener->onKernelController($event);
$this->assertSame($this->paramFetcher, $request->attributes->get('paramFetcher'));
}
public function testParamFetcherOnRequestNoZone()
{
$request = new Request();
$request->attributes->set(FOSRestBundle::ZONE_ATTRIBUTE, false);
$event = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent')
->disableOriginalConstructor()
->getMock();
$event->expects($this->atLeastOnce())
->method('getRequest')
->will($this->returnValue($request));
$this->paramFetcher->expects($this->never())
->method('all')
->will($this->returnValue(array()));
$this->paramFetcherListener->onKernelController($event);
$this->assertNull($request->attributes->get('paramFetcher'));
}
/**
* Tests that the ParamFetcher can be injected by the default name
* ($paramFetcher) or by a different name if type-hinted.
*
* @dataProvider setParamFetcherByTypehintProvider
*/
public function testSettingParamFetcherByTypehint($actionName, $expectedAttribute)
{
$request = new Request();
$event = $this->getEvent($request, $actionName);
$this->paramFetcher->expects($this->once())
->method('all')
->will($this->returnValue([]));
$this->paramFetcherListener->onKernelController($event);
$this->assertSame($this->paramFetcher, $request->attributes->get($expectedAttribute));
}
/**
* Tests that the ParamFetcher can be injected in a invokable controller.
*/
public function testSettingParamFetcherForInvokable()
{
$request = new Request();
$event = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent')
->disableOriginalConstructor()
->getMock();
$event->expects($this->atLeastOnce())
->method('getRequest')
->will($this->returnValue($request));
$controller = new ParamFetcherController();
$event->expects($this->atLeastOnce())
->method('getController')
->will($this->returnValue($controller));
$this->paramFetcher->expects($this->once())
->method('all')
->will($this->returnValue([]));
$this->paramFetcherListener->onKernelController($event);
$this->assertSame($this->paramFetcher, $request->attributes->get('pfInvokable'));
}
public function setParamFetcherByTypehintProvider()
{
return [
// Without a typehint, the ParamFetcher should be injected as
// $paramFetcher.
['byNameAction', 'paramFetcher'],
// With a typehint, the ParamFetcher should be injected as whatever
// the parameter name is.
['byTypeAction', 'pf'],
// The user can typehint using ParamFetcherInterface, too.
['byInterfaceAction', 'pfi'],
// If there is no controller argument for the ParamFetcher, it
// should be injected as the default name.
['notProvidedAction', 'paramFetcher'],
];
}
protected function getEvent(Request $request, $actionMethod = 'byNameAction')
{
$event = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent')
->disableOriginalConstructor()
->getMock();
$event->expects($this->atLeastOnce())
->method('getRequest')
->will($this->returnValue($request));
$controller = new ParamFetcherController();
$event->expects($this->atLeastOnce())
->method('getController')
->will($this->returnValue([$controller, $actionMethod]));
return $event;
}
public function setUp()
{
$this->paramFetcher = $this->getMockBuilder('FOS\\RestBundle\\Request\\ParamFetcher')
->disableOriginalConstructor()
->getMock();
$this->paramFetcherListener = new ParamFetcherListener($this->paramFetcher, true);
}
}
Tests/EventListener/VersionListenerTest.php 0000666 00000003602 13052362416 0015150 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\EventListener;
use FOS\RestBundle\EventListener\VersionListener;
use FOS\RestBundle\FOSRestBundle;
use Symfony\Component\HttpFoundation\Request;
/**
* Version listener test.
*
* @author Ener-Getick
*/
class VersionListenerTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \FOS\RestBundle\View\ConfigurableViewHandlerInterface
*/
private $viewHandler;
/**
* @var \FOS\RestBundle\Version\VersionResolverInterface
*/
private $resolver;
/**
* @var \FOS\RestBundle\EventListener\VersionListener
*/
private $listener;
public function setUp()
{
$this->viewHandler = $this->getMockBuilder('FOS\RestBundle\View\ConfigurableViewHandlerInterface')->getMock();
$this->resolver = $this->getMockBuilder('FOS\RestBundle\Version\VersionResolverInterface')->getMock();
$this->listener = new VersionListener($this->viewHandler, $this->resolver);
}
public function testDefaultVersion()
{
$this->assertEquals(false, $this->listener->getVersion());
}
public function testMatchNoZone()
{
$request = new Request();
$request->attributes->set(FOSRestBundle::ZONE_ATTRIBUTE, false);
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()
->getMock();
$event
->expects($this->once())
->method('getRequest')
->willReturn($request);
$this->listener->onKernelRequest($event);
$this->assertFalse($this->listener->getVersion());
}
}
Tests/EventListener/ViewResponseListenerTest.php 0000666 00000022200 13052362416 0016147 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\EventListener;
use FOS\RestBundle\Controller\Annotations\View as ViewAnnotation;
use FOS\RestBundle\EventListener\ViewResponseListener;
use FOS\RestBundle\FOSRestBundle;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
/**
* View response listener test.
*
* @author Lukas Kahwe Smith
*/
class ViewResponseListenerTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \FOS\RestBundle\EventListener\ViewResponseListener
*/
public $listener;
/**
* @var \FOS\RestBundle\View\ViewHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
public $viewHandler;
/**
* @var \Symfony\Bundle\FrameworkBundle\Templating\EngineInterface|\PHPUnit_Framework_MockObject_MockObject
*/
public $templating;
private $router;
private $serializer;
private $requestStack;
/**
* @param Request $request
*
* @return \Symfony\Component\HttpKernel\Event\FilterControllerEvent|\PHPUnit_Framework_MockObject_MockObject
*/
protected function getFilterEvent(Request $request)
{
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\FilterControllerEvent')
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
return $event;
}
/**
* @param Request $request
* @param mixed $result
*
* @return \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent|\PHPUnit_Framework_MockObject_MockObject
*/
protected function getResponseEvent(Request $request, $result)
{
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent')
->disableOriginalConstructor()
->getMock();
$event->expects($this->atLeastOnce())
->method('getRequest')
->will($this->returnValue($request));
$event->expects($this->any())
->method('getControllerResult')
->will($this->returnValue($result));
return $event;
}
public function testOnKernelView()
{
$template = $this->getMockBuilder('Symfony\Component\Templating\TemplateReferenceInterface')
->disableOriginalConstructor()
->getMock();
$template->expects($this->once())
->method('set')
->with('format', null);
$annotation = new ViewAnnotation([]);
$annotation->setOwner([new FooController(), 'onKernelViewAction']);
$annotation->setTemplate($template);
$request = new Request();
$request->attributes->set('foo', 'baz');
$request->attributes->set('halli', 'galli');
$request->attributes->set('_template', $annotation);
$response = new Response();
$view = $this->getMockBuilder('FOS\RestBundle\View\View')
->disableOriginalConstructor()
->getMock();
$view->expects($this->exactly(2))
->method('getFormat')
->will($this->onConsecutiveCalls(null, 'html'));
$viewHandler = $this->getMockBuilder('FOS\RestBundle\View\ViewHandlerInterface')->getMock();
$viewHandler->expects($this->once())
->method('handle')
->with($this->isInstanceOf('FOS\RestBundle\View\View'), $this->equalTo($request))
->will($this->returnValue($response));
$viewHandler->expects($this->once())
->method('isFormatTemplating')
->with('html')
->will($this->returnValue(true));
$this->listener = new ViewResponseListener($viewHandler, false);
$event = $this->getResponseEvent($request, $view);
$event->expects($this->once())
->method('setResponse');
$this->listener->onKernelView($event);
}
public function testOnKernelViewWhenControllerResultIsNotViewObject()
{
$this->createViewResponseListener();
$request = new Request();
$event = $this->getResponseEvent($request, []);
$event->expects($this->never())
->method('setResponse');
$this->assertEquals(null, $this->listener->onKernelView($event));
}
public static function statusCodeProvider()
{
return [
[201, 200, 201],
[201, 404, 404],
[201, 500, 500],
];
}
/**
* @dataProvider statusCodeProvider
*/
public function testStatusCode($annotationCode, $viewCode, $expectedCode)
{
$this->createViewResponseListener(['json' => true]);
$viewAnnotation = new ViewAnnotation([]);
$viewAnnotation->setOwner([$this, 'statusCodeProvider']);
$viewAnnotation->setStatusCode($annotationCode);
$request = new Request();
$request->setRequestFormat('json');
$request->attributes->set('_template', $viewAnnotation);
$this->templating->expects($this->any())
->method('render')
->will($this->returnValue('foo'));
$view = new View();
$view->setStatusCode($viewCode);
$view->setData('foo');
$event = $this->getResponseEvent($request, $view);
$response = new Response();
$event->expects($this->any())
->method('setResponse')
->will($this->returnCallback(function ($r) use (&$response) {
$response = $r;
}));
$this->listener->onKernelView($event);
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
$this->assertSame($expectedCode, $response->getStatusCode());
}
public static function serializerEnableMaxDepthChecksProvider()
{
return [
[false, null],
[true, 0],
];
}
/**
* @dataProvider serializerEnableMaxDepthChecksProvider
*/
public function testSerializerEnableMaxDepthChecks($enableMaxDepthChecks, $expectedMaxDepth)
{
$this->createViewResponseListener(['json' => true]);
$viewAnnotation = new ViewAnnotation([]);
$viewAnnotation->setSerializerEnableMaxDepthChecks($enableMaxDepthChecks);
$request = new Request();
$request->setRequestFormat('json');
$request->attributes->set('_view', $viewAnnotation);
$this->templating->expects($this->any())
->method('render')
->will($this->returnValue('foo'));
$view = new View();
$event = $this->getResponseEvent($request, $view);
$this->listener->onKernelView($event);
$context = $view->getContext();
$maxDepth = $context->getMaxDepth();
$this->assertEquals($expectedMaxDepth, $maxDepth);
}
public function getDataForDefaultVarsCopy()
{
return [
[false],
[true],
];
}
/**
* @dataProvider getDataForDefaultVarsCopy
*/
public function testViewWithNoCopyDefaultVars($populateDefaultVars)
{
$this->createViewResponseListener(['html' => true]);
$request = new Request();
$request->attributes->set('customer', 'A person goes here');
$view = View::create();
$viewAnnotation = new ViewAnnotation([]);
$viewAnnotation->setOwner([new FooController(), 'viewAction']);
$viewAnnotation->setPopulateDefaultVars($populateDefaultVars);
$request->attributes->set('_template', $viewAnnotation);
$event = $this->getResponseEvent($request, $view);
$this->listener->onKernelView($event);
$data = $view->getData();
if ($populateDefaultVars) {
$this->assertArrayHasKey('customer', $data);
$this->assertEquals('A person goes here', $data['customer']);
} else {
$this->assertNull($data);
}
}
protected function setUp()
{
$this->router = $this->getMockBuilder('Symfony\Component\Routing\RouterInterface')->getMock();
$this->serializer = $this->getMockBuilder('FOS\RestBundle\Serializer\Serializer')->getMock();
$this->templating = $this->getMockBuilder('Symfony\Bundle\FrameworkBundle\Templating\EngineInterface')->getMock();
$this->requestStack = new RequestStack();
}
private function createViewResponseListener($formats = null)
{
$this->viewHandler = new ViewHandler($this->router, $this->serializer, $this->templating, $this->requestStack, $formats);
$this->listener = new ViewResponseListener($this->viewHandler, false);
}
}
class FooController
{
/**
* @see testOnKernelView()
*/
public function onKernelViewAction($foo, $halli)
{
}
/**
* @see testViewWithNoCopyDefaultVars()
*/
public function viewAction($customer)
{
}
}
Tests/EventListener/ZoneMatcherListenerTest.php 0000666 00000005027 13052362416 0015745 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\EventListener;
use FOS\RestBundle\EventListener\ZoneMatcherListener;
use FOS\RestBundle\FOSRestBundle;
use Symfony\Component\HttpFoundation\Request;
class ZoneMatcherListenerTest extends \PHPUnit_Framework_TestCase
{
public function testNoRequestMatcher()
{
$request = new Request();
$event = $this->getGetResponseEvent($request);
$listener = new ZoneMatcherListener();
$listener->onKernelRequest($event);
$this->assertTrue($request->attributes->has(FOSRestBundle::ZONE_ATTRIBUTE));
}
public function testWithRequestMatcherMatch()
{
$request = new Request();
$event = $this->getGetResponseEvent($request);
$requestMatcher = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestMatcherInterface')->getMock();
$requestMatcher
->expects($this->once())
->method('matches')
->with($request)
->will($this->returnValue(true));
$listener = new ZoneMatcherListener();
$listener->addRequestMatcher($requestMatcher);
$listener->onKernelRequest($event);
$this->assertTrue($request->attributes->has(FOSRestBundle::ZONE_ATTRIBUTE));
}
public function testWithRequestMatcherNoMatch()
{
$request = new Request();
$event = $this->getGetResponseEvent($request);
$requestMatcher = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestMatcherInterface')->getMock();
$requestMatcher
->expects($this->once())
->method('matches')
->with($request)
->will($this->returnValue(false));
$listener = new ZoneMatcherListener();
$listener->addRequestMatcher($requestMatcher);
$listener->onKernelRequest($event);
$this->assertFalse($request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE));
}
private function getGetResponseEvent(Request $request)
{
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
->disableOriginalConstructor()
->getMock();
$event
->expects($this->once())
->method('getRequest')
->will($this->returnValue($request));
return $event;
}
}
Tests/FOSRestBundleTest.php 0000666 00000002004 13052362416 0011640 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests;
use FOS\RestBundle\FOSRestBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* FOSRestBundle test.
*
* @author Eriksen Costa
*/
class FOSRestBundleTest extends \PHPUnit_Framework_TestCase
{
public function testBuild()
{
$container = $this->getMockBuilder(ContainerBuilder::class)
->setMethods(['addCompilerPass'])
->getMock();
$container->expects($this->exactly(5))
->method('addCompilerPass')
->with($this->isInstanceOf(CompilerPassInterface::class));
$bundle = new FOSRestBundle();
$bundle->build($container);
}
}
Tests/Fixtures/Context/Adapter/SerializerAwareAdapter.php 0000666 00000001354 13052362416 0017577 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Context\Adapter;
use FOS\RestBundle\Context\Adapter\DeserializationContextAdapterInterface;
use FOS\RestBundle\Context\Adapter\SerializationContextAdapterInterface;
use FOS\RestBundle\Context\Adapter\SerializerAwareInterface;
/**
* {@inheritdoc}
*
* @author Ener-Getick
*/
interface SerializerAwareAdapter extends SerializationContextAdapterInterface, DeserializationContextAdapterInterface, SerializerAwareInterface
{
}
Tests/Fixtures/Controller/AnnotatedConditionalUsersController.php 0000666 00000010454 13052362416 0021514 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Controller\Annotations\Delete;
use FOS\RestBundle\Controller\Annotations\Get;
use FOS\RestBundle\Controller\Annotations\Head;
use FOS\RestBundle\Controller\Annotations\Link;
use FOS\RestBundle\Controller\Annotations\NoRoute;
use FOS\RestBundle\Controller\Annotations\Options;
use FOS\RestBundle\Controller\Annotations\Patch;
use FOS\RestBundle\Controller\Annotations\Post;
use FOS\RestBundle\Controller\Annotations\Put;
use FOS\RestBundle\Controller\Annotations\Route;
use FOS\RestBundle\Controller\Annotations\Unlink;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FOS\RestBundle\Tests\Fixtures\User;
class AnnotatedConditionalUsersController extends Controller
{
/**
* [OPTIONS] /users.
*/
public function optionsUsersAction()
{
}
/**
* [OPTIONS] /users.
*
* @Options
*/
public function boptionsUsersAction()
{
}
/**
* [GET] /users.
*/
public function getUsersAction()
{
}
/**
* [GET] /users/{slug}.
*
* @Route(requirements={"slug" = "[a-z]+"})
*/
public function getUserAction(User $slug)
{
}
/**
* [PATCH] /users.
*
* @Patch
*/
public function patchUsersAction()
{
}
/**
* [GET] /users/{slug}.
*
* @Patch(requirements={"slug" = "[a-z]+"})
*/
public function patchUserAction($slug)
{
}
/**
* [GET] /users/{slug}/comments/{id}.
*
* @Route(requirements={"slug" = "[a-z]+", "id" = "\d+"})
*/
public function getUserCommentAction($slug, $id)
{
}
/**
* [POST] /users/{slug}/rate.
*
* @Post(requirements={"slug" = "[a-z]+"})
*/
public function rateUserAction($slug)
{
}
/**
* [PATCH, POST] /users/{slug}/rate_comment/{id}.
*
* @Route("/users/{slug}/rate_comment/{id}", requirements={"slug" = "[a-z]+", "id" = "\d+"}, methods={"PATCH", "POST"})
*/
public function rateUserCommentAction($slug, $id)
{
}
/**
* [GET] /users/{slug}/bget.
*
* @Get
*/
public function bgetUserAction($slug)
{
}
/**
* [POST] /users/{slug}/bpost.
*
* @Post
*/
public function bpostUserAction($slug)
{
}
/**
* [PUT] /users/{slug}/bput.
*
* @Put
*/
public function bputUserAction($slug)
{
}
/**
* [DELETE] /users/{slug}/bdel.
*
* @Delete
*/
public function bdelUserAction($slug)
{
}
/**
* [HEAD] /users/{slug}/bhead.
*
* @Head
*/
public function bheadUserAction($slug)
{
}
/**
* [LINK] /users/{slug}/blink.
*
* @Link
*/
public function bLinkUserAction($slug)
{
}
/**
* [UNLINK] /users/{slug}/bunlink.
*
* @Unlink
*/
public function bunlinkUserAction($slug)
{
}
/**
* @NoRoute
*/
public function splitUserAction($slug)
{
}
/**
* [GET] /users/{slug}/custom.
*
* @Route(requirements={"_format"="custom"})
*/
public function customUserAction($slug)
{
}
/**
* [GET, HEAD] /users/{slug}/conditional.
*
* @Get(condition="context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'")
*/
public function conditionalUserAction()
{
}
/**
* @Link("/users1", name="_a_link_method", condition="context.getMethod() in ['LINK'] and request.headers.get('User-Agent') matches '/firefox/i'")
* @Get("/users2", name="_a_get_method", condition="context.getMethod() in ['GET'] and request.headers.get('User-Agent') matches '/firefox/i'")
* @Get("/users3", name="_an_other_get_method")
* @Post("/users4", name="_a_post_method", condition="context.getMethod() in ['POST'] and request.headers.get('User-Agent') matches '/firefox/i'")
*/
public function multiplegetUsersAction()
{
}
}
Tests/Fixtures/Controller/AnnotatedNonPluralizedArticleController.php 0000666 00000002063 13052362416 0022316 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\FOSRestController;
/**
* @Rest\RouteResource("Article", pluralize=false)
*/
class AnnotatedNonPluralizedArticleController extends FOSRestController
{
/**
* [GET] /article.
*/
public function cgetAction()
{
}
/**
* [GET] /article/{slug}.
*
* @param $slug
*/
public function getAction($slug)
{
}
/**
* [GET] /article/{slug}/comment.
*
* @param $slug
*/
public function cgetCommentAction($slug)
{
}
/**
* [GET] /article/{slug}/comment/{slug}.
*
* @param $slug
* @param $comment
*/
public function getCommentAction($slug, $comment)
{
}
}
Tests/Fixtures/Controller/AnnotatedPrefixedController.php 0000666 00000001336 13052362416 0017774 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Controller\Annotations\Prefix;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Donald Tyler
* @Prefix("aprefix")
*/
class AnnotatedPrefixedController extends Controller
{
/**
* [GET] /aprefix/something.{_format}.
*/
public function getSomethingAction(Request $request)
{
}
}
Tests/Fixtures/Controller/AnnotatedUsersController.php 0000666 00000012314 13052362416 0017325 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Controller\Annotations\Copy;
use FOS\RestBundle\Controller\Annotations\Delete;
use FOS\RestBundle\Controller\Annotations\Get;
use FOS\RestBundle\Controller\Annotations\Head;
use FOS\RestBundle\Controller\Annotations\Link;
use FOS\RestBundle\Controller\Annotations\Lock;
use FOS\RestBundle\Controller\Annotations\Mkcol;
use FOS\RestBundle\Controller\Annotations\Move;
use FOS\RestBundle\Controller\Annotations\NoRoute;
use FOS\RestBundle\Controller\Annotations\Options;
use FOS\RestBundle\Controller\Annotations\Patch;
use FOS\RestBundle\Controller\Annotations\Post;
use FOS\RestBundle\Controller\Annotations\PropFind;
use FOS\RestBundle\Controller\Annotations\PropPatch;
use FOS\RestBundle\Controller\Annotations\Put;
use FOS\RestBundle\Controller\Annotations\Route;
use FOS\RestBundle\Controller\Annotations\Unlink;
use FOS\RestBundle\Controller\Annotations\Unlock;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class AnnotatedUsersController extends Controller
{
/**
* @Options
*/
public function optionsUsersAction()
{
}
/**
* [COPY] /users/{id}.
*
* @param $id
*
* @Copy()
*/
public function copyUserAction($id)
{
}
/**
* [PROPFIND] /users/{id}/props/{property}.
*
* @param $id
* @param $property
*
* @PropFind()
*/
public function propfindUserPropsAction($id, $property)
{
}
/**
* @PropPatch()
*/
public function proppatchUserPropsAction($id, $property)
{
}
/**
* @Move()
*/
public function moveUserAction($id)
{
}
/**
* @Mkcol()
*/
public function mkcolUsersAction()
{
}
/**
* @Lock()
*/
public function lockUserAction($slug)
{
}
/**
* @Unlock()
*/
public function unlockUserAction($slug)
{
}
public function boptionsUsersAction()
{
}
/**
* [GET] /users.
*/
public function getUsersAction()
{
}
/**
* [GET] /users/{slug}.
*
* @Route(requirements={"slug" = "[a-z]+"})
*/
public function getUserAction($slug)
{
}
/**
* [GET] /users/{slug}/posts/{id}.
*
* @Route(requirements={"slug" = "[a-z]+", "id" = "\d+"}, options={"expose"=true})
*/
public function getUserPostAction($slug, $id)
{
}
/**
* [PATCH] /users.
*
* @Patch
*/
public function patchUsersAction()
{
}
/**
* [PATCH] /users/{slug}.
*
* @Patch(requirements={"slug" = "[a-z]+"})
*/
public function patchUserAction($slug)
{
}
/**
* [GET] /users/{slug}/comments/{id}.
*
* @Route(requirements={"slug" = "[a-z]+", "id" = "\d+"})
*/
public function getUserCommentAction($slug, $id)
{
}
/**
* [POST] /users/{slug}/rate.
*
* @Post(requirements={"slug" = "[a-z]+"})
*/
public function rateUserAction($slug)
{
}
/**
* [PATCH, POST] /users/{slug}/rate_comment/{id}.
*
* @Route("/users/{slug}/rate_comment/{id}", requirements={"slug" = "[a-z]+", "id" = "\d+"}, methods={"PATCH", "POST"})
*/
public function rateUserCommentAction($slug, $id)
{
}
/**
* [GET] /users/{slug}/bget.
*
* @Get
*/
public function bgetUserAction($slug)
{
}
/**
* [POST] /users/{slug}/bpost.
*
* @Post
*/
public function bpostUserAction($slug)
{
}
/**
* [PUT] /users/{slug}/bput.
*
* @Put
*/
public function bputUserAction($slug)
{
}
/**
* [DELETE] /users/{slug}/bdel.
*
* @Delete
*/
public function bdelUserAction($slug)
{
}
/**
* [HEAD] /users/{slug}/bhead.
*
* @Head
*/
public function bheadUserAction($slug)
{
}
/**
* [LINK] /users/{slug}/blink.
*
* @Link
*/
public function bLinkUserAction($slug)
{
}
/**
* [UNLINK] /users/{slug}/bunlink.
*
* @Unlink
*/
public function bunlinkUserAction($slug)
{
}
/**
* @NoRoute
*/
public function splitUserAction($slug)
{
}
/**
* [GET] /users/{slug}/custom.
*
* @Route(requirements={"_format"="custom"})
*/
public function customUserAction($slug)
{
}
/**
* @Link("/users1", name="_a_link_method")
* @Get("/users2", name="_a_get_method")
* @Get("/users3", name="_an_other_get_method")
* @Post("/users4", name="_a_post_method")
*/
public function multiplegetUsersAction()
{
}
/**
* @POST("/users1/{foo}", name="post_users_foo", options={ "method_prefix" = false })
* @POST("/users2/{foo}", name="post_users_bar", options={ "method_prefix" = false })
*/
public function multiplepostUsersAction()
{
}
}
Tests/Fixtures/Controller/AnnotatedVersionUserController.php 0000666 00000001737 13052362416 0020517 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Controller\Annotations\Version;
use FOS\RestBundle\Controller\Annotations\Get;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
/**
* @Version({"v1", "v3"})
*/
class AnnotatedVersionUserController extends Controller
{
/**
* [GET, HEAD] /users/{slug}/v2.
*
* @Get()
*/
public function v1UserAction()
{
}
/**
* [GET, HEAD] /users/{slug}/conditional.
*
* @Get(condition="context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'")
*/
public function conditionalUserAction()
{
}
public function v3UserAction()
{
}
}
Tests/Fixtures/Controller/ArticleController.php 0000666 00000010337 13052362416 0015754 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Controller\ControllerTrait;
use FOS\RestBundle\Routing\ClassResourceInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Validator\ConstraintViolationList;
class ArticleController extends Controller implements ClassResourceInterface
{
use ControllerTrait;
/**
* [OPTIONS] /articles.
*/
public function optionsAction()
{
}
/**
* [GET] /articles.
*/
public function cgetAction(ConstraintViolationList $errors)
{
}
/**
* [GET] /articles/{slug}.
*
* @param $slug
*/
public function getAction($slug)
{
}
/**
* [POST] /articles.
*/
public function cpostAction()
{
}
/**
* [PATCH] /articles.
*/
public function cpatchAction()
{
}
/**
* [PUT] /articles/{slug}.
*
* @param $slug
*/
public function putAction($slug)
{
}
/**
* [PATCH] /articles/{slug}.
*
* @param $slug
*/
public function patchAction($slug)
{
}
/**
* [LOCK] /articles/{slug}.
*
* @param $slug
*/
public function lockAction($slug)
{
}
/**
* [GET] /articles/{slug}/comments.
*
* @param $slug
*/
public function getCommentsAction($slug)
{
}
/**
* [GET] /articles/{slug}/comments/{id}.
*
* @param $slug
* @param $id
*/
public function getCommentAction($slug, $id)
{
}
/**
* [DELETE] /articles/{slug}/comments/{id}.
*
* @param $slug
* @param $id
*/
public function deleteCommentAction($slug, $id)
{
}
/**
* [PATCH] /articles/{slug}/ban.
*
* @param $slug
* @param $id
*/
public function banAction($slug, $id)
{
}
/**
* [POST] /articles/{slug}/comments/{id}/vote.
*
* @param $slug
* @param $id
*/
public function postCommentVoteAction($slug, $id)
{
}
/**
* NO route.
*/
public function _articlebarAction()
{
}
/**
* [GET] /articles/check_articlename.
*/
public function check_articlenameAction()
{
}
// conventional HATEOAS actions below
/**
* [GET] /articles/new.
*/
public function newAction()
{
}
/**
* [GET] /article/{slug}/edit.
*
* @param $slug
*/
public function editAction($slug)
{
}
/**
* [GET] /article/{slug}/remove.
*
* @param $slug
*/
public function removeAction($slug)
{
}
/**
* [GET] /articles/{slug}/comments/new.
*
* @param $slug
*/
public function newCommentAction($slug)
{
}
/**
* [GET] /articles/{slug}/comments/{id}/edit.
*
* @param $slug
* @param $id
*/
public function editCommentAction($slug, $id)
{
}
/**
* [GET] /articles/{slug}/comments/{id}/remove.
*
* @param $slug
* @param $id
*/
public function removeCommentAction($slug, $id)
{
}
/**
* [PATCH] /articles/{articleId}/comments/{commentId}/hide.
*
* @param $articleId
* @param $commentId
*/
public function hideCommentAction($articleId, $commentId)
{
}
// Parameter of type Request should be ignored
/**
* [GET] /articles/{slug}/votes.
*
* @param Request $request
* @param $slug
*/
public function getVotesAction(Request $request, $slug)
{
}
/**
* [GET] /articles/{slug}/votes/{id}.
*
* @param Request $request
* @param $slug
* @param $id
*/
public function getVoteAction(Request $request, $slug, $id)
{
}
/**
* [GET] /articles/{slug}/foos.
*
* @param $slug
* @param Request $request
*/
public function getFoosAction($slug, Request $request)
{
}
}
Tests/Fixtures/Controller/Directory/NoClass.php 0000666 00000000055 13052362416 0015627 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller\Directory;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class UserTopicCommentsController extends Controller
{
public function getCommentsAction($slug, $title)
{
}
// [GET] /users/{slug}/topics/{title}/comments
public function putCommentAction($slug, $title, $id)
{
}
// [PUT] /users/{slug}/topics/{title}/comments/{id}
}
Tests/Fixtures/Controller/Directory/UserTopicsController.php 0000666 00000001326 13052362416 0020433 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller\Directory;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class UserTopicsController extends Controller
{
public function getTopicsAction($slug)
{
}
// [GET] /users/{slug}/topics
public function getTopicAction($slug, $title)
{
}
// [GET] /users/{slug}/topics/{title}
public function putTopicAction($slug, $title)
{
}
// [PUT] /users/{slug}/topics/{title}
}
Tests/Fixtures/Controller/Directory/UsersController.php 0000666 00000001323 13052362416 0017431 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller\Directory;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class UsersController extends Controller
{
public function getUsersAction()
{
}
// [GET] /users
public function getUserAction($slug)
{
}
// [GET] /users/{slug}
public function postUsersAction()
{
}
// [POST] /users
public function putUserAction($slug)
{
}
// [PUT] /users/{slug}
}
Tests/Fixtures/Controller/InformationController.php 0000666 00000001236 13052362416 0016654 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Controller\ControllerTrait;
use FOS\RestBundle\Routing\ClassResourceInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class InformationController extends Controller implements ClassResourceInterface
{
use ControllerTrait;
/**
* [GET] /information.
*/
public function cgetAction()
{
}
}
Tests/Fixtures/Controller/MediaController.php 0000666 00000001413 13052362416 0015403 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Controller\ControllerTrait;
use FOS\RestBundle\Routing\ClassResourceInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MediaController extends Controller implements ClassResourceInterface
{
use ControllerTrait;
/**
* [GET] /media.
*/
public function cgetAction()
{
}
/**
* [GET] /media/{slug}.
*
* @param $slug
*/
public function getAction($slug)
{
}
}
Tests/Fixtures/Controller/OrdersController.php 0000666 00000001724 13052362416 0015627 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class OrdersController extends Controller
{
// conventional HATEOAS action after REST action
public function newFoosAction()
{
}
// [GET] /foos/new
public function getFoosAction()
{
}
// [GET] /foos
// conventional HATEOAS action before REST action
/**
* [GET] /bars/new.
*/
public function newBarsAction()
{
}
/**
* [GET] /bars/custom.
*/
public function getBarsCustomAction()
{
}
/**
* [GET] /bars/{slug}.
*
* @param $slug
*/
public function getBarsAction($slug)
{
}
}
Tests/Fixtures/Controller/ParamFetcherController.php 0000666 00000002600 13052362416 0016724 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Request\ParamFetcher;
use FOS\RestBundle\Request\ParamFetcherInterface;
/**
* Fixture for testing whether the ParamFetcher can be injected into
* a type-hinted controller method.
*/
class ParamFetcherController
{
/**
* Make sure the ParamFetcher can be injected by name.
*/
public function byNameAction($paramFetcher)
{
}
/**
* Make sure the ParamFetcher can be injected according to the typehint.
*/
public function byTypeAction(ParamFetcher $pf)
{
}
/**
* Make sure the ParamFetcher can be injected if the typehint is for
* the interface.
*/
public function byInterfaceAction(ParamFetcherInterface $pfi)
{
}
/**
* Make sure the ParamFetcher can be set as a request attribute even if
* there is no controller parameter to receive it.
*/
public function notProvidedAction()
{
}
/**
* Make sure the ParamFetcher can be set for controller which are used as invokable.
*/
public function __invoke(ParamFetcher $pfInvokable)
{
}
}
Tests/Fixtures/Controller/ParamsAnnotatedController.php 0000666 00000002415 13052362416 0017450 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Request\ParamFetcher;
use FOS\RestBundle\Controller\Annotations\RequestParam;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\FileParam;
use Symfony\Component\Validator\Constraints\NotNull;
/**
* Extract from the documentation.
*
* @author Ener-Getick
*/
class ParamsAnnotatedController
{
/**
* @QueryParam(name="page", requirements="\d+", default="1", description="Page of the overview.")
* @RequestParam(name="byauthor", requirements="[a-z]+", description="by author", incompatibles={"search"}, strict=true)
* @QueryParam(name="filters", map=true, requirements=@NotNull)
* @FileParam(name="avatar", requirements={"mimeTypes"="application/json"}, image=true)
* @FileParam(name="foo", requirements=@NotNull, strict=false)
*
* @param ParamFetcher $paramFetcher
*/
public function getArticlesAction(ParamFetcher $paramFetcher)
{
}
}
Tests/Fixtures/Controller/ReportController.php 0000666 00000002125 13052362416 0015640 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\ControllerTrait;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
/**
* Class ReportController.
*/
class ReportController extends Controller
{
use ControllerTrait;
public function getBillingSpendingsAction()
{
}
/**
* @Rest\Get("billing/spendings/{campaign}")
*/
public function getBillingSpendingsByCampaignAction($campaign)
{
}
public function getBillingPaymentsAction()
{
}
public function getBillingEarningsAction()
{
}
/**
* @Rest\Get("billing/earnings/{platform}")
*/
public function getBillingEarningsByPlatformAction($platform)
{
}
public function getBillingWithdrawalsAction()
{
}
}
Tests/Fixtures/Controller/TypeHintedController.php 0000666 00000001730 13052362416 0016443 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Routing\ClassResourceInterface;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* @Rest\RouteResource("Article")
*/
class TypeHintedController implements ClassResourceInterface
{
public function cgetAction(Request $request)
{
}
public function cpostAction(MessageInterface $request)
{
}
public function getAction(Request $request, $id)
{
}
public function postAction(ServerRequestInterface $request, $id)
{
}
}
Tests/Fixtures/Controller/UserTopicCommentsController.php 0000666 00000003227 13052362416 0020014 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class UserTopicCommentsController extends Controller
{
/**
* [GET] /users/{slug}/topics/{title}/comments.
*
* @param $slug
* @param $title
*/
public function getCommentsAction($slug, $title)
{
}
/**
* [PUT] /users/{slug}/topics/{title}/comments/{id}.
*
* @param $slug
* @param $title
* @param $id
*/
public function putCommentAction($slug, $title, $id)
{
}
/**
* [POST] /users/{slug}/topics/{title}/comments/{id}/ban.
*
* @param $slug
* @param $title
* @param $id
*/
public function banCommentAction($slug, $title, $id)
{
}
// conventional HATEOAS actions below
/**
* [GET] /users/{slug}/topics/{title}/comments/new.
*
* @param $slug
* @param $title
*/
public function newCommentsAction($slug, $title)
{
}
/**
* [GET] /users/{slug}/topics/{title}/comments/edit.
*
* @param $slug
* @param $title
* @param $id
*/
public function editCommentAction($slug, $title, $id)
{
}
/**
* [GET] /users/{slug}/topics/{title}/comments/remove.
*
* @param $slug
* @param $title
* @param $id
*/
public function removeCommentAction($slug, $title, $id)
{
}
}
Tests/Fixtures/Controller/UserTopicsController.php 0000666 00000003113 13052362416 0016463 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class UserTopicsController extends Controller
{
/**
* [GET] /users/{slug}/topics.
*
* @param $slug
*/
public function getTopicsAction($slug)
{
}
/**
* [GET] /users/{slug}/topics/{title}.
*
* @param $slug
* @param $title
*/
public function getTopicAction($slug, $title)
{
}
/**
* [PUT] /users/{slug}/topics/{title}.
*
* @param $slug
* @param $title
*/
public function putTopicAction($slug, $title)
{
}
/**
* [POST] /users/{slug}/topics/{title}/hide.
*
* @param $slug
* @param $title
*/
public function hideTopicAction($slug, $title)
{
}
// conventional HATEOAS actions below
/**
* [GET] /users/{slug}/topics/new.
*
* @param $slug
*/
public function newTopicsAction($slug)
{
}
/**
* [GET] /users/{slug}/topics/{title}/edit.
*
* @param $slug
* @param $title
*/
public function editTopicAction($slug, $title)
{
}
/**
* [GET] /remove/{slug}/topics/{title}/remove.
*
* @param $slug
* @param $title
*/
public function removeTopicAction($slug, $title)
{
}
}
Tests/Fixtures/Controller/UsersController.php 0000666 00000011535 13052362416 0015473 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class UsersController extends Controller
{
public function copyUserAction($id)
{
}
/**
* [PROPFIND] /users/{id}/props/{property}.
*
* @param $id
* @param $property
*/
public function propfindUserPropsAction($id, $property)
{
}
/**
* [PROPPATCH] /users/{id}/props/{property}.
*
* @param $id
* @param $property
*/
public function proppatchUserPropsAction($id, $property)
{
}
/**
* [MOVE] /users/{id}.
*
* @param $id
*/
public function moveUserAction($id)
{
}
/**
* [MKCOL] /users.
*/
public function mkcolUsersAction()
{
}
/**
* [OPTIONS] /users.
*/
public function optionsUsersAction()
{
}
/**
* [GET] /users.
*/
public function getUsersAction()
{
}
/**
* [GET] /users/{slug}.
*
* @param $slug
*/
public function getUserAction($slug)
{
}
/**
* [POST] /users.
*/
public function postUsersAction()
{
}
/**
* [PATCH] /users.
*/
public function patchUsersAction()
{
}
/**
* [PUT] /users/{slug}.
*
* @param $slug
*/
public function putUserAction($slug)
{
}
/**
* [PATCH] /users/{slug}.
*
* @param $slug
*/
public function patchUserAction($slug)
{
}
/**
* [LOCK] /users/{slug}.
*
* @param $slug
*/
public function lockUserAction($slug)
{
}
public function unlockUserAction($slug)
{
}
// [PATCH] /users/{slug}/unlock
public function getUserCommentsAction($slug)
{
}
/**
* [GET] /users/{slug}/comments/{id}.
*
* @param $slug
* @param $id
*/
public function getUserCommentAction($slug, $id)
{
}
/**
* [DELETE] /users/{slug}/comments/{id}.
*
* @param $slug
* @param $id
*/
public function deleteUserCommentAction($slug, $id)
{
}
/**
* [PATCH] /users/{slug}/ban.
*
* @param $slug
* @param $id
*/
public function banUserAction($slug, $id)
{
}
/**
* [POST] /users/{slug}/comments/{id}/vote.
*
* @param $slug
* @param $id
*/
public function postUserCommentVoteAction($slug, $id)
{
}
/**
* NO route.
*/
public function _userbarAction()
{
}
/**
* [GET] /users/check_username.
*/
public function check_usernameUsersAction()
{
}
// conventional HATEOAS actions below
/**
* [GET] /users/new.
*/
public function newUsersAction()
{
}
/**
* [GET] /user/{slug}/edit.
*
* @param $slug
*/
public function editUserAction($slug)
{
}
/**
* [GET] /user/{slug}/remove.
*
* @param $slug
*/
public function removeUserAction($slug)
{
}
/**
* [GET] /users/{slug}/comments/new.
*
* @param $slug
*/
public function newUserCommentsAction($slug)
{
}
/**
* [GET] /users/{slug}/comments/{id}/edit.
*
* @param $slug
* @param $id
*/
public function editUserCommentAction($slug, $id)
{
}
/**
* [GET] /users/{slug}/comments/{id}/remove.
*
* @param $slug
* @param $id
*/
public function removeUserCommentAction($slug, $id)
{
}
/**
* [PATCH] /users/{userId}/comments/{commentId}/hide.
*
* @param $userId
* @param $commentId
*/
public function hideUserCommentAction($userId, $commentId)
{
}
/**
* [GET] /foos/{foo}/bars.
*
* @param $foo
*/
public function getFooBarsAction($foo)
{
}
// Parameter of type Request should be ignored
/**
* [GET] /users/{slug}/votes.
*
* @param Request $request
* @param $slug
*/
public function getUserVotesAction(Request $request, $slug)
{
}
/**
* [GET] /users/{slug}/votes/{id}.
*
* @param Request $request
* @param $slug
* @param $id
*/
public function getUserVoteAction(Request $request, $slug, $id)
{
}
/**
* [GET] /users/{slug}/foos.
*
* @param $slug
* @param Request $request
*/
public function getUserFoosAction($slug, Request $request)
{
}
/**
* [GET] /categories.
*/
public function getCategoriesAction()
{
}
}
Tests/Fixtures/Etalon/annotated_conditional_controller.yml 0000666 00000004733 13052362416 0020244 0 ustar 00 get_users:
path: /users.{_format}
controller: ::getUsersAction
methods: [GET]
get_user:
path: /users/{slug}.{_format}
controller: ::getUserAction
requirements: {slug: '[a-z]+'}
methods: [GET]
patch_users:
path: /users.{_format}
controller: ::patchUsersAction
methods: [PATCH]
patch_user:
path: /users/{slug}.{_format}
controller: ::patchUserAction
requirements: {slug: '[a-z]+'}
methods: [PATCH]
get_user_comment:
path: /users/{slug}/comments/{id}.{_format}
controller: ::getUserCommentAction
requirements: {slug: '[a-z]+', id: '\d+'}
methods: [GET]
rate_user:
path: /users/{slug}/rate.{_format}
controller: ::rateUserAction
requirements: {slug: '[a-z]+'}
methods: [POST]
rate_user_comment:
path: /users/{slug}/rate_comment/{id}.{_format}
controller: ::rateUserCommentAction
requirements: {slug: '[a-z]+', id: '\d+'}
methods: [PATCH, POST]
bget_user:
path: /users/{slug}/bget.{_format}
controller: ::bgetUserAction
methods: [GET]
bpost_user:
path: /users/{slug}/bpost.{_format}
controller: ::bpostUserAction
methods: [POST]
bput_user:
path: /users/{slug}/bput.{_format}
controller: ::bputUserAction
methods: [PUT]
bdel_user:
path: /users/{slug}/bdel.{_format}
controller: ::bdelUserAction
methods: [DELETE]
bhead_user:
path: /users/{slug}/bhead.{_format}
controller: ::bheadUserAction
methods: [HEAD]
conditional_user:
path: /users/conditional.{_format}
controller: ::conditionalUserAction
methods: [GET]
condition: "context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'"
multipleget_users_a_get_method:
path: /users2.{_format}
controller: ::multiplegetUsersAction
methods: [GET]
condition: "context.getMethod() in ['GET'] and request.headers.get('User-Agent') matches '/firefox/i'"
multipleget_users_an_other_get_method:
path: /users3.{_format}
controller: ::multiplegetUsersAction
methods: [GET]
multipleget_users_a_post_method:
path: /users4.{_format}
controller: ::multiplegetUsersAction
methods: [POST]
condition: "context.getMethod() in ['POST'] and request.headers.get('User-Agent') matches '/firefox/i'"
multipleget_users_a_link_method:
path: /users1.{_format}
controller: ::multiplegetUsersAction
methods: [LINK]
condition: "context.getMethod() in ['LINK'] and request.headers.get('User-Agent') matches '/firefox/i'"
Tests/Fixtures/Etalon/annotated_users_controller.yml 0000666 00000006160 13052362416 0017076 0 ustar 00 copy_user:
methods: [COPY]
path: /users/{id}.{_format}
controller: ::copyUserAction
propfind_user_props:
methods: [PROPFIND]
path: /users/{id}/props/{property}.{_format}
controller: ::propfindUserPropsAction
proppatch_user_props:
methods: [PROPPATCH]
path: /users/{id}/props/{property}.{_format}
controller: ::proppatchUserPropsAction
move_user:
methods: [MOVE]
path: /users/{id}.{_format}
controller: ::moveUserAction
mkcol_users:
methods: [MKCOL]
path: /users.{_format}
controller: ::mkcolUsersAction
lock_user:
methods: [LOCK]
path: /users/{slug}.{_format}
controller: ::lockUserAction
unlock_user:
methods: [UNLOCK]
path: /users/{slug}.{_format}
controller: ::unlockUserAction
get_users:
path: /users.{_format}
controller: ::getUsersAction
methods: [GET]
get_user:
path: /users/{slug}.{_format}
controller: ::getUserAction
requirements: {slug: '[a-z]+'}
methods: [GET]
patch_users:
path: /users.{_format}
controller: ::patchUsersAction
methods: [PATCH]
patch_user:
path: /users/{slug}.{_format}
controller: ::patchUserAction
requirements: {slug: '[a-z]+'}
methods: [PATCH]
get_user_comment:
path: /users/{slug}/comments/{id}.{_format}
controller: ::getUserCommentAction
requirements: {slug: '[a-z]+', id: '\d+'}
methods: [GET]
get_user_post:
path: /users/{slug}/posts/{id}.{_format}
controller: ::getUserPostAction
options:
expose: true
requirements: {slug: '[a-z]+', id: '\d+'}
methods: [GET]
rate_user:
path: /users/{slug}/rate.{_format}
controller: ::rateUserAction
requirements: {slug: '[a-z]+'}
methods: [POST]
rate_user_comment:
path: /users/{slug}/rate_comment/{id}.{_format}
controller: ::rateUserCommentAction
requirements: {slug: '[a-z]+', id: '\d+'}
methods: [PATCH, POST]
bget_user:
path: /users/{slug}/bget.{_format}
controller: ::bgetUserAction
methods: [GET]
bpost_user:
path: /users/{slug}/bpost.{_format}
controller: ::bpostUserAction
methods: [POST]
bput_user:
path: /users/{slug}/bput.{_format}
controller: ::bputUserAction
methods: [PUT]
bdel_user:
path: /users/{slug}/bdel.{_format}
controller: ::bdelUserAction
methods: [DELETE]
bhead_user:
path: /users/{slug}/bhead.{_format}
controller: ::bheadUserAction
methods: [HEAD]
multipleget_users_a_get_method:
path: /users2.{_format}
controller: ::multiplegetUsersAction
methods: [GET]
multipleget_users_an_other_get_method:
path: /users3.{_format}
controller: ::multiplegetUsersAction
methods: [GET]
multipleget_users_a_post_method:
path: /users4.{_format}
controller: ::multiplegetUsersAction
methods: [POST]
multipleget_users_a_link_method:
path: /users1.{_format}
controller: ::multiplegetUsersAction
methods: [LINK]
post_users_foo:
path: /users1/{foo}.{_format}
controller: ::multiplepostUsersAction
methods: [POST]
post_users_bar:
path: /users2/{foo}.{_format}
controller: ::multiplepostUsersAction
methods: [POST]
Tests/Fixtures/Etalon/annotated_version_controller.yml 0000666 00000001127 13052362416 0017420 0 ustar 00 v1_user:
path: /users/v1.{_format}
controller: ::v1UserAction
methods: [GET]
condition: "request.attributes.get('version') in ['v1', 'v3']"
conditional_user:
path: /users/conditional.{_format}
controller: ::conditionalUserAction
methods: [GET]
condition: "(request.attributes.get('version') in ['v1', 'v3']) and (context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i')"
v3_user:
path: /users/v3.{_format}
controller: ::v3UserAction
methods: [GET]
condition: "request.attributes.get('version') in ['v1', 'v3']"
Tests/Fixtures/Etalon/base_named_prefixed_reports_collection.yml 0000666 00000001572 13052362416 0021374 0 ustar 00 base_api_get_billing_spendings:
path: /base/billing/spendings.{_format}
controller: ::getBillingSpendingsAction
method: GET
base_api_get_billing_spendings_by_campaign:
path: /base/billing/spendings/{campaign}.{_format}
controller: ::getBillingSpendingsByCampaignAction
method: GET
base_api_get_billing_payments:
path: /base/billing/payments.{_format}
controller: ::getBillingPaymentsAction
method: GET
base_api_get_billing_earnings:
path: /base/billing/earnings.{_format}
controller: ::getBillingEarningsAction
method: GET
base_api_get_billing_earnings_by_platform:
path: /base/billing/earnings/{platform}.{_format}
controller: ::getBillingEarningsByPlatformAction
method: GET
base_api_get_billing_withdrawals:
path: /base/billing/withdrawals.{_format}
controller: ::getBillingWithdrawalsAction
method: GET
Tests/Fixtures/Etalon/named_prefixed_reports_collection.yml 0000666 00000001520 13052362416 0020373 0 ustar 00 api_get_billing_spendings:
path: /billing/spendings.{_format}
controller: ::getBillingSpendingsAction
methods: [GET]
api_get_billing_spendings_by_campaign:
path: /billing/spendings/{campaign}.{_format}
controller: ::getBillingSpendingsByCampaignAction
methods: [GET]
api_get_billing_payments:
path: /billing/payments.{_format}
controller: ::getBillingPaymentsAction
methods: [GET]
api_get_billing_earnings:
path: /billing/earnings.{_format}
controller: ::getBillingEarningsAction
methods: [GET]
api_get_billing_earnings_by_platform:
path: /billing/earnings/{platform}.{_format}
controller: ::getBillingEarningsByPlatformAction
methods: [GET]
api_get_billing_withdrawals:
path: /billing/withdrawals.{_format}
controller: ::getBillingWithdrawalsAction
methods: [GET]
Tests/Fixtures/Etalon/prefixed_users_collection.yml 0000666 00000005603 13052362416 0016700 0 ustar 00 get_users:
methods: [GET]
path: /resources/all/users.{_format}
controller: ::getUsersAction
get_user:
methods: [GET]
path: /resources/all/users/{slug}.{_format}
controller: ::getUserAction
post_users:
methods: [POST]
path: /resources/all/users.{_format}
controller: ::postUsersAction
patch_users:
methods: [PATCH]
path: /resources/all/users.{_format}
controller: ::patchUsersAction
put_user:
methods: [PUT]
path: /resources/all/users/{slug}.{_format}
controller: ::putUserAction
patch_user:
methods: [PATCH]
path: /resources/all/users/{slug}.{_format}
controller: ::patchUserAction
lock_user:
methods: [LOCK]
path: /resources/all/users/{slug}.{_format}
controller: ::lockUserAction
get_user_comments:
methods: [GET]
path: /resources/all/users/{slug}/comments.{_format}
controller: ::getUserCommentsAction
get_user_comment:
methods: [GET]
path: /resources/all/users/{slug}/comments/{id}.{_format}
controller: ::getUserCommentAction
delete_user_comment:
methods: [DELETE]
path: /resources/all/users/{slug}/comments/{id}.{_format}
controller: ::deleteUserCommentAction
new_users:
methods: [GET]
path: /resources/all/users/new.{_format}
controller: ::newUsersAction
new_user_comments:
methods: [GET]
path: /resources/all/users/{slug}/comments/new.{_format}
controller: ::newUserCommentsAction
ban_user:
methods: [PATCH]
path: /resources/all/users/{slug}/ban.{_format}
controller: ::banUserAction
post_user_comment_vote:
methods: [POST]
path: /resources/all/users/{slug}/comments/{id}/votes.{_format}
controller: ::postUserCommentVoteAction
get_user_topics:
methods: [GET]
path: /resources/all/users/{slug}/topics.{_format}
controller: ::getTopics
new_user_topics:
methods: [GET]
path: /resources/all/users/{slug}/topics/new.{_format}
controller: ::newTopics
get_user_topic:
methods: [GET]
path: /resources/all/users/{slug}/topics/{title}.{_format}
controller: ::getTopic
put_user_topic:
methods: [PUT]
path: /resources/all/users/{slug}/topics/{title}.{_format}
controller: ::putTopic
hide_user_topic:
methods: [PATCH]
path: /resources/all/users/{slug}/topics/{title}/hide.{_format}
controller: ::hideTopic
test_get_user_topic_comments:
methods: [GET]
path: /resources/all/users/{slug}/topics/{title}/additional/comments.{_format}
controller: ::getComments
test_new_user_topic_comments:
methods: [GET]
path: /resources/all/users/{slug}/topics/{title}/additional/comments/new.{_format}
controller: ::newComments
test_put_user_topic_comment:
methods: [PUT]
path: /resources/all/users/{slug}/topics/{title}/additional/comments/{id}.{_format}
controller: ::putComment
test_ban_user_topic_comment:
methods: [PATCH]
path: /resources/all/users/{slug}/topics/{title}/additional/comments/{id}/ban.{_format}
controller: ::banComment
Tests/Fixtures/Etalon/resource_controller.yml 0000666 00000004253 13052362416 0015530 0 ustar 00 options_articles:
methods: [OPTIONS]
path: /articles.{_format}
controller: ::optionsAction
get_articles:
methods: [GET]
path: /articles.{_format}
controller: ::cgetAction
get_article:
methods: [GET]
path: /articles/{slug}.{_format}
controller: ::getAction
post_articles:
methods: [POST]
path: /articles.{_format}
controller: ::cpostAction
patch_articles:
methods: [PATCH]
path: /articles.{_format}
controller: ::cpatchAction
put_article:
methods: [PUT]
path: /articles/{slug}.{_format}
controller: ::putAction
patch_article:
methods: [PATCH]
path: /articles/{slug}.{_format}
controller: ::patchAction
lock_article:
methods: [LOCK]
path: /articles/{slug}.{_format}
controller: ::lockAction
get_article_comments:
methods: [GET]
path: /articles/{slug}/comments.{_format}
controller: ::getCommentsAction
get_article_comment:
methods: [GET]
path: /articles/{slug}/comments/{id}.{_format}
controller: ::getCommentAction
delete_article_comment:
methods: [DELETE]
path: /articles/{slug}/comments/{id}.{_format}
controller: ::deleteCommentAction
new_article:
methods: [GET]
path: /articles/new.{_format}
controller: ::newAction
new_article_comment:
methods: [GET]
path: /articles/{slug}/comments/new.{_format}
controller: ::newCommentAction
ban_article:
methods: [PATCH]
path: /articles/{slug}/ban.{_format}
controller: ::banAction
post_article_comment_vote:
methods: [POST]
path: /articles/{slug}/comments/{id}/votes.{_format}
controller: ::postCommentVoteAction
check_articlename_article:
methods: [GET]
path: /articles/check_articlename.{_format}
controller: ::check_articlenameAction
hide_article_comment:
methods: [PATCH]
path: /articles/{articleId}/comments/{commentId}/hide.{_format}
controller: ::hideCommentAction
get_article_votes:
methods: [GET]
path: /articles/{slug}/votes.{_format}
controller: ::getVotesAction
get_article_vote:
methods: [GET]
path: /articles/{slug}/votes/{id}.{_format}
controller: ::getVoteAction
get_article_foos:
methods: [GET]
path: /articles/{slug}/foos.{_format}
controller: ::getFoosAction
Tests/Fixtures/Etalon/users_collection.yml 0000666 00000005001 13052362416 0015002 0 ustar 00 get_users:
methods: [GET]
path: /users.{_format}
controller: ::getUsersAction
get_user:
methods: [GET]
path: /users/{slug}.{_format}
controller: ::getUserAction
post_users:
methods: [POST]
path: /users.{_format}
controller: ::postUsersAction
patch_users:
methods: [PATCH]
path: /users.{_format}
controller: ::patchUsersAction
put_user:
methods: [PUT]
path: /users/{slug}.{_format}
controller: ::putUserAction
patch_user:
methods: [PATCH]
path: /users/{slug}.{_format}
controller: ::patchUserAction
lock_user:
methods: [LOCK]
path: /users/{slug}.{_format}
controller: ::lockUserAction
get_user_comments:
methods: [GET]
path: /users/{slug}/comments.{_format}
controller: ::getUserCommentsAction
get_user_comment:
methods: [GET]
path: /users/{slug}/comments/{id}.{_format}
controller: ::getUserCommentAction
delete_user_comment:
methods: [DELETE]
path: /users/{slug}/comments/{id}.{_format}
controller: ::deleteUserCommentAction
new_users:
methods: [GET]
path: /users/new.{_format}
controller: ::newUsersAction
new_user_comments:
methods: [GET]
path: /users/{slug}/comments/new.{_format}
controller: ::newUserCommentsAction
ban_user:
methods: [PATCH]
path: /users/{slug}/ban.{_format}
controller: ::banUserAction
post_user_comment_vote:
methods: [POST]
path: /users/{slug}/comments/{id}/votes.{_format}
controller: ::postUserCommentVoteAction
get_user_topics:
methods: [GET]
path: /users/{slug}/topics.{_format}
controller: ::getTopics
new_user_topics:
methods: [GET]
path: /users/{slug}/topics/new.{_format}
controller: ::newTopics
get_user_topic:
methods: [GET]
path: /users/{slug}/topics/{title}.{_format}
controller: ::getTopic
put_user_topic:
methods: [PUT]
path: /users/{slug}/topics/{title}.{_format}
controller: ::putTopic
hide_user_topic:
methods: [PATCH]
path: /users/{slug}/topics/{title}/hide.{_format}
controller: ::hideTopic
get_user_topic_comments:
methods: [GET]
path: /users/{slug}/topics/{title}/comments.{_format}
controller: ::getComments
new_user_topic_comments:
methods: [GET]
path: /users/{slug}/topics/{title}/comments/new.{_format}
controller: ::newComments
put_user_topic_comment:
methods: [PUT]
path: /users/{slug}/topics/{title}/comments/{id}.{_format}
controller: ::putComment
ban_user_topic_comment:
methods: [PATCH]
path: /users/{slug}/topics/{title}/comments/{id}/ban.{_format}
controller: ::banComment
Tests/Fixtures/Etalon/users_controller.yml 0000666 00000005625 13052362416 0015046 0 ustar 00 copy_user:
methods: [COPY]
path: /users/{id}.{_format}
controller: ::copyUserAction
propfind_user_props:
methods: [PROPFIND]
path: /users/{id}/props/{property}.{_format}
controller: ::propfindUserPropsAction
proppatch_user_props:
methods: [PROPPATCH]
path: /users/{id}/props/{property}.{_format}
controller: ::proppatchUserPropsAction
move_user:
methods: [MOVE]
path: /users/{id}.{_format}
controller: ::moveUserAction
mkcol_users:
methods: [MKCOL]
path: /users.{_format}
controller: ::mkcolUsersAction
get_users:
methods: [GET]
path: /users.{_format}
controller: ::getUsersAction
get_user:
methods: [GET]
path: /users/{slug}.{_format}
controller: ::getUserAction
post_users:
methods: [POST]
path: /users.{_format}
controller: ::postUsersAction
patch_users:
methods: [PATCH]
path: /users.{_format}
controller: ::patchUsersAction
put_user:
methods: [PUT]
path: /users/{slug}.{_format}
controller: ::putUserAction
patch_user:
methods: [PATCH]
path: /users/{slug}.{_format}
controller: ::patchUserAction
lock_user:
methods: [LOCK]
path: /users/{slug}.{_format}
controller: ::lockUserAction
unlock_user:
methods: [UNLOCK]
path: /users/{slug}.{_format}
controller: ::unlockUserAction
get_user_comments:
methods: [GET]
path: /users/{slug}/comments.{_format}
controller: ::getUserCommentsAction
get_user_comment:
methods: [GET]
path: /users/{slug}/comments/{id}.{_format}
controller: ::getUserCommentAction
delete_user_comment:
methods: [DELETE]
path: /users/{slug}/comments/{id}.{_format}
controller: ::deleteUserCommentAction
new_users:
methods: [GET]
path: /users/new.{_format}
controller: ::newUsersAction
new_user_comments:
methods: [GET]
path: /users/{slug}/comments/new.{_format}
controller: ::newUserCommentsAction
ban_user:
methods: [PATCH]
path: /users/{slug}/ban.{_format}
controller: ::banUserAction
post_user_comment_vote:
methods: [POST]
path: /users/{slug}/comments/{id}/votes.{_format}
controller: ::postUserCommentVoteAction
check_username_users:
methods: [GET]
path: /users/check_username.{_format}
controller: ::check_usernameUsersAction
hide_user_comment:
methods: [PATCH]
path: /users/{userId}/comments/{commentId}/hide.{_format}
controller: ::hideUserCommentAction
get_foo_bars:
methods: [GET]
path: /foos/{foo}/bars.{_format}
controller: ::getFooBarsAction
get_user_votes:
methods: [GET]
path: /users/{slug}/votes.{_format}
controller: ::getUserVotesAction
get_user_vote:
methods: [GET]
path: /users/{slug}/votes/{id}.{_format}
controller: ::getUserVoteAction
get_user_foos:
methods: [GET]
path: /users/{slug}/foos.{_format}
controller: ::getUserFoosAction
get_categories:
methods: [GET]
path: /categories.{_format}
controller: ::getCategoriesAction
Tests/Fixtures/Files/big_file.txt 0000666 00000000030 13052362416 0013021 0 ustar 00 More than 20bytes .....
Tests/Fixtures/Files/valid_file.txt 0000666 00000000012 13052362416 0013357 0 ustar 00 -20 bytes
Tests/Fixtures/Routes/bad_format.yml 0000666 00000000132 13052362416 0013563 0 ustar 00 get_users:
path: /users
defaults:
_controller: FOSRestBundle:UsersController:getUsers
Tests/Fixtures/Routes/base_named_prefixed_reports_collection.yml 0000666 00000000166 13052362416 0021431 0 ustar 00 report:
type: rest
resource: named_prefixed_reports_collection.yml
prefix: /base
name_prefix: base_
Tests/Fixtures/Routes/empty.yml 0000666 00000000000 13052362416 0012615 0 ustar 00 Tests/Fixtures/Routes/invalid_route_parent.xml 0000666 00000000732 13052362416 0015707 0 ustar 00
Tests/Fixtures/Routes/invalid_route_parent.yml 0000666 00000000172 13052362416 0015706 0 ustar 00 user_topics:
type: rest
resource: FOS\RestBundle\Tests\Fixtures\Controller\UserTopicsController
parent: users
Tests/Fixtures/Routes/invalid_tag.xml 0000666 00000000676 13052362416 0013762 0 ustar 00
Tests/Fixtures/Routes/named_prefixed_reports_collection.yml 0000666 00000000167 13052362416 0020440 0 ustar 00 report:
type: rest
resource: 'FOS\RestBundle\Tests\Fixtures\Controller\ReportController'
name_prefix: api_
Tests/Fixtures/Routes/nonvalid.yml 0000666 00000000004 13052362416 0013275 0 ustar 00 foo
Tests/Fixtures/Routes/prefixed_users_collection.xml 0000666 00000001560 13052362416 0016734 0 ustar 00
Tests/Fixtures/Routes/prefixed_users_collection.yml 0000666 00000000701 13052362416 0016731 0 ustar 00 users:
type: rest
resource: FOS\RestBundle\Tests\Fixtures\Controller\UsersController
prefix: /resources/all
user_topics:
type: rest
resource: FOS\RestBundle\Tests\Fixtures\Controller\UserTopicsController
parent: users
user_topic_comments:
type: rest
resource: FOS\RestBundle\Tests\Fixtures\Controller\UserTopicCommentsController
parent: user_topics
prefix: /additional
name_prefix: test_
Tests/Fixtures/Routes/routes.xml 0000666 00000000706 13052362416 0013014 0 ustar 00
FOSRestBundle:UsersController:getUsers
Tests/Fixtures/Routes/routes.yml 0000666 00000000146 13052362416 0013013 0 ustar 00 get_users:
path: /users
defaults:
_controller: FOSRestBundle:UsersController:getUsers
Tests/Fixtures/Routes/routes_with_options_requirements_and_defaults.xml 0000666 00000001121 13052362416 0023126 0 ustar 00
FOSRestBundle:UsersController:getUsers[a-z]+home
Tests/Fixtures/Routes/routes_with_options_requirements_and_defaults.yml 0000666 00000000267 13052362416 0023141 0 ustar 00 users:
type: rest
resource: FOS\RestBundle\Tests\Fixtures\Controller\UsersController
options:
expose: true
requirements:
slug: "[a-z]+"
defaults:
slug: home
Tests/Fixtures/Routes/routes_with_pattern.yml 0000666 00000000151 13052362416 0015577 0 ustar 00 get_users:
pattern: /users
defaults:
_controller: FOSRestBundle:UsersController:getUsers
Tests/Fixtures/Routes/users_collection.xml 0000666 00000001427 13052362416 0015050 0 ustar 00
Tests/Fixtures/Routes/users_collection.yml 0000666 00000000550 13052362416 0015045 0 ustar 00 users:
type: rest
resource: FOS\RestBundle\Tests\Fixtures\Controller\UsersController
user_topics:
type: rest
resource: FOS\RestBundle\Tests\Fixtures\Controller\UserTopicsController
parent: users
user_topic_comments:
type: rest
resource: FOS\RestBundle\Tests\Fixtures\Controller\UserTopicCommentsController
parent: user_topics
Tests/Fixtures/User.php 0000666 00000000561 13052362416 0011116 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Fixtures;
/**
* @author Ener-Getick
*/
class User
{
}
Tests/Functional/AllowedMethodsTest.php 0000666 00000001270 13052362416 0014242 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional;
/**
* @author Ener-Getick
*/
class AllowedMethodsTest extends WebTestCase
{
public function testAllowHeader()
{
$client = $this->createClient(array('test_case' => 'AllowedMethodsListener'));
$client->request('POST', '/allowed-methods');
$this->assertEquals('GET, LOCK, POST, PUT', $client->getResponse()->headers->get('Allow'));
}
}
Tests/Functional/Bundle/TestBundle/Controller/AllowedMethodsController.php 0000666 00000000756 13052362416 0023063 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional\Bundle\TestBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class AllowedMethodsController
{
public function indexAction()
{
return new Response();
}
}
Tests/Functional/Bundle/TestBundle/Controller/Api/CommentController.php 0000666 00000001015 13052362416 0022250 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional\Bundle\TestBundle\Controller\Api;
use Symfony\Component\HttpFoundation\JsonResponse;
class CommentController
{
public function getCommentAction($id)
{
return new JsonResponse(array('id' => $id));
}
}
Tests/Functional/Bundle/TestBundle/Controller/Api/PostController.php 0000666 00000001007 13052362416 0021574 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional\Bundle\TestBundle\Controller\Api;
use Symfony\Component\HttpFoundation\JsonResponse;
class PostController
{
public function getPostAction($id)
{
return new JsonResponse(array('id' => $id));
}
}
Tests/Functional/Bundle/TestBundle/Controller/ArticleController.php 0000666 00000002511 13052362416 0021522 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional\Bundle\TestBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations\RouteResource;
use FOS\RestBundle\Controller\Annotations\View;
use FOS\RestBundle\Controller\FOSRestController;
/**
* @RouteResource("Article")
*/
class ArticleController extends FOSRestController
{
/**
* Create a new resource.
*
* @param Request $request
*
* @return View view instance
*
* @View()
*/
public function cpostAction(Request $request)
{
$view = $this->routeRedirectView('test_redirect_endpoint', array('name' => $request->request->get('name')));
$view->setTemplate('TestBundle:Article:foo.html.twig');
return $view;
}
/**
* Get list.
*
* @param Request $request
*
* @return View view instance
*
* @View()
*/
public function cgetAction(Request $request)
{
$view = $this->view();
$view->setTemplate('TestBundle:Article:foo.html.twig');
return $view;
}
}
Tests/Functional/Bundle/TestBundle/Controller/ParamFetcherController.php 0000666 00000004122 13052362416 0022500 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional\Bundle\TestBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\RequestParam;
use FOS\RestBundle\Request\ParamFetcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Validator\Constraints\IdenticalTo;
class ParamFetcherController extends FOSRestController
{
/**
* @RequestParam(name="raw", requirements=@IdenticalTo({"foo"="raw", "bar"="foo"}), default="invalid", strict=false)
* @RequestParam(name="map", map=true, requirements=@IdenticalTo({"foo"="map", "foobar"="foo"}), default="%invalid2% %%", strict=false)
* @RequestParam(name="bar", nullable=true, requirements="%bar%\ foo")
*/
public function paramsAction(ParamFetcherInterface $fetcher)
{
return new JsonResponse($fetcher->all());
}
/**
* @QueryParam(name="foo", default="invalid")
* @RequestParam(name="bar", default="%foo%")
*/
public function testAction(Request $request, ParamFetcherInterface $fetcher)
{
$paramsBefore = $fetcher->all();
$newRequest = new Request();
$newRequest->query = $request->query;
$newRequest->request = $request->request;
$newRequest->attributes->set('_controller', sprintf('%s::paramsAction', __CLASS__));
$response = $this->container->get('http_kernel')->handle($newRequest, HttpKernelInterface::SUB_REQUEST, false);
$paramsAfter = $fetcher->all(false);
return new JsonResponse(array(
'before' => $paramsBefore,
'during' => json_decode($response->getContent(), true),
'after' => $paramsAfter,
));
}
}
Tests/Functional/Bundle/TestBundle/Controller/RequestBodyParamConverterController.php 0000666 00000001721 13052362416 0025260 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional\Bundle\TestBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class RequestBodyParamConverterController extends Controller
{
public function putPostAction(Post $post, \Datetime $date)
{
return new Response($post->getName());
}
}
class Post
{
private $name;
private $body;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getBody()
{
return $this->body;
}
public function setBody($body)
{
$this->body = $body;
}
}
Tests/Functional/Bundle/TestBundle/Controller/SerializerErrorController.php 0000666 00000003076 13052362416 0023271 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional\Bundle\TestBundle\Controller;
use FOS\RestBundle\Controller\Annotations\View;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Validator\Constraints\NotBlank;
/**
* Controller to test serialization of various errors and exceptions.
*
* @author Florian Voutzinos
*/
class SerializerErrorController extends Controller
{
/**
* @View
*/
public function logicExceptionAction()
{
throw new \LogicException('Something bad happened.');
}
/**
* @View
*/
public function unknownExceptionAction()
{
throw new \OutOfBoundsException('Unknown exception message.');
}
/**
* @View
*/
public function invalidFormAction()
{
// BC hack for Symfony 2.7 where FormType's didn't yet get configured via the FQN
$formType = method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\TextType'
: 'text'
;
$form = $this->createFormBuilder(null, [
'csrf_protection' => false,
])->add('name', $formType, [
'constraints' => [new NotBlank()],
])->getForm();
$form->submit([]);
return $form;
}
}
Tests/Functional/Bundle/TestBundle/Controller/Version2Controller.php 0000666 00000001431 13052362416 0021646 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional\Bundle\TestBundle\Controller;
use FOS\RestBundle\Controller\Annotations\Get;
use FOS\RestBundle\Controller\Annotations\Version;
use FOS\RestBundle\Controller\Annotations\View;
/**
* @author Ener-Getick
*
* @Version({"1.2"})
*/
class Version2Controller
{
/**
* @View("TestBundle:Version:version.html.twig")
* @Get(path="/version")
*/
public function versionAction($version)
{
return array('version' => 'test annotation');
}
}
Tests/Functional/Bundle/TestBundle/Controller/VersionController.php 0000666 00000001170 13052362416 0021564 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional\Bundle\TestBundle\Controller;
use FOS\RestBundle\Controller\Annotations\View;
/**
* @author Ener-Getick
*/
class VersionController
{
/**
* @View("TestBundle:Version:version.html.twig")
*/
public function versionAction($version)
{
return array('version' => $version);
}
}
Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml 0000666 00000003260 13052362416 0020672 0 ustar 00 request_body_param_converter:
path: /body-converter
defaults: { _controller: TestBundle:RequestBodyParamConverter:putPost, date: 16-06-2016 }
test_serializer_error_exception:
path: /serializer-error/exception.{_format}
defaults: { _controller: TestBundle:SerializerError:logicException }
test_serializer_unknown_exception:
path: /serializer-error/unknown_exception.{_format}
defaults: { _controller: TestBundle:SerializerError:unknownException }
test_serializer_error_invalid_form:
path: /serializer-error/invalid-form.{_format}
defaults: { _controller: TestBundle:SerializerError:invalidForm }
# Must be defined before test_version
test_version2:
resource: FOS\RestBundle\Tests\Functional\Bundle\TestBundle\Controller\Version2Controller
type: rest
test_version:
path: /version
defaults: { _controller: TestBundle:Version:version }
requirements:
version: 2.1|3.4.2|2.3
test_param_fetcher:
path: /params
defaults: { _controller: TestBundle:ParamFetcher:params }
test_param_fetcher_test:
path: /params/test
defaults: { _controller: TestBundle:ParamFetcher:test }
test_view_response_listener:
resource: FOS\RestBundle\Tests\Functional\Bundle\TestBundle\Controller\ArticleController
type: rest
test_redirect_endpoint:
path: /hello/{name}
defaults: { _controller: TestBundle:Article:redirect }
test_allowed_methods1:
path: /allowed-methods
methods: ['GET', 'LOCK']
defaults: { _controller: TestBundle:AllowedMethods:index }
test_allowed_methods2:
path: /allowed-methods
methods: ['POST', 'PUT']
defaults: { _controller: TestBundle:AllowedMethods:index }
Tests/Functional/Bundle/TestBundle/Resources/views/Article/foo.html.twig 0000666 00000000004 13052362416 0022346 0 ustar 00 fooo Tests/Functional/Bundle/TestBundle/Resources/views/Version/version.html.twig 0000666 00000000016 13052362416 0023315 0 ustar 00 {{ version }}
Tests/Functional/Bundle/TestBundle/TestBundle.php 0000666 00000000630 13052362416 0016021 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional\Bundle\TestBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class TestBundle extends Bundle
{
}
Tests/Functional/ConfigurationTest.php 0000666 00000001555 13052362416 0014144 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional;
/**
* @author Ener-Getick
*/
class ConfigurationTest extends WebTestCase
{
private $client;
public function setUp()
{
$this->client = $this->createClient(['test_case' => 'Configuration']);
}
public function testConfiguration()
{
// Just create a client
}
public function testToolbar()
{
$this->client->request(
'GET',
'/_profiler/empty/search/results?limit=10',
[],
[],
['HTTP_Accept' => 'application/json']
);
}
}
Tests/Functional/ErrorWithTemplatingFormatTest.php 0000666 00000002544 13052362416 0016457 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional;
/**
* @author Ener-Getick
*/
class ErrorWithTemplatingFormatTest extends WebTestCase
{
public function testSerializeExceptionHtml()
{
$this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul');
$client = $this->createClient(['test_case' => 'Serializer', 'debug' => false]);
$client->request('GET', '/serializer-error/exception.html');
$this->assertContains('The server returned a "500 Internal Server Error".', $client->getResponse()->getContent());
$this->assertNotContains('Something bad happened', $client->getResponse()->getContent());
}
public function testSerializeExceptionHtmlInDebugMode()
{
$this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul');
$client = $this->createClient(['test_case' => 'Serializer', 'debug' => true]);
$client->request('GET', '/serializer-error/exception.html');
$this->assertContains('Something bad happened. (500 Internal Server Error)', $client->getResponse()->getContent());
}
}
Tests/Functional/ExceptionListenerTest.php 0000666 00000001750 13052362416 0014776 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional;
class ExceptionListenerTest extends WebTestCase
{
private $client;
public function setUp()
{
$this->client = $this->createClient(['test_case' => 'ExceptionListener']);
}
public function testBundleListenerHandlesExceptionsInRestZones()
{
$this->client->request('GET', '/api/test');
$this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
}
public function testSymfonyListenerHandlesExceptionsOutsideRestZones()
{
$this->client->request('GET', '/test');
$this->assertEquals('text/html; charset=UTF-8', $this->client->getResponse()->headers->get('Content-Type'));
}
}
Tests/Functional/ParamFetcherTest.php 0000666 00000004076 13052362416 0013677 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional;
/**
* @author Ener-Getick
*/
class ParamFetcherTest extends WebTestCase
{
private $validRaw = [
'foo' => 'raw',
'bar' => 'foo',
];
private $validMap = [
'foo' => 'map',
'foobar' => 'foo',
];
public function setUp()
{
$this->client = $this->createClient(['test_case' => 'ParamFetcher']);
}
public function testDefaultParameters()
{
$this->client->request('POST', '/params');
$this->assertEquals(['raw' => 'invalid', 'map' => 'invalid2 %', 'bar' => null], $this->getData());
}
public function testValidRawParameter()
{
$this->client->request('POST', '/params', ['raw' => $this->validRaw, 'map' => $this->validMap]);
$this->assertEquals(['raw' => $this->validRaw, 'map' => 'invalid2 %', 'bar' => null], $this->getData());
}
public function testValidMapParameter()
{
$map = [
'foo' => $this->validMap,
'bar' => $this->validMap,
];
$this->client->request('POST', '/params', ['raw' => 'bar', 'map' => $map, 'bar' => 'bar foo']);
$this->assertEquals(['raw' => 'invalid', 'map' => $map, 'bar' => 'bar foo'], $this->getData());
}
public function testWithSubRequests()
{
$this->client->request('POST', '/params/test?foo=quz', array('raw' => $this->validRaw));
$this->assertEquals(array(
'before' => array('foo' => 'quz', 'bar' => 'foo'),
'during' => array('raw' => $this->validRaw, 'map' => 'invalid2 %', 'bar' => null),
'after' => array('foo' => 'quz', 'bar' => 'foo'),
), $this->getData());
}
protected function getData()
{
return json_decode($this->client->getResponse()->getContent(), true);
}
}
Tests/Functional/RequestBodyParamConverterTest.php 0000666 00000002664 13052362416 0016456 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional;
class RequestBodyParamConverterTest extends WebTestCase
{
public function testRequestBodyIsDeserialized()
{
$client = $this->createClient(['test_case' => 'RequestBodyParamConverter']);
$client->request(
'POST',
'/body-converter',
[],
[],
['CONTENT_TYPE' => 'application/json'],
'{"name": "Post 1", "body": "This is a blog post"}'
);
$this->assertSame(200, $client->getResponse()->getStatusCode());
$this->assertSame('Post 1', $client->getResponse()->getContent());
}
/**
* @see https://github.com/FriendsOfSymfony/FOSRestBundle/issues/1237
*/
public function testTwigErrorPage()
{
$client = $this->createClient(['test_case' => 'RequestBodyParamConverter']);
$client->request('GET', '/_error/404.txt');
// Status code 200 as this page describes an error but is not the result of an error.
$this->assertEquals(200, $client->getResponse()->getStatusCode());
$this->assertContains('The server returned a "404 Not Found".', $client->getResponse()->getContent());
}
}
Tests/Functional/RoutingTest.php 0000666 00000002135 13052362416 0012757 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional;
class RoutingTest extends WebTestCase
{
private $client;
public function setUp()
{
$this->client = $this->createClient(array('test_case' => 'Routing'));
}
public function testPostControllerRoutesAreRegistered()
{
$this->client->request('GET', '/posts/1');
$this->assertSame(200, $this->client->getResponse()->getStatusCode());
$this->assertJsonStringEqualsJsonString('{ "id": 1 }', $this->client->getResponse()->getContent());
}
public function testCommentControllerRoutesAreRegistered()
{
$this->client->request('GET', '/comments/3');
$this->assertSame(200, $this->client->getResponse()->getStatusCode());
$this->assertJsonStringEqualsJsonString('{ "id": 3 }', $this->client->getResponse()->getContent());
}
}
Tests/Functional/SerializerErrorTest.php 0000666 00000011161 13052362416 0014452 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional;
/**
* Test class for serialization errors and exceptions.
*
* @author Florian Voutzinos
*/
class SerializerErrorTest extends WebTestCase
{
/**
* @dataProvider testCaseProvider
*/
public function testSerializeExceptionJson($testCase)
{
$this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul');
$client = $this->createClient(['test_case' => $testCase, 'debug' => false]);
$client->request('GET', '/serializer-error/exception.json');
$this->assertEquals('{"code":500,"message":"Something bad happened."}', $client->getResponse()->getContent());
}
public function testSerializeExceptionJsonWithDebug()
{
$this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul');
$client = $this->createClient(array('test_case' => 'Debug', 'debug' => false));
$client->request('GET', '/serializer-error/unknown_exception.json');
$this->assertEquals('{"code":500,"message":"Unknown exception message."}', $client->getResponse()->getContent());
}
public function testSerializeExceptionJsonWithoutDebug()
{
$this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul');
$client = $this->createClient(array('test_case' => 'Serializer', 'debug' => false));
$client->request('GET', '/serializer-error/unknown_exception.json');
$this->assertEquals('{"code":500,"message":"Internal Server Error"}', $client->getResponse()->getContent());
}
/**
* @dataProvider serializeExceptionXmlProvider
*/
public function testSerializeExceptionXml($testCase, $expectedContent)
{
$this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul');
$client = $this->createClient(['test_case' => $testCase, 'debug' => false]);
$client->request('GET', '/serializer-error/exception.xml');
$this->assertEquals($expectedContent, $client->getResponse()->getContent());
}
public function serializeExceptionXmlProvider()
{
$expectedSerializerContent = <<<'XML'
500Something bad happened.
XML;
$expectedJMSContent = <<<'XML'
500
XML;
return [
['Serializer', $expectedSerializerContent],
['JMSSerializer', $expectedJMSContent],
];
}
/**
* @dataProvider testCaseProvider
*/
public function testSerializeInvalidFormJson($testCase)
{
$client = $this->createClient(['test_case' => $testCase, 'debug' => false]);
$client->request('GET', '/serializer-error/invalid-form.json');
$this->assertEquals('{"code":400,"message":"Validation Failed","errors":{"children":{"name":{"errors":["This value should not be blank."]}}}}', $client->getResponse()->getContent());
}
public function testCaseProvider()
{
return [
['Serializer'],
['JMSSerializer'],
];
}
/**
* @dataProvider serializeInvalidFormXmlProvider
*/
public function testSerializeInvalidFormXml($testCase, $expectedContent)
{
$client = $this->createClient(['test_case' => $testCase, 'debug' => false]);
$client->request('GET', '/serializer-error/invalid-form.xml');
$this->assertEquals($expectedContent, $client->getResponse()->getContent());
}
public function serializeInvalidFormXmlProvider()
{
$expectedSerializerContent = <<<'XML'
400Validation FailedThis value should not be blank.
XML;
$expectedJMSContent = <<<'XML'
400
XML;
return [
['Serializer', $expectedSerializerContent],
['JMSSerializer', $expectedJMSContent],
];
}
}
Tests/Functional/VersionTest.php 0000666 00000004206 13052362416 0012756 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional;
/**
* @author Ener-Getick
*/
class VersionTest extends WebTestCase
{
private $client;
public function setUp()
{
$this->client = $this->createClient(['test_case' => 'Version']);
}
public function testVersionAnnotation()
{
$this->client->request(
'GET',
'/version?query_version=1.2'
);
$this->assertContains('test annotation', $this->client->getResponse()->getContent());
}
public function testCustomHeaderVersion()
{
$this->client->request(
'GET',
'/version?query_version=3.2',
[],
[],
['HTTP_Version-Header' => '2.1', 'HTTP_Accept' => 'application/vnd.foo.api+json;myversion=2.3']
);
$this->assertEquals('{"version":"2.1"}', $this->client->getResponse()->getContent());
}
public function testQueryVersion()
{
$this->client->request(
'GET',
'/version?query_version=3.2',
[],
[],
['HTTP_Accept' => 'text/html']
);
$this->assertEquals("3.2\n", $this->client->getResponse()->getContent());
}
public function testAcceptHeaderVersion()
{
$this->client->request(
'GET',
'/version?query_version=3.2',
[],
[],
['HTTP_Accept' => 'application/vnd.foo.api+json;myversion=2.3']
);
$this->assertEquals('{"version":"2.3"}', $this->client->getResponse()->getContent());
}
public function testDefaultVersion()
{
$this->client->request(
'GET',
'/version',
[],
[],
['HTTP_Accept' => 'application/json']
);
$this->assertEquals('{"version":"3.4.2"}', $this->client->getResponse()->getContent());
}
}
Tests/Functional/ViewResponseListenerTest.php 0000666 00000002537 13052362416 0015475 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional;
class ViewResponseListenerTest extends WebTestCase
{
public function testRedirect()
{
$client = $this->createClient(array('test_case' => 'ViewResponseListener'));
$client->request(
'POST',
'/articles',
array(),
array(),
array('CONTENT_TYPE' => 'application/json'),
'{"name": "Post 1", "body": "This is a blog post"}'
);
$this->assertSame(302, $client->getResponse()->getStatusCode());
$this->assertSame('http://localhost/hello/Post%201', $client->getResponse()->headers->get('location'));
$this->assertNotContains('fooo', $client->getResponse()->getContent());
}
public function testTemplateOverride()
{
$client = $this->createClient(array('test_case' => 'ViewResponseListener'));
$client->request(
'GET',
'/articles'
);
$this->assertSame(200, $client->getResponse()->getStatusCode());
$this->assertContains('fooo', $client->getResponse()->getContent());
}
}
Tests/Functional/WebTestCase.php 0000666 00000003046 13052362416 0012643 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Kernel;
class WebTestCase extends BaseWebTestCase
{
protected function deleteTmpDir($testCase)
{
if (!file_exists($dir = sys_get_temp_dir().'/'.Kernel::VERSION.'/'.$testCase)) {
return;
}
$fs = new Filesystem();
$fs->remove($dir);
}
protected static function getKernelClass()
{
require_once __DIR__.'/app/AppKernel.php';
return 'FOS\RestBundle\Tests\Functional\app\AppKernel';
}
protected static function createKernel(array $options = [])
{
$class = self::getKernelClass();
if (!isset($options['test_case'])) {
throw new \InvalidArgumentException('The option "test_case" must be set.');
}
$debug = isset($options['debug']) ? $options['debug'] : true;
return new $class(
$options['test_case'],
isset($options['root_config']) ? $options['root_config'] : 'config.yml',
isset($options['environment']) ? $options['environment'] : 'fosrestbundletest'.strtolower($options['test_case']).(int) $debug,
$debug
);
}
}
Tests/Functional/app/AllowedMethodsListener/bundles.php 0000666 00000001015 13052362416 0017321 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return array(
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \FOS\RestBundle\FOSRestBundle(),
new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(),
new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
);
Tests/Functional/app/AllowedMethodsListener/config.yml 0000666 00000000223 13052362416 0017144 0 ustar 00 imports:
- { resource: ../config/default.yml }
framework:
serializer:
enabled: true
fos_rest:
allowed_methods_listener: true
Tests/Functional/app/AppKernel.php 0000666 00000005751 13052362416 0013140 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Functional\app;
// get the autoload file
$dir = __DIR__;
$lastDir = null;
while ($dir !== $lastDir) {
$lastDir = $dir;
if (file_exists($dir.'/autoload.php')) {
require_once $dir.'/autoload.php';
break;
}
if (file_exists($dir.'/autoload.php.dist')) {
require_once $dir.'/autoload.php.dist';
break;
}
if (file_exists($dir.'/vendor/autoload.php')) {
require_once $dir.'/vendor/autoload.php';
break;
}
$dir = dirname($dir);
}
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Kernel;
/**
* App Test Kernel for functional tests.
*
* @author Johannes M. Schmitt
*/
class AppKernel extends Kernel
{
private $testCase;
private $rootConfig;
public function __construct($testCase, $rootConfig, $environment, $debug)
{
if (!is_dir(__DIR__.'/'.$testCase)) {
throw new \InvalidArgumentException(sprintf('The test case "%s" does not exist.', $testCase));
}
$this->testCase = $testCase;
$fs = new Filesystem();
if (!$fs->isAbsolutePath($rootConfig) && !file_exists($rootConfig = __DIR__.'/'.$testCase.'/'.$rootConfig)) {
throw new \InvalidArgumentException(sprintf('The root config "%s" does not exist.', $rootConfig));
}
$this->rootConfig = $rootConfig;
parent::__construct($environment, $debug);
}
public function registerBundles()
{
if (!file_exists($filename = $this->getRootDir().'/'.$this->testCase.'/bundles.php')) {
throw new \RuntimeException(sprintf('The bundles file "%s" does not exist.', $filename));
}
return include $filename;
}
public function getRootDir()
{
return __DIR__;
}
public function getCacheDir()
{
return sys_get_temp_dir().'/'.Kernel::VERSION.'/'.$this->testCase.'/cache/'.$this->environment;
}
public function getLogDir()
{
return sys_get_temp_dir().'/'.Kernel::VERSION.'/'.$this->testCase.'/logs';
}
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load($this->rootConfig);
}
public function serialize()
{
return serialize([$this->testCase, $this->rootConfig, $this->getEnvironment(), $this->isDebug()]);
}
public function unserialize($str)
{
$a = unserialize($str);
$this->__construct($a[0], $a[1], $a[2], $a[3]);
}
protected function getKernelParameters()
{
$parameters = parent::getKernelParameters();
$parameters['kernel.test_case'] = $this->testCase;
return $parameters;
}
}
Tests/Functional/app/Configuration/bundles.php 0000666 00000001261 13052362416 0015512 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \Symfony\Bundle\TwigBundle\TwigBundle(),
new \Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(),
new \Symfony\Bundle\SecurityBundle\SecurityBundle(),
new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
new \FOS\RestBundle\FOSRestBundle(),
new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(),
];
Tests/Functional/app/Configuration/config.yml 0000666 00000001447 13052362416 0015343 0 ustar 00 imports:
- { resource: ../config/default.yml }
framework:
serializer:
enabled: true
router: { resource: "%kernel.root_dir%/Configuration/routing.yml" }
profiler: { only_exceptions: false }
fos_rest:
view:
mime_types: true
view_response_listener: true
param_fetcher_listener: true
disable_csrf_role: foo
allowed_methods_listener: true
body_converter:
enabled: true
validate: true
exception: true
body_listener: true
format_listener:
enabled: true
rules:
- { path: '^/', priorities: ['xml'], fallback_format: ~ }
versioning: true
security:
providers:
in_memory:
memory: ~
firewalls:
default:
anonymous: ~
web_profiler:
toolbar: true
Tests/Functional/app/Configuration/routing.yml 0000666 00000000314 13052362416 0015555 0 ustar 00 _wdt:
resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml"
prefix: /_wdt
_profiler:
resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml"
prefix: /_profiler
Tests/Functional/app/Debug/bundles.php 0000666 00000000770 13052362416 0013735 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return array(
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \JMS\SerializerBundle\JMSSerializerBundle(),
new \FOS\RestBundle\FOSRestBundle(),
new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(),
);
Tests/Functional/app/Debug/config.yml 0000666 00000000226 13052362416 0013554 0 ustar 00 imports:
- { resource: ../config/default.yml }
- { resource: ../config/exception_listener.yml }
fos_rest:
exception:
debug: true
Tests/Functional/app/ExceptionListener/bundles.php 0000666 00000000757 13052362416 0016360 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \Symfony\Bundle\TwigBundle\TwigBundle(),
new \FOS\RestBundle\FOSRestBundle(),
new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(),
];
Tests/Functional/app/ExceptionListener/config.yml 0000666 00000000451 13052362416 0016172 0 ustar 00 imports:
- { resource: ../config/default.yml }
framework:
serializer:
enabled: true
templating:
engines: ['twig']
fos_rest:
exception: ~
format_listener:
rules:
- { path: '^/', fallback_format: json }
zone:
- { path: ^/api/* }
Tests/Functional/app/JMSSerializer/bundles.php 0000666 00000000763 13052362416 0015374 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \JMS\SerializerBundle\JMSSerializerBundle(),
new \FOS\RestBundle\FOSRestBundle(),
new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(),
];
Tests/Functional/app/JMSSerializer/config.yml 0000666 00000000245 13052362416 0015212 0 ustar 00 imports:
- { resource: ../config/default.yml }
- { resource: ../config/exception_listener.yml }
fos_rest:
view:
view_response_listener: 'force'
Tests/Functional/app/ParamFetcher/bundles.php 0000666 00000001010 13052362416 0015234 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \FOS\RestBundle\FOSRestBundle(),
new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(),
new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
];
Tests/Functional/app/ParamFetcher/config.yml 0000666 00000000317 13052362416 0015070 0 ustar 00 imports:
- { resource: ../config/default.yml }
parameters:
bar: bar
foo: foo
invalid2: invalid2
framework:
serializer:
enabled: true
fos_rest:
param_fetcher_listener: true
Tests/Functional/app/RequestBodyParamConverter/bundles.php 0000666 00000001071 13052362416 0020021 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \Symfony\Bundle\TwigBundle\TwigBundle(),
new \FOS\RestBundle\FOSRestBundle(),
new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(),
new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
];
Tests/Functional/app/RequestBodyParamConverter/config.yml 0000666 00000000766 13052362416 0017656 0 ustar 00 imports:
- { resource: ../config/default.yml }
framework:
serializer: true
router:
resource: "%kernel.root_dir%/RequestBodyParamConverter/routing.yml"
strict_requirements: true
fos_rest:
body_converter:
enabled: true
sensio_framework_extra:
request:
converters: true
services:
get_set_method_normalizer:
class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
tags:
- { name: serializer.normalizer }
Tests/Functional/app/RequestBodyParamConverter/routing.yml 0000666 00000000217 13052362416 0020067 0 ustar 00 routing:
resource: "../config/routing.yml"
_errors:
resource: "@TwigBundle/Resources/config/routing/errors.xml"
prefix: /_error
Tests/Functional/app/Routing/bundles.php 0000666 00000000703 13052362416 0014332 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return array(
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \FOS\RestBundle\FOSRestBundle(),
new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(),
);
Tests/Functional/app/Routing/config.yml 0000666 00000000264 13052362416 0014157 0 ustar 00 imports:
- { resource: ../config/default.yml }
framework:
serializer:
enabled: true
router: { resource: "%kernel.root_dir%/Routing/routing.yml" }
fos_rest: ~
Tests/Functional/app/Routing/routing.yml 0000666 00000000103 13052362416 0014371 0 ustar 00 api_dir:
resource: '@TestBundle/Controller/Api'
type: rest
Tests/Functional/app/Serializer/bundles.php 0000666 00000000757 13052362416 0015025 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \Symfony\Bundle\TwigBundle\TwigBundle(),
new \FOS\RestBundle\FOSRestBundle(),
new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(),
];
Tests/Functional/app/Serializer/config.yml 0000666 00000000401 13052362416 0014632 0 ustar 00 imports:
- { resource: ../config/default.yml }
- { resource: ../config/exception_listener.yml }
framework:
serializer:
enabled: true
templating:
engines: ['twig']
fos_rest:
view:
view_response_listener: 'force'
Tests/Functional/app/Version/bundles.php 0000666 00000001071 13052362416 0014327 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \Symfony\Bundle\TwigBundle\TwigBundle(),
new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
new \FOS\RestBundle\FOSRestBundle(),
new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(),
];
Tests/Functional/app/Version/config.yml 0000666 00000001723 13052362416 0014156 0 ustar 00 imports:
- { resource: ../config/default.yml }
framework:
serializer:
enabled: true
templating:
engines: ['twig']
fos_rest:
format_listener:
rules:
- { path: '^/', priorities: ['json', 'html'], fallback_format: json }
view:
view_response_listener: true
mime_types:
json:
- application/json
- application/vnd.foo.api+json;myversion=2.3
- application/vnd.foo.api+json # Fix for https://github.com/FriendsOfSymfony/FOSRestBundle/issues/1399
versioning:
enabled: true
default_version: 3.4.2
resolvers:
query:
parameter_name: query_version
custom_header:
header_name: Version-Header
media_type:
regex: '/(myversion)=(?P[0-9\.]+)/'
guessing_order:
- custom_header
- media_type
- query
Tests/Functional/app/ViewResponseListener/bundles.php 0000666 00000001015 13052362416 0017037 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return array(
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
new \FOS\RestBundle\FOSRestBundle(),
new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(),
);
Tests/Functional/app/ViewResponseListener/config.yml 0000666 00000000637 13052362416 0016673 0 ustar 00 imports:
- { resource: ../config/default.yml }
framework:
serializer:
enabled: true
fos_rest:
view:
view_response_listener: 'force'
formats:
xml: true
json: true
templating_formats:
html: true
format_listener:
rules:
- { path: ^/, priorities: [ html, json, xml ], fallback_format: ~, prefer_extension: true } Tests/Functional/app/config/default.yml 0000666 00000000053 13052362416 0014150 0 ustar 00 imports:
- { resource: framework.yml }
Tests/Functional/app/config/exception_listener.yml 0000666 00000000142 13052362416 0016426 0 ustar 00 fos_rest:
exception:
enabled: true
messages:
LogicException: true
Tests/Functional/app/config/framework.yml 0000666 00000000407 13052362416 0014524 0 ustar 00 framework:
secret: test
router: { resource: "%kernel.root_dir%/config/routing.yml" }
test: ~
csrf_protection: ~
form: ~
session:
storage_id: session.storage.mock_file
default_locale: en
templating:
engines: ['php']
Tests/Functional/app/config/routing.yml 0000666 00000000106 13052362416 0014212 0 ustar 00 test_bundle:
resource: "@TestBundle/Resources/config/routing.yml"
Tests/Negotiation/FormatNegotiatorTest.php 0000666 00000010670 13052362416 0014775 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Negotiatior;
use FOS\RestBundle\Negotiation\FormatNegotiator;
use Negotiation\Accept;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Request;
/**
* FormatNegotiatorTest.
*
* @author Ener-Getick
*/
class FormatNegotiatorTest extends \PHPUnit_Framework_TestCase
{
private $requestStack;
private $request;
private $negotiator;
public function setUp()
{
$this->requestStack = new RequestStack();
$this->request = new Request();
$this->requestStack->push($this->request);
$this->negotiator = new FormatNegotiator($this->requestStack, ['json' => ['application/json;version=1.0']]);
}
public function testEmptyRequestMatcherMap()
{
$this->assertNull($this->negotiator->getBest(''));
}
/**
* @expectedException FOS\RestBundle\Util\StopFormatListenerException
* @expectedExceptionMessage Stopped
*/
public function testStopException()
{
$this->addRequestMatcher(false);
$this->addRequestMatcher(true, ['stop' => true]);
$this->negotiator->getBest('');
}
public function testFallbackFormat()
{
$this->addRequestMatcher(true, ['fallback_format' => null]);
$this->assertNull($this->negotiator->getBest(''));
$this->addRequestMatcher(true, ['fallback_format' => 'html']);
$this->assertEquals(new Accept('text/html'), $this->negotiator->getBest(''));
}
public function testFallbackFormatWithPriorities()
{
$this->addRequestMatcher(true, ['priorities' => ['json', 'xml'], 'fallback_format' => null]);
$this->assertNull($this->negotiator->getBest(''));
$this->addRequestMatcher(true, ['priorities' => ['json', 'xml'], 'fallback_format' => 'json']);
$this->assertEquals(new Accept('application/json'), $this->negotiator->getBest(''));
}
public function testGetBest()
{
$this->request->headers->set('Accept', 'application/xhtml+xml, text/html, application/xml;q=0.9, */*;q=0.8');
$priorities = ['text/html; charset=UTF-8', 'html', 'application/json'];
$this->addRequestMatcher(true, ['priorities' => $priorities]);
$this->assertEquals(
new Accept('text/html;charset=utf-8'),
$this->negotiator->getBest('')
);
$this->request->headers->set('Accept', 'application/xhtml+xml, application/xml;q=0.9, */*;q=0.8');
$this->assertEquals(
new Accept('application/xhtml+xml'),
$this->negotiator->getBest('', ['html', 'json'])
);
}
public function testGetBestFallback()
{
$this->request->headers->set('Accept', 'text/html');
$priorities = ['application/json'];
$this->addRequestMatcher(true, ['priorities' => $priorities, 'fallback_format' => 'xml']);
$this->assertEquals(new Accept('text/xml'), $this->negotiator->getBest(''));
}
public function testGetBestWithFormat()
{
$this->request->headers->set('Accept', 'application/json;version=1.0');
$priorities = ['json'];
$this->addRequestMatcher(true, ['priorities' => $priorities, 'fallback_format' => 'xml']);
$this->assertEquals(new Accept('application/json;version=1.0'), $this->negotiator->getBest(''));
}
public function testGetBestWithFormatWithRequestMimeTypeFallback()
{
$negotiator = new FormatNegotiator($this->requestStack);
$this->request->headers->set('Accept', 'application/json');
$priorities = ['json'];
$this->addRequestMatcher(true, ['priorities' => $priorities, 'fallback_format' => 'xml']);
$this->assertEquals(new Accept('application/json'), $this->negotiator->getBest(''));
}
/**
* @param bool $match
* @param array $options
*/
private function addRequestMatcher($match, array $options = [])
{
$matcher = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestMatcherInterface')->getMock();
$matcher->expects($this->any())
->method('matches')
->with($this->request)
->willReturn($match);
$this->negotiator->add($matcher, $options);
}
}
Tests/Normalizer/CamelKeysNormalizerTest.php 0000666 00000005011 13052362416 0015264 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Normalizer;
use FOS\RestBundle\Normalizer\CamelKeysNormalizer;
use FOS\RestBundle\Normalizer\CamelKeysNormalizerWithLeadingUnderscore;
class CamelKeysNormalizerTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \FOS\RestBundle\Normalizer\Exception\NormalizationException
*/
public function testNormalizeSameValueException()
{
$normalizer = new CamelKeysNormalizer();
$normalizer->normalize([
'foo' => [
'foo_bar' => 'foo',
'foo_Bar' => 'foo',
],
]);
}
/**
* @dataProvider normalizeProvider
*/
public function testNormalize(array $array, array $expected)
{
$normalizer = new CamelKeysNormalizer();
$this->assertEquals($expected, $normalizer->normalize($array));
}
public function normalizeProvider()
{
$array = $this->normalizeProviderCommon();
$array[] = array(array('__username' => 'foo', '_password' => 'bar', '_foo_bar' => 'foobar'), array('_Username' => 'foo', 'Password' => 'bar', 'FooBar' => 'foobar'));
return $array;
}
/**
* @dataProvider normalizeProviderLeadingUnderscore
*/
public function testNormalizeLeadingUnderscore(array $array, array $expected)
{
$normalizer = new CamelKeysNormalizerWithLeadingUnderscore();
$this->assertEquals($expected, $normalizer->normalize($array));
}
public function normalizeProviderLeadingUnderscore()
{
$array = $this->normalizeProviderCommon();
$array[] = array(array('__username' => 'foo', '_password' => 'bar', '_foo_bar' => 'foobar'), array('__username' => 'foo', '_password' => 'bar', '_fooBar' => 'foobar'));
return $array;
}
private function normalizeProviderCommon()
{
return array(
array(array(), array()),
array(
array('foo' => array('Foo_bar_baz' => array('foo_Bar' => array('foo_bar' => 'foo_bar'))),
'foo_1ar' => array('foo_bar'),
),
array('foo' => array('FooBarBaz' => array('fooBar' => array('fooBar' => 'foo_bar'))),
'foo1ar' => array('foo_bar'),
),
),
);
}
}
Tests/Request/ParamFetcherTest.php 0000666 00000035523 13052362416 0013226 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Request;
use Doctrine\Common\Util\ClassUtils;
use FOS\RestBundle\Exception\InvalidParameterException;
use FOS\RestBundle\Request\ParamFetcher;
use FOS\RestBundle\Request\ParamReaderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* ParamFetcher test.
*
* @author Alexander
* @author Boris Guéry
*/
class ParamFetcherTest extends \PHPUnit_Framework_TestCase
{
/**
* @var callable
*/
private $controller;
/**
* @var ParamReaderInterface
*/
private $paramReader;
/**
* @var ParamFetcherTest|ValidatorInterface
*/
private $validator;
/**
* @var RequestStack
*/
private $requestStack;
/**
* Test setup.
*/
public function setup()
{
$this->controller = [new \stdClass(), 'fooAction'];
$this->params = [];
$this->paramReader = $this->getMockBuilder(ParamReaderInterface::class)->getMock();
$this->validator = $this->getMockBuilder(ValidatorInterface::class)->getMock();
$this->request = new Request();
$this->requestStack = $this->getMockBuilder(RequestStack::class)->getMock();
$this->requestStack
->expects($this->any())
->method('getCurrentRequest')
->willReturn($this->request);
$this->paramFetcherBuilder = $this->getMockBuilder(ParamFetcher::class);
$this->paramFetcherBuilder
->setConstructorArgs(array(
$this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(),
$this->paramReader,
$this->requestStack,
$this->validator,
))
->setMethods(null);
$this->container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
}
public function testParamDynamicCreation()
{
$fetcher = $this->paramFetcherBuilder->getMock();
$fetcher->setController($this->controller);
$param1 = $this->createMockedParam('foo');
$param2 = $this->createMockedParam('foobar');
$param3 = $this->createMockedParam('bar');
$this->setParams(array($param1)); // Controller params
$fetcher->addParam($param2);
$fetcher->addParam($param3);
$this->assertEquals(array('foo' => $param1, 'foobar' => $param2, 'bar' => $param3), $fetcher->getParams());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage No @ParamInterface configuration for parameter 'foo'.
*/
public function testInexistentParam()
{
$fetcher = $this->paramFetcherBuilder
->setMethods(array('getParams'))
->getMock();
$fetcher
->expects($this->once())
->method('getParams')
->willReturn(array(
'bar' => $this->createMockedParam('bar'),
));
$fetcher->get('foo');
}
public function testDefaultReplacement()
{
$fetcher = $this->paramFetcherBuilder
->setMethods(['getParams', 'cleanParamWithRequirements'])
->getMock();
$param = $this->createMockedParam('foo', 'bar'); // Default value: bar
$fetcher
->expects($this->once())
->method('getParams')
->willReturn(['foo' => $param]);
$fetcher
->expects($this->once())
->method('cleanParamWithRequirements')
->with($param, 'bar', true)
->willReturn('foooo');
$this->assertEquals('foooo', $fetcher->get('foo', true));
}
public function testReturnBeforeGettingConstraints()
{
$param = $this->getMockBuilder(\FOS\RestBundle\Controller\Annotations\ParamInterface::class)->getMock();
$param
->expects($this->never())
->method('getConstraints');
list($fetcher, $method) = $this->getFetcherToCheckValidation($param);
$this->assertEquals(
'default',
$method->invokeArgs($fetcher, array($param, 'default', null, 'default'))
);
}
public function testReturnWhenEmptyConstraints()
{
$param = $this->createMockedParam('foo');
list($fetcher, $method) = $this->getFetcherToCheckValidation($param);
$this->assertEquals(
'value',
$method->invokeArgs($fetcher, array($param, 'value', null, null))
);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage The ParamFetcher requirements feature requires the symfony/validator component.
*/
public function testEmptyValidator()
{
$param = $this->createMockedParam('none', null, array(), false, null, array('constraint'));
$this->setParams([$param]);
list($fetcher, $method) = $this->getFetcherToCheckValidation(
$param,
array(
$this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(),
$this->paramReader,
$this->requestStack,
null,
)
);
$fetcher->setController($this->controller);
$fetcher->get('none', '42');
}
public function testNoValidationErrors()
{
$param = $this->createMockedParam('foo', null, array(), false, null, array('constraint'));
list($fetcher, $method) = $this->getFetcherToCheckValidation($param);
$this->validator
->expects($this->once())
->method('validate')
->with('value', array('constraint'))
->willReturn(array());
$this->assertEquals('value', $method->invokeArgs($fetcher, array($param, 'value', null, null)));
}
public function testValidationErrors()
{
$param = $this->createMockedParam('foo', 'default', [], false, null, ['constraint']);
list($fetcher, $method) = $this->getFetcherToCheckValidation($param);
$errors = $this->getMockBuilder(ConstraintViolationListInterface::class)->getMock();
$errors
->expects($this->once())
->method('count')
->willReturn(1);
$this->validator
->expects($this->once())
->method('validate')
->with('value', ['constraint'])
->willReturn($errors);
$this->assertEquals('default', $method->invokeArgs($fetcher, array($param, 'value', false, 'default')));
}
public function testValidationException()
{
$param = $this->createMockedParam('foo', 'default', [], true, null, ['constraint']);
list($fetcher, $method) = $this->getFetcherToCheckValidation($param);
$errors = new ConstraintViolationList([
$this->getMockBuilder(ConstraintViolationInterface::class)->getMock(),
$this->getMockBuilder(ConstraintViolationInterface::class)->getMock(),
]);
$this->validator
->expects($this->once())
->method('validate')
->with('value', ['constraint'])
->willReturn($errors);
try {
$method->invokeArgs($fetcher, array($param, 'value', true, 'default'));
$this->fail(sprintf('An exception must be thrown in %s', __METHOD__));
} catch (InvalidParameterException $exception) {
$this->assertSame($param, $exception->getParameter());
$this->assertSame($errors, $exception->getViolations());
$this->assertEquals(
'Parameter "foo" of value "" violated a constraint ""'.
"\n".'Parameter "foo" of value "" violated a constraint ""',
$exception->getMessage()
);
}
}
/**
* @expectedException \FOS\RestBundle\Exception\InvalidParameterException
* @expectedMessage expected exception.
*/
public function testValidationErrorsInStrictMode()
{
$param = $this->createMockedParam('foo', null, [], false, null, ['constraint']);
list($fetcher, $method) = $this->getFetcherToCheckValidation($param);
$errors = $this->getMockBuilder(ConstraintViolationListInterface::class)->getMock();
$errors
->expects($this->once())
->method('count')
->willReturn(1);
$this->validator
->expects($this->once())
->method('validate')
->with('value', array('constraint'))
->willReturn($errors);
$method->invokeArgs($fetcher, array($param, 'value', true, null));
}
protected function getFetcherToCheckValidation($param, array $constructionArguments = null)
{
$this->paramFetcherBuilder->setMethods(array('checkNotIncompatibleParams'));
if (null !== $constructionArguments) {
$this->paramFetcherBuilder->setConstructorArgs($constructionArguments);
}
$fetcher = $this->paramFetcherBuilder->getMock();
$fetcher
->expects($this->once())
->method('checkNotIncompatibleParams')
->with($param);
$reflection = new \ReflectionClass($fetcher);
$method = $reflection->getMethod('cleanParamWithRequirements');
$method->setAccessible(true);
return [$fetcher, $method];
}
public function testAllGetter()
{
$fetcher = $this->paramFetcherBuilder
->setMethods(array('getParams', 'get'))
->getMock();
$fetcher
->expects($this->once())
->method('getParams')
->willReturn(array(
'foo' => $this->createMockedParam('foo', null, array(), true), // strict
'bar' => $this->createMockedParam('bar'),
));
$fetcher
->expects($this->exactly(2))
->method('get')
->with(
$this->logicalOr('foo', 'bar'),
$this->logicalOr(true, false)
)
->will($this->onConsecutiveCalls('first', 'second'));
$this->assertEquals(array('foo' => 'first', 'bar' => 'second'), $fetcher->all());
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Controller and method needs to be set via setController
*/
public function testEmptyControllerExceptionWhenInitParams()
{
$fetcher = $this->paramFetcherBuilder->getMock();
$fetcher->all();
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Controller needs to be set as a class instance (closures/functions are not supported)
* @dataProvider invalidControllerProvider
*/
public function testNotCallableControllerExceptionWhenInitParams($controller)
{
$fetcher = $this->paramFetcherBuilder->getMock();
$fetcher->setController($controller);
$fetcher->all();
}
public function invalidControllerProvider()
{
return [
['controller'],
[[null, 'foo']],
[['Foo', null]],
];
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage No @ParamInterface configuration for parameter 'foobar'.
*/
public function testInexistentIncompatibleParam()
{
$fetcher = $this->paramFetcherBuilder
->setMethods(array('getParams'))
->getMock();
$fetcher
->expects($this->once())
->method('getParams')
->willReturn(array('foo' => $this->createMockedParam('foo')));
$param = $this->createMockedParam('bar', null, array('foobar', 'fos')); // Incompatible with foobar & fos
$reflection = new \ReflectionClass($fetcher);
$method = $reflection->getMethod('checkNotIncompatibleParams');
$method->setAccessible(true);
$method->invokeArgs($fetcher, array($param));
}
/**
* @expectedException Symfony\Component\HttpKernel\Exception\BadRequestHttpException
* @expectedExceptionMessage 'bar' param is incompatible with fos param.
*/
public function testIncompatibleParam()
{
$fetcher = $this->paramFetcherBuilder
->setMethods(array('getParams'))
->getMock();
$fetcher
->expects($this->once())
->method('getParams')
->willReturn(array(
'foobar' => $this->createMockedParam('foobar'),
'fos' => $this->createMockedParam('fos', null, array(), false, 'value'),
));
$param = $this->createMockedParam('bar', null, array('foobar', 'fos')); // Incompatible with foobar & fos
$reflection = new \ReflectionClass($fetcher);
$method = $reflection->getMethod('checkNotIncompatibleParams');
$method->setAccessible(true);
$method->invokeArgs($fetcher, array($param));
}
protected function setParams(array $params = array())
{
$newParams = array();
foreach ($params as $param) {
$newParams[$param->getName()] = $param;
}
$this->paramReader
->expects($this->any())
->method('read')
->with(new \ReflectionClass(ClassUtils::getClass($this->controller[0])), $this->controller[1])
->willReturn($newParams);
}
protected function createMockedParam(
$name,
$default = null,
array $incompatibles = [],
$strict = false,
$value = null,
array $constraints = []
) {
$param = $this->getMockBuilder('FOS\RestBundle\Controller\Annotations\ParamInterface')->getMock();
$param
->expects($this->any())
->method('getName')
->willReturn($name);
$param
->expects($this->any())
->method('getDefault')
->willReturn($default);
$param
->expects($this->any())
->method('getIncompatibilities')
->willReturn($incompatibles);
$param
->expects($this->any())
->method('getConstraints')
->willReturn($constraints);
$param
->expects($this->any())
->method('isStrict')
->willReturn($strict);
$param
->expects($this->any())
->method('getValue')
->with($this->request, $default)
->will($value !== null ? $this->returnValue($value) : $this->returnArgument(1));
return $param;
}
}
Tests/Request/ParamReaderTest.php 0000666 00000011516 13052362416 0013044 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Request;
use FOS\RestBundle\Controller\Annotations\ParamInterface;
use FOS\RestBundle\Controller\Annotations\NamePrefix;
use FOS\RestBundle\Request\ParamReader;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Validator\Constraints\NotNull;
/**
* ParamReader test.
*
* @author Alexander
*/
class ParamReaderTest extends \PHPUnit_Framework_TestCase
{
private $paramReader;
/**
* Test setup.
*/
public function setup()
{
$annotationReader = $this->getMockBuilder(AnnotationReader::class)->getMock();
$methodAnnotations = [];
$foo = $this->createMockedParam();
$foo
->expects($this->any())
->method('getName')
->willReturn('foo');
$methodAnnotations[] = $foo;
$bar = $this->createMockedParam();
$bar
->expects($this->any())
->method('getName')
->willReturn('bar');
$methodAnnotations[] = $bar;
$methodAnnotations[] = new NamePrefix([]);
$annotationReader
->expects($this->any())
->method('getMethodAnnotations')
->will($this->returnValue($methodAnnotations));
$classAnnotations = [];
$baz = $this->createMockedParam();
$baz
->expects($this->any())
->method('getName')
->willReturn('baz');
$classAnnotations[] = $baz;
$mikz = $this->createMockedParam();
$mikz
->expects($this->any())
->method('getName')
->willReturn('micz');
$classAnnotations[] = $mikz;
$classAnnotations[] = new NamePrefix([]);
$annotationReader
->expects($this->any())
->method('getClassAnnotations')
->will($this->returnValue($classAnnotations));
$this->paramReader = new ParamReader($annotationReader);
}
/**
* Test that only ParamInterface annotations are returned.
*/
public function testReadsOnlyParamAnnotations()
{
$annotations = $this->paramReader->read(new \ReflectionClass(__CLASS__), 'setup');
$this->assertCount(4, $annotations);
foreach ($annotations as $name => $annotation) {
$this->assertInstanceOf(ParamInterface::class, $annotation);
$this->assertEquals($annotation->getName(), $name);
}
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Class 'FOS\RestBundle\Tests\Request\ParamReaderTest' has no method 'foo'.
*/
public function testExceptionOnNonExistingMethod()
{
$this->paramReader->read(new \ReflectionClass(__CLASS__), 'foo');
}
public function testAnnotationReader()
{
$reader = new AnnotationReader();
$method = new \ReflectionMethod('FOS\RestBundle\Tests\Fixtures\Controller\ParamsAnnotatedController', 'getArticlesAction');
$params = $reader->getMethodAnnotations($method);
// Param 1 (query)
$this->assertEquals('page', $params[0]->name);
$this->assertEquals('\\d+', $params[0]->requirements);
$this->assertEquals('1', $params[0]->default);
$this->assertEquals('Page of the overview.', $params[0]->description);
$this->assertFalse($params[0]->map);
$this->assertFalse($params[0]->strict);
// Param 2 (request)
$this->assertEquals('byauthor', $params[1]->name);
$this->assertEquals('[a-z]+', $params[1]->requirements);
$this->assertEquals('by author', $params[1]->description);
$this->assertEquals(['search'], $params[1]->incompatibles);
$this->assertFalse($params[1]->map);
$this->assertTrue($params[1]->strict);
// Param 3 (query)
$this->assertEquals('filters', $params[2]->name);
$this->assertTrue($params[2]->map);
$this->assertEquals(new NotNull(), $params[2]->requirements);
// Param 4 (file)
$this->assertEquals('avatar', $params[3]->name);
$this->assertEquals(['mimeTypes' => 'application/json'], $params[3]->requirements);
$this->assertTrue($params[3]->image);
$this->assertTrue($params[3]->strict);
// Param 5 (file)
$this->assertEquals('foo', $params[4]->name);
$this->assertEquals(new NotNull(), $params[4]->requirements);
$this->assertFalse($params[4]->image);
$this->assertFalse($params[4]->strict);
}
protected function createMockedParam()
{
return $this->getMockBuilder(ParamInterface::class)->getMock();
}
}
Tests/Request/RequestBodyParamConverterTest.php 0000666 00000022574 13052362416 0016006 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Request;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Context\Context;
use FOS\RestBundle\Request\RequestBodyParamConverter;
/**
* @author Tyler Stroud
*/
class RequestBodyParamConverterTest extends \PHPUnit_Framework_TestCase
{
protected $serializer;
protected $converterBuilder;
public function setUp()
{
// skip the test if the installed version of SensioFrameworkExtraBundle
// is not compatible with the RequestBodyParamConverter class
$parameter = new \ReflectionParameter(
[
'Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface',
'supports',
],
'configuration'
);
if ('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter' != $parameter->getClass()->getName()) {
$this->markTestSkipped(
'skipping RequestBodyParamConverterTest due to an incompatible version of the SensioFrameworkExtraBundle'
);
}
$this->serializer = $this->getMockBuilder('FOS\RestBundle\Serializer\Serializer')->getMock();
$this->converter = new RequestBodyParamConverter($this->serializer);
}
public function testInterface()
{
$converter = new RequestBodyParamConverter($this->serializer);
$this->assertInstanceOf('Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface', $converter);
}
public function testContextMergeDuringExecution()
{
$options = [
'deserializationContext' => [
'groups' => ['foo', 'bar'],
'foobar' => 'foo',
],
];
$configuration = $this->createConfiguration(null, null, $options);
$converter = $this->getMockBuilder(RequestBodyParamConverter::class)
->setConstructorArgs([$this->serializer, ['foogroup'], 'fooversion'])
->setMethods(['configureContext'])
->getMock();
$converter
->expects($this->once())
->method('configureContext')
->with($this->anything(), ['groups' => ['foo', 'bar'], 'foobar' => 'foo', 'version' => 'fooversion'])
->willReturn(new Context());
$this->launchExecution($converter, null, $configuration);
}
/**
* @expectedException Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException
*/
public function testExecutionInterceptsUnsupportedFormatException()
{
$converter = new RequestBodyParamConverter($this->serializer);
$this->serializer
->expects($this->once())
->method('deserialize')
->will($this->throwException(new \Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException()));
$this->launchExecution($converter);
}
/**
* @expectedException Symfony\Component\HttpKernel\Exception\BadRequestHttpException
*/
public function testExecutionInterceptsJMSException()
{
$converter = new RequestBodyParamConverter($this->serializer);
$this->serializer
->expects($this->once())
->method('deserialize')
->will($this->throwException(new \JMS\Serializer\Exception\InvalidArgumentException()));
$this->launchExecution($converter);
}
/**
* @expectedException Symfony\Component\HttpKernel\Exception\BadRequestHttpException
*/
public function testExecutionInterceptsSymfonySerializerException()
{
$converter = new RequestBodyParamConverter($this->serializer);
$this->serializer
->expects($this->once())
->method('deserialize')
->will($this->throwException(new \Symfony\Component\Serializer\Exception\InvalidArgumentException()));
$this->launchExecution($converter);
}
public function testRequestAttribute()
{
$converter = new RequestBodyParamConverter($this->serializer);
$this->serializer
->expects($this->once())
->method('deserialize')
->willReturn('Object');
$request = $this->createRequest();
$this->launchExecution($converter, $request);
$this->assertEquals('Object', $request->attributes->get('foo'));
}
public function testValidatorParameters()
{
$this->serializer
->expects($this->once())
->method('deserialize')
->willReturn('Object');
$validator = $this->getMockBuilder('Symfony\Component\Validator\Validator\ValidatorInterface')->getMock();
$validator
->expects($this->once())
->method('validate')
->with('Object', null, ['foo'])
->willReturn('fooError');
$converter = new RequestBodyParamConverter($this->serializer, null, null, $validator, 'errors');
$request = $this->createRequest();
$configuration = $this->createConfiguration(null, null, ['validator' => ['groups' => ['foo']]]);
$this->launchExecution($converter, $request, $configuration);
$this->assertEquals('fooError', $request->attributes->get('errors'));
}
public function testReturn()
{
$converter = new RequestBodyParamConverter($this->serializer);
$this->assertTrue($this->launchExecution($converter));
}
public function testContextConfiguration()
{
$converter = new RequestBodyParamConverter($this->serializer);
$options = [
'groups' => ['foo', 'bar'],
'version' => 'v1.2',
'maxDepth' => 5,
'serializeNull' => false,
'foo' => 'bar',
];
$contextConfigurationMethod = new \ReflectionMethod($converter, 'configureContext');
$contextConfigurationMethod->setAccessible(true);
$contextConfigurationMethod->invoke($converter, $context = new Context(), $options);
$expectedContext = new Context();
$expectedContext
->addGroups($options['groups'])
->setVersion($options['version'])
->setMaxDepth($options['maxDepth'])
->setSerializeNull($options['serializeNull'])
->setAttribute('foo', 'bar');
$this->assertEquals($expectedContext, $context);
}
public function testValidatorOptionsGetter()
{
$converter = new RequestBodyParamConverter($this->serializer);
$options1 = [
'validator' => [
'groups' => ['foo'],
'traverse' => true,
],
];
$options2 = [
'validator' => [
'deep' => true,
],
];
$validatorMethod = new \ReflectionMethod($converter, 'getValidatorOptions');
$validatorMethod->setAccessible(true);
$this->assertEquals(['groups' => ['foo'], 'traverse' => true, 'deep' => false], $validatorMethod->invoke($converter, $options1));
$this->assertEquals(['groups' => false, 'traverse' => false, 'deep' => true], $validatorMethod->invoke($converter, $options2));
}
public function testSupports()
{
$converter = new RequestBodyParamConverter($this->serializer);
$config = $this->createConfiguration('FOS\RestBundle\Tests\Request\Post', 'post');
$this->assertTrue($converter->supports($config));
}
public function testSupportsWithNoClass()
{
$converter = new RequestBodyParamConverter($this->serializer);
$this->assertFalse($converter->supports($this->createConfiguration(null, 'post')));
}
protected function launchExecution($converter, $request = null, $configuration = null)
{
if ($request === null) {
$request = $this->createRequest('body', 'application/json');
}
if ($configuration === null) {
$configuration = $this->createConfiguration('FooClass', 'foo');
}
return $converter->apply($request, $configuration);
}
protected function createConfiguration($class = null, $name = null, $options = null)
{
$config = $this->getMockBuilder('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter')
->disableOriginalConstructor()
->setMethods(['getClass', 'getAliasName', 'getOptions', 'getName', 'allowArray'])
->getMock();
if ($name !== null) {
$config->expects($this->any())
->method('getName')
->will($this->returnValue($name));
}
if ($class !== null) {
$config->expects($this->any())
->method('getClass')
->will($this->returnValue($class));
}
if ($options !== null) {
$config->expects($this->any())
->method('getOptions')
->will($this->returnValue($options));
}
return $config;
}
protected function createRequest($body = null, $contentType = null)
{
$request = new Request(
[],
[],
[],
[],
[],
[],
$body
);
$request->headers->set('CONTENT_TYPE', $contentType);
return $request;
}
}
Tests/Routing/Loader/DirectoryRouteLoaderTest.php 0000666 00000004350 13052362416 0016176 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Routing\Loader;
use FOS\RestBundle\Routing\Loader\DirectoryRouteLoader;
use FOS\RestBundle\Routing\Loader\RestRouteProcessor;
use Symfony\Component\Config\Loader\LoaderResolver;
class DirectoryRouteLoaderTest extends LoaderTest
{
public function testLoad()
{
$collection = $this->loadFromDirectory(__DIR__.'/../../Fixtures/Controller/Directory');
$this->assertCount(9, $collection);
foreach ($collection as $route) {
$this->assertInstanceOf('Symfony\Component\Routing\Route', $route);
}
}
/**
* @dataProvider supportsDataProvider
*/
public function testSupports($resource, $type, $expected)
{
$loader = new DirectoryRouteLoader($this->getMockBuilder('Symfony\Component\Config\FileLocatorInterface')->getMock(), new RestRouteProcessor());
if ($expected) {
$this->assertTrue($loader->supports($resource, $type));
} else {
$this->assertFalse($loader->supports($resource, $type));
}
}
public function supportsDataProvider()
{
return array(
'existing-directory' => array(__DIR__.'/../../Fixtures/Controller', 'rest', true),
'non-existing-directory' => array(__DIR__.'/Fixtures/Controller', 'rest', false),
'class-name' => array('FOS\RestBundle\Tests\Fixtures\Controller\UsersController', 'rest', false),
'null-type' => array(__DIR__.'/../../Fixtures/Controller', null, false),
);
}
private function loadFromDirectory($resource)
{
$directoryLoader = new DirectoryRouteLoader($this->getMockBuilder('Symfony\Component\Config\FileLocatorInterface')->getMock(), new RestRouteProcessor());
$controllerLoader = $this->getControllerLoader();
// LoaderResolver sets the resolvers on the loaders passed to it
new LoaderResolver(array($directoryLoader, $controllerLoader));
return $directoryLoader->load($resource, 'rest');
}
}
Tests/Routing/Loader/LoaderTest.php 0000666 00000005117 13052362416 0013274 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Routing\Loader;
use Doctrine\Common\Annotations\AnnotationReader;
use FOS\RestBundle\Inflector\DoctrineInflector;
use FOS\RestBundle\Request\ParamReader;
use FOS\RestBundle\Routing\Loader\Reader\RestActionReader;
use FOS\RestBundle\Routing\Loader\Reader\RestControllerReader;
use FOS\RestBundle\Routing\Loader\RestRouteLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Yaml\Yaml;
/**
* Base Loader testing class.
*
* @author Konstantin Kudryashov
*/
abstract class LoaderTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ContainerBuilder
*/
protected $container;
/**
* Load routes etalon from yml fixture file under Tests\Fixtures directory.
*
* @param string $etalonName name of the YML fixture
*
* @return array
*/
protected function loadEtalonRoutesInfo($etalonName)
{
return Yaml::parse(file_get_contents(__DIR__.'/../../Fixtures/Etalon/'.$etalonName));
}
private function getAnnotationReader()
{
return new AnnotationReader();
}
/**
* Builds a RestRouteLoader.
*
* @param array $formats available resource formats
*
* @return RestRouteLoader
*/
protected function getControllerLoader(array $formats = [])
{
// This check allows to override the container
if ($this->container === null) {
$this->container = $this->getMockBuilder(ContainerBuilder::class)
->disableOriginalConstructor()
->setMethods(['get', 'has'])
->getMock();
}
$l = $this->getMockBuilder('Symfony\Component\Config\FileLocator')
->disableOriginalConstructor()
->getMock();
$p = $this->getMockBuilder('Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser')
->disableOriginalConstructor()
->getMock();
$annotationReader = $this->getAnnotationReader();
$paramReader = new ParamReader($annotationReader);
$inflector = new DoctrineInflector();
$ar = new RestActionReader($annotationReader, $paramReader, $inflector, true, $formats);
$cr = new RestControllerReader($ar, $annotationReader);
return new RestRouteLoader($this->container, $l, $p, $cr, 'html');
}
}
Tests/Routing/Loader/RestRouteLoaderTest.php 0000666 00000040450 13052362416 0015150 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Routing\Loader;
use FOS\RestBundle\Routing\RestRouteCollection;
use Symfony\Component\Routing\RouteCollection;
/**
* RestRouteLoader test.
*
* @author Konstantin Kudryashov
*/
class RestRouteLoaderTest extends LoaderTest
{
/**
* Test that UsersController RESTful class gets parsed correctly.
*/
public function testUsersFixture()
{
$collection = $this->loadFromControllerFixture('UsersController');
$etalonRoutes = $this->loadEtalonRoutesInfo('users_controller.yml');
$this->assertTrue($collection instanceof RestRouteCollection);
$this->assertEquals(32, count($collection->all()));
foreach ($etalonRoutes as $name => $params) {
$route = $collection->get($name);
$methods = $route->getMethods();
$this->assertNotNull($route, sprintf('route for %s does not exist', $name));
$this->assertEquals($params['path'], $route->getPath(), 'Path does not match for route: '.$name);
$this->assertEquals($params['methods'][0], $methods[0], 'Method does not match for route: '.$name);
$this->assertContains($params['controller'], $route->getDefault('_controller'), 'Controller does not match for route: '.$name);
}
}
/**
* Test that ResourceController RESTful class gets parsed correctly.
*/
public function testResourceFixture()
{
$collection = $this->loadFromControllerFixture('ArticleController');
$etalonRoutes = $this->loadEtalonRoutesInfo('resource_controller.yml');
$this->assertTrue($collection instanceof RestRouteCollection);
$this->assertEquals(24, count($collection->all()));
foreach ($etalonRoutes as $name => $params) {
$route = $collection->get($name);
$methods = $route->getMethods();
$this->assertNotNull($route, sprintf('route for %s does not exist', $name));
$this->assertEquals($params['path'], $route->getPath(), 'Path does not match for route: '.$name);
$this->assertEquals($params['methods'][0], $methods[0], 'Method does not match for route: '.$name);
$this->assertContains($params['controller'], $route->getDefault('_controller'), 'Controller does not match for route: '.$name);
}
}
/**
* Test that custom actions (new/edit/remove) are dumped earlier.
*/
public function testCustomActionRoutesOrder()
{
// without prefix
$collection = $this->loadFromControllerFixture('OrdersController');
$pos = array_flip(array_keys($collection->all()));
$this->assertLessThan($pos['get_foos'], $pos['new_foos']);
$this->assertLessThan($pos['get_bars'], $pos['new_bars']);
// with prefix
$collection = $this->loadFromControllerFixture('OrdersController', 'prefix_');
$pos = array_flip(array_keys($collection->all()));
$this->assertLessThan($pos['prefix_get_foos'], $pos['prefix_new_foos']);
$this->assertLessThan($pos['prefix_get_bars'], $pos['prefix_new_bars']);
}
/**
* Test that annotated UsersController RESTful class gets parsed correctly.
*/
public function testAnnotatedUsersFixture()
{
$collection = $this->loadFromControllerFixture('AnnotatedUsersController');
$etalonRoutes = $this->loadEtalonRoutesInfo('annotated_users_controller.yml');
$this->assertTrue($collection instanceof RestRouteCollection);
$this->assertEquals(31, count($collection->all()));
foreach ($etalonRoutes as $name => $params) {
$route = $collection->get($name);
$this->assertNotNull($route, "no route found for '$name'");
$this->assertEquals($params['path'], $route->getPath(), 'path failed to match for '.$name);
$params['requirements'] = isset($params['requirements']) ? $params['requirements'] : array();
$requirements = $route->getRequirements();
unset($requirements['_method']);
$this->assertEquals($params['requirements'], $requirements, 'requirements failed to match for '.$name);
$this->assertContains($params['controller'], $route->getDefault('_controller'), 'controller failed to match for '.$name);
if (isset($params['condition'])) {
$this->assertEquals($params['condition'], $route->getCondition(), 'condition failed to match for '.$name);
}
if (isset($params['options'])) {
foreach ($params['options'] as $option => $value) {
$this->assertEquals($value, $route->getOption($option));
}
}
}
}
public function testDisabledPluralization()
{
$collection = $this->loadFromControllerFixture('AnnotatedNonPluralizedArticleController');
$this->assertEquals('/article.{_format}', $collection->get('cget_article')->getPath());
$this->assertEquals('/article/{slug}.{_format}', $collection->get('get_article')->getPath());
$this->assertEquals('/article/{slug}/comment.{_format}', $collection->get('cget_article_comment')->getPath());
$this->assertEquals('/article/{slug}/comment/{comment}.{_format}', $collection->get('get_article_comment')->getPath());
}
/**
* Test that annotated UsersController RESTful class gets parsed correctly with condition option (expression-language).
*/
public function testAnnotatedConditionalUsersFixture()
{
$collection = $this->loadFromControllerFixture('AnnotatedConditionalUsersController');
$etalonRoutes = $this->loadEtalonRoutesInfo('annotated_conditional_controller.yml');
$this->assertTrue($collection instanceof RestRouteCollection);
$this->assertEquals(22, count($collection->all()));
foreach ($etalonRoutes as $name => $params) {
$route = $collection->get($name);
$this->assertNotNull($route, "no route found for '$name'");
$this->assertEquals($params['path'], $route->getPath(), 'path failed to match for '.$name);
$params['requirements'] = isset($params['requirements']) ? $params['requirements'] : array();
$requirements = $route->getRequirements();
unset($requirements['_method']);
$this->assertEquals($params['requirements'], $requirements, 'requirements failed to match for '.$name);
$this->assertContains($params['controller'], $route->getDefault('_controller'), 'controller failed to match for '.$name);
if (isset($params['condition'])) {
$this->assertEquals($params['condition'], $route->getCondition(), 'condition failed to match for '.$name);
}
}
}
/**
* Test that annotated UsersController RESTful class gets parsed correctly with condition option (expression-language).
*/
public function testAnnotatedVersionUserFixture()
{
$collection = $this->loadFromControllerFixture('AnnotatedVersionUserController');
$etalonRoutes = $this->loadEtalonRoutesInfo('annotated_version_controller.yml');
$this->assertTrue($collection instanceof RestRouteCollection);
$this->assertEquals(3, count($collection->all()));
foreach ($etalonRoutes as $name => $params) {
$route = $collection->get($name);
$this->assertNotNull($route, "no route found for '$name'");
$this->assertEquals($params['path'], $route->getPath(), 'path failed to match for '.$name);
$params['requirements'] = isset($params['requirements']) ? $params['requirements'] : array();
$requirements = $route->getRequirements();
unset($requirements['_method']);
$this->assertEquals($params['requirements'], $requirements, 'requirements failed to match for '.$name);
$this->assertContains($params['controller'], $route->getDefault('_controller'), 'controller failed to match for '.$name);
if (isset($params['condition'])) {
$this->assertEquals($params['condition'], $route->getCondition(), 'condition failed to match for '.$name);
}
}
}
/**
* Test that a custom format annotation is not overwritten.
*/
public function testCustomFormatRequirementIsKept()
{
$collection = $this->loadFromControllerFixture(
'AnnotatedUsersController',
null,
['json' => true, 'xml' => true, 'html' => true]
);
$routeCustom = $collection->get('custom_user');
$routeWithRequirements = $collection->get('get_user');
$this->assertEquals('custom', $routeCustom->getRequirement('_format'));
$this->assertEquals('json|xml|html', $routeWithRequirements->getRequirement('_format'));
}
/**
* @see https://github.com/FriendsOfSymfony/RestBundle/issues/37
*/
public function testPrefixIsResetForEachController()
{
// we can't use the getControllerLoader method because we need to verify that the prefix
// is reset when using the same ControllerLoader for both Controllers.
$loader = $this->getControllerLoader();
// get the path for the prefixed controller, and verify it is prefixed
$collection = $loader->load('FOS\RestBundle\Tests\Fixtures\Controller\AnnotatedPrefixedController', 'rest');
$prefixedRoute = $collection->get('get_something');
$this->assertEquals('/aprefix/', substr($prefixedRoute->getPath(), 0, 9));
// get the path for the non-prefixed controller, and verify it's not prefixed
$collection2 = $loader->load('FOS\RestBundle\Tests\Fixtures\Controller\UsersController', 'rest');
$nonPrefixedRoute = $collection2->get('get_users');
$this->assertNotEquals('/aprefix/', substr($nonPrefixedRoute->getPath(), 0, 9));
}
/**
* Test that conventional actions exist and are registered as GET methods.
*
* @see https://github.com/FriendsOfSymfony/RestBundle/issues/67
*/
public function testConventionalActions()
{
$expectedMethod = ['GET'];
$collection = $this->loadFromControllerFixture('UsersController');
$subcollection = $this->loadFromControllerFixture('UserTopicsController');
$subsubcollection = $this->loadFromControllerFixture('UserTopicCommentsController');
// resource actions
$this->assertEquals($expectedMethod, $collection->get('new_users')->getMethods());
$this->assertEquals($expectedMethod, $collection->get('edit_user')->getMethods());
$this->assertEquals($expectedMethod, $collection->get('remove_user')->getMethods());
// subresource actions
$this->assertEquals($expectedMethod, $collection->get('new_user_comments')->getMethods());
$this->assertEquals($expectedMethod, $collection->get('edit_user_comment')->getMethods());
$this->assertEquals($expectedMethod, $collection->get('remove_user_comment')->getMethods());
// resource collection actions
$this->assertEquals($expectedMethod, $subcollection->get('new_topics')->getMethods());
$this->assertEquals($expectedMethod, $subcollection->get('edit_topic')->getMethods());
$this->assertEquals($expectedMethod, $subcollection->get('remove_topic')->getMethods());
// resource collection's resource collection actions
$this->assertEquals($expectedMethod, $subsubcollection->get('new_comments')->getMethods());
$this->assertEquals($expectedMethod, $subsubcollection->get('edit_comment')->getMethods());
$this->assertEquals($expectedMethod, $subsubcollection->get('remove_comment')->getMethods());
}
/**
* Test that custom actions (new/edit/remove) are dumped earlier,
* and that developer routes order is kept.
*
* @see https://github.com/FriendsOfSymfony/RestBundle/issues/379
*/
public function testCustomActionRoutesInDeveloperOrder()
{
// without prefix
$collection = $this->loadFromControllerFixture('OrdersController');
$pos = array_flip(array_keys($collection->all()));
$this->assertLessThan($pos['get_bars'], $pos['get_bars_custom']);
// with prefix
$collection = $this->loadFromControllerFixture('OrdersController', 'prefix_');
$pos = array_flip(array_keys($collection->all()));
$this->assertLessThan($pos['prefix_get_bars'], $pos['prefix_get_bars_custom']);
}
/**
* Test if the routes are also working with uninflected words.
*
* @see https://github.com/FriendsOfSymfony/FOSRestBundle/pull/761
*/
public function testMediaFixture()
{
$expectedMethod = ['GET'];
$collection = $this->loadFromControllerFixture('MediaController');
$this->assertCount(2, $collection->all());
$this->assertEquals($expectedMethod, $collection->get('get_media')->getMethods());
$this->assertEquals($expectedMethod, $collection->get('cget_media')->getMethods());
}
/**
* Test if the routes are also working with uninflected words.
*
* @see https://github.com/FriendsOfSymfony/FOSRestBundle/pull/761
*/
public function testInformationFixture()
{
$collection = $this->loadFromControllerFixture('InformationController');
$this->assertCount(2, $collection->all());
$getRoute = $collection->get('get_information');
$cgetRoute = $collection->get('cget_information');
$this->assertEquals($getRoute, $cgetRoute);
$this->assertNotSame($getRoute, $cgetRoute);
}
/**
* @see https://github.com/FriendsOfSymfony/FOSRestBundle/issues/770
* @see https://github.com/FriendsOfSymfony/FOSRestBundle/pull/792
*/
public function testNamePrefixIsPrependingCorrectly()
{
$collection = $this->loadFromControllerFixture('InformationController', 'prefix_');
$this->assertNotNull($collection->get('prefix_get_information'));
$this->assertNotNull($collection->get('prefix_cget_information'));
}
/**
* @see https://github.com/FriendsOfSymfony/FOSRestBundle/pull/879
*/
public function testNameMethodPrefixIsPrependingCorrectly()
{
$collection = $this->loadFromControllerFixture('AnnotatedUsersController');
$this->assertNotNull($collection->get('post_users_foo'), 'route for "post_users_foo" does not exist');
$this->assertNotNull($collection->get('post_users_bar'), 'route for "post_users_bar" does not exist');
}
/**
* RestActionReader::getMethodArguments should ignore certain types of
* parameters.
*/
public function testRequestTypeHintsIgnoredCorrectly()
{
$collection = $this->loadFromControllerFixture('TypeHintedController');
$this->assertNotNull($collection->get('get_articles'), 'route for "get_articles" does not exist');
$this->assertEquals('/articles.{_format}', $collection->get('get_articles')->getPath());
$this->assertNotNull($collection->get('post_articles'), 'route for "post_articles" does not exist');
$this->assertEquals('/articles.{_format}', $collection->get('post_articles')->getPath());
$this->assertNotNull($collection->get('get_article'), 'route for "get_article" does not exist');
$this->assertEquals('/articles/{id}.{_format}', $collection->get('get_article')->getPath());
$this->assertNotNull($collection->get('post_article'), 'route for "post_article" does not exist');
$this->assertEquals('/articles/{id}.{_format}', $collection->get('post_article')->getPath());
}
/**
* Load routes collection from fixture class under Tests\Fixtures directory.
*
* @param string $fixtureName name of the class fixture
* @param string $namePrefix route name prefix
* @param array $formats resource formats available
*
* @return RouteCollection
*/
protected function loadFromControllerFixture($fixtureName, $namePrefix = null, array $formats = [])
{
$loader = $this->getControllerLoader($formats);
$loader->getControllerReader()->getActionReader()->setNamePrefix($namePrefix);
return $loader->load('FOS\RestBundle\Tests\Fixtures\Controller\\'.$fixtureName, 'rest');
}
}
Tests/Routing/Loader/RestXmlCollectionLoaderTest.php 0000666 00000013606 13052362416 0016631 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Routing\Loader;
use FOS\RestBundle\Routing\Loader\RestRouteProcessor;
use FOS\RestBundle\Routing\Loader\RestXmlCollectionLoader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\Routing\RouteCollection;
/**
* RestXmlCollectionLoader test.
*
* @author Konstantin Kudryashov
*/
class RestXmlCollectionLoaderTest extends LoaderTest
{
/**
* Test that route route not found.
*
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Cannot find parent resource with name
*/
public function testLoadThrowsExceptionWithInvalidRouteParent()
{
$this->loadFromXmlCollectionFixture('invalid_route_parent.xml');
}
/**
* Test that invalid tag.
*
* @expectedException \InvalidArgumentException
* @expectedExceptionMessageRegExp /This element is not expected. Expected is one of/
*/
public function testLoadThrowsExceptionWithInvalidTag()
{
$this->loadFromXmlCollectionFixture('invalid_tag.xml');
}
/**
* Test that XML collection gets parsed correctly.
*/
public function testUsersFixture()
{
$collection = $this->loadFromXmlCollectionFixture('users_collection.xml');
$etalonRoutes = $this->loadEtalonRoutesInfo('users_collection.yml');
foreach ($etalonRoutes as $name => $params) {
$route = $collection->get($name);
$methods = $route->getMethods();
$this->assertNotNull($route, $name);
$this->assertEquals($params['path'], $route->getPath(), $name);
$this->assertEquals($params['methods'][0], $methods[0], $name);
$this->assertContains($params['controller'], $route->getDefault('_controller'), $name);
}
}
/**
* Test that XML collection with custom prefixes gets parsed correctly.
*/
public function testPrefixedUsersFixture()
{
$collection = $this->loadFromXmlCollectionFixture('prefixed_users_collection.xml');
$etalonRoutes = $this->loadEtalonRoutesInfo('prefixed_users_collection.yml');
foreach ($etalonRoutes as $name => $params) {
$route = $collection->get($name);
$methods = $route->getMethods();
$this->assertNotNull($route, $name);
$this->assertEquals($params['path'], $route->getPath(), $name);
$this->assertEquals($params['methods'][0], $methods[0], $name);
$this->assertContains($params['controller'], $route->getDefault('_controller'), $name);
}
}
public function testManualRoutes()
{
$collection = $this->loadFromXmlCollectionFixture('routes.xml');
$route = $collection->get('get_users');
$this->assertEquals('/users.{_format}', $route->getPath());
$this->assertEquals('json|xml|html', $route->getRequirement('_format'));
$this->assertEquals('FOSRestBundle:UsersController:getUsers', $route->getDefault('_controller'));
}
public function testManualRoutesWithoutIncludeFormat()
{
$collection = $this->loadFromXmlCollectionFixture('routes.xml', false);
$route = $collection->get('get_users');
$this->assertEquals('/users', $route->getPath());
}
public function testManualRoutesWithFormats()
{
$collection = $this->loadFromXmlCollectionFixture(
'routes.xml',
true,
[
'json' => false,
]
);
$route = $collection->get('get_users');
$this->assertEquals('json', $route->getRequirement('_format'));
}
public function testManualRoutesWithDefaultFormat()
{
$collection = $this->loadFromXmlCollectionFixture(
'routes.xml',
true,
[
'json' => false,
'xml' => false,
'html' => true,
],
'xml'
);
$route = $collection->get('get_users');
$this->assertEquals('xml', $route->getDefault('_format'));
}
public function testForwardOptionsRequirementsAndDefaults()
{
$collection = $this->loadFromXmlCollectionFixture('routes_with_options_requirements_and_defaults.xml');
foreach ($collection as $route) {
$this->assertTrue('true' === $route->getOption('expose'));
$this->assertEquals('[a-z]+', $route->getRequirement('slug'));
$this->assertEquals('home', $route->getDefault('slug'));
}
}
/**
* Load routes collection from XML fixture routes under Tests\Fixtures directory.
*
* @param string $fixtureName name of the class fixture
* @param bool $includeFormat whether or not the requested view format must be included in the route path
* @param string[] $formats supported view formats
* @param string $defaultFormat default view format
*
* @return RouteCollection
*/
protected function loadFromXmlCollectionFixture(
$fixtureName,
$includeFormat = true,
array $formats = [
'json' => false,
'xml' => false,
'html' => true,
],
$defaultFormat = null
) {
$collectionLoader = new RestXmlCollectionLoader(
new FileLocator([__DIR__.'/../../Fixtures/Routes']),
new RestRouteProcessor(),
$includeFormat,
$formats,
$defaultFormat
);
$controllerLoader = $this->getControllerLoader();
new LoaderResolver([$collectionLoader, $controllerLoader]);
return $collectionLoader->load($fixtureName, 'rest');
}
}
Tests/Routing/Loader/RestYamlCollectionLoaderTest.php 0000666 00000024122 13052362416 0016766 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\Routing\Loader;
use FOS\RestBundle\Routing\Loader\RestRouteProcessor;
use FOS\RestBundle\Routing\Loader\RestYamlCollectionLoader;
use FOS\RestBundle\Tests\Fixtures\Controller\UsersController;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Routing\RouteCollection;
/**
* RestYamlCollectionLoader test.
*
* @author Konstantin Kudryashov
*/
class RestYamlCollectionLoaderTest extends LoaderTest
{
/**
* Test that YAML file is empty.
*/
public function testLoadDoesNothingIfEmpty()
{
$collection = $this->loadFromYamlCollectionFixture('empty.yml');
$this->assertEquals([], $collection->all());
}
/**
* Test that invalid YAML format.
*
* @expectedException \InvalidArgumentException
* @expectedExceptionMessageRegExp /The file "*.+\/bad_format\.yml" does not contain valid YAML\./
*/
public function testLoadThrowsExceptionWithInvalidYaml()
{
$this->loadFromYamlCollectionFixture('bad_format.yml');
}
/**
* Test that YAML value not an array.
*
* @expectedException \InvalidArgumentException
* @expectedExceptionMessageRegExp /The file "*.+\/nonvalid.yml" must contain a Yaml mapping \(an array\)\./
*/
public function testLoadThrowsExceptionWithValueNotArray()
{
$this->loadFromYamlCollectionFixture('nonvalid.yml');
}
/**
* Test that route parent not found.
*
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Cannot find parent resource with name
*/
public function testLoadThrowsExceptionWithInvalidRouteParent()
{
$this->loadFromYamlCollectionFixture('invalid_route_parent.yml');
}
/**
* Test that YAML collection gets parsed correctly.
*/
public function testUsersFixture()
{
$collection = $this->loadFromYamlCollectionFixture('users_collection.yml');
$etalonRoutes = $this->loadEtalonRoutesInfo('users_collection.yml');
foreach ($etalonRoutes as $name => $params) {
$route = $collection->get($name);
$methods = $route->getMethods();
$this->assertNotNull($route, $name);
$this->assertEquals($params['path'], $route->getPath(), $name);
$this->assertEquals($params['methods'][0], $methods[0], $name);
$this->assertContains($params['controller'], $route->getDefault('_controller'), $name);
}
}
/**
* Test that YAML collection with custom prefixes gets parsed correctly.
*/
public function testPrefixedUsersFixture()
{
$collection = $this->loadFromYamlCollectionFixture('prefixed_users_collection.yml');
$etalonRoutes = $this->loadEtalonRoutesInfo('prefixed_users_collection.yml');
foreach ($etalonRoutes as $name => $params) {
$route = $collection->get($name);
$methods = $route->getMethods();
$this->assertNotNull($route, $name);
$this->assertEquals($params['path'], $route->getPath(), $name);
$this->assertEquals($params['methods'][0], $methods[0], $name);
$this->assertContains($params['controller'], $route->getDefault('_controller'), $name);
}
}
/**
* Test that YAML collection with named prefixes gets parsed correctly.
*/
public function testNamedPrefixedReportsFixture()
{
$collection = $this->loadFromYamlCollectionFixture('named_prefixed_reports_collection.yml');
$etalonRoutes = $this->loadEtalonRoutesInfo('named_prefixed_reports_collection.yml');
foreach ($etalonRoutes as $name => $params) {
$route = $collection->get($name);
$methods = $route->getMethods();
$this->assertNotNull($route, $name);
$this->assertEquals($params['path'], $route->getPath(), $name);
$this->assertEquals($params['methods'][0], $methods[0], $name);
$this->assertContains($params['controller'], $route->getDefault('_controller'), $name);
}
}
/**
* Test that collection with named prefixes has no duplicates.
*/
public function testNamedPrefixedReportsFixtureHasNoDuplicates()
{
$names = [];
$collection = $this->loadFromYamlCollectionFixture('named_prefixed_reports_collection.yml');
foreach ($collection as $route) {
$names[] = $route->getPath();
}
$this->assertEquals(count($names), count(array_unique($names)));
}
public function testForwardOptionsRequirementsAndDefaults()
{
$collection = $this->loadFromYamlCollectionFixture('routes_with_options_requirements_and_defaults.yml');
foreach ($collection as $route) {
$this->assertTrue($route->getOption('expose'));
$this->assertEquals('[a-z]+', $route->getRequirement('slug'));
$this->assertEquals('home', $route->getDefault('slug'));
}
}
public function testManualRoutes()
{
$collection = $this->loadFromYamlCollectionFixture('routes.yml');
$route = $collection->get('get_users');
$this->assertEquals('/users.{_format}', $route->getPath());
$this->assertEquals('json|xml|html', $route->getRequirement('_format'));
$this->assertEquals('FOSRestBundle:UsersController:getUsers', $route->getDefault('_controller'));
}
public function testManualRoutesWithoutIncludeFormat()
{
$collection = $this->loadFromYamlCollectionFixture('routes.yml', false);
$route = $collection->get('get_users');
$this->assertEquals('/users', $route->getPath());
}
public function testManualRoutesWithFormats()
{
$collection = $this->loadFromYamlCollectionFixture(
'routes.yml',
true,
[
'json' => false,
]
);
$route = $collection->get('get_users');
$this->assertEquals('json', $route->getRequirement('_format'));
}
public function testManualRoutesWithDefaultFormat()
{
$collection = $this->loadFromYamlCollectionFixture(
'routes.yml',
true,
[
'json' => false,
'xml' => false,
'html' => true,
],
'xml'
);
$route = $collection->get('get_users');
$this->assertEquals('xml', $route->getDefault('_format'));
}
/**
* Tests that we can use "controller as service" even if the controller is registered in the
* container by its class name.
*
* @link https://github.com/FriendsOfSymfony/FOSRestBundle/issues/604#issuecomment-40284026
*/
public function testControllerAsServiceWithClassName()
{
$controller = new UsersController();
// We register the controller in the fake container by its class name
$this->container = new Container();
$this->container->set(get_class($controller), $controller);
$collection = $this->loadFromYamlCollectionFixture('users_collection.yml');
$route = $collection->get('get_users');
// We check that it's "controller:method" (controller as service) and not "controller::method"
$this->assertEquals(
'FOS\RestBundle\Tests\Fixtures\Controller\UsersController:getUsersAction',
$route->getDefault('_controller')
);
$this->container = null;
}
/**
* Test that YAML collection with named prefixes gets parsed correctly with inheritance.
*/
public function testNamedPrefixedBaseReportsFixture()
{
$collection = $this->loadFromYamlCollectionFixture('base_named_prefixed_reports_collection.yml');
$etalonRoutes = $this->loadEtalonRoutesInfo('base_named_prefixed_reports_collection.yml');
foreach ($etalonRoutes as $name => $params) {
$route = $collection->get($name);
$methods = $route->getMethods();
$this->assertNotNull($route, $name);
$this->assertEquals($params['path'], $route->getPath(), $name);
$this->assertEquals($params['method'], $methods[0], $name);
$this->assertContains($params['controller'], $route->getDefault('_controller'), $name);
}
$name = 'api_get_billing_payments';
$this->assertArrayNotHasKey($name, $etalonRoutes);
}
public function testRoutesWithPattern()
{
$collection = $this->loadFromYamlCollectionFixture('routes_with_pattern.yml');
$route = $collection->get('get_users');
$this->assertEquals('/users.{_format}', $route->getPath());
}
/**
* Load routes collection from YAML fixture routes under Tests\Fixtures directory.
*
* @param string $fixtureName name of the class fixture
* @param bool $includeFormat whether or not the requested view format must be included in the route path
* @param string[] $formats supported view formats
* @param string $defaultFormat default view format
*
* @return RouteCollection
*/
protected function loadFromYamlCollectionFixture(
$fixtureName,
$includeFormat = true,
array $formats = [
'json' => false,
'xml' => false,
'html' => true,
],
$defaultFormat = null
) {
$collectionLoader = new RestYamlCollectionLoader(
new FileLocator([__DIR__.'/../../Fixtures/Routes']),
new RestRouteProcessor(),
$includeFormat,
$formats,
$defaultFormat
);
$controllerLoader = $this->getControllerLoader();
// LoaderResolver sets the resolvers on the loaders passed to it
new LoaderResolver([$collectionLoader, $controllerLoader]);
return $collectionLoader->load($fixtureName, 'rest');
}
}
Tests/Util/ExceptionValueMapTest.php 0000666 00000002465 13052362416 0013542 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests;
use FOS\RestBundle\Util\ExceptionValueMap;
/**
* ExceptionValueMap test.
*
* @author Mikhail Shamin
*/
class ExceptionValueMapTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ExceptionValueMap
*/
private $valueMap;
protected function setUp()
{
$map = [
\LogicException::class => 'logic',
\DomainException::class => 'domain',
\OutOfBoundsException::class => null,
];
$this->valueMap = new ExceptionValueMap($map);
}
public function testResolveExceptionValueIsFound()
{
$this->assertSame('logic', $this->valueMap->resolveException(new \LogicException()));
}
public function testResolveExceptionValueIsFoundBySubclass()
{
$this->assertSame('logic', $this->valueMap->resolveException(new \BadFunctionCallException()));
}
public function testResolveExceptionValueNotFound()
{
$this->assertFalse($this->valueMap->resolveException(new \RuntimeException()));
}
}
Tests/View/JsonpHandlerTest.php 0000666 00000006775 13052362416 0012545 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\View;
use FOS\RestBundle\View\JsonpHandler;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Jsonp handler test.
*
* @author Victor Berchet
* @author Lukas K. Smith
*/
class JsonpHandlerTest extends \PHPUnit_Framework_TestCase
{
private $router;
private $serializer;
private $templating;
private $requestStack;
protected function setUp()
{
$this->router = $this->getMockBuilder('Symfony\Component\Routing\RouterInterface')->getMock();
$this->serializer = $this->getMockBuilder('FOS\RestBundle\Serializer\Serializer')->getMock();
$this->templating = $this->getMockBuilder('Symfony\Bundle\FrameworkBundle\Templating\EngineInterface')->getMock();
$this->requestStack = new RequestStack();
}
/**
* @dataProvider handleDataProvider
*/
public function testHandle($query)
{
$data = ['foo' => 'bar'];
$viewHandler = new ViewHandler($this->router, $this->serializer, $this->templating, $this->requestStack, ['jsonp' => false]);
$jsonpHandler = new JsonpHandler(key($query));
$viewHandler->registerHandler('jsonp', [$jsonpHandler, 'createResponse']);
$this->serializer
->expects($this->once())
->method('serialize')
->will($this->returnValue(var_export($data, true)));
$view = new View($data);
$view->setFormat('jsonp');
$request = new Request($query);
$response = $viewHandler->handle($view, $request);
$this->assertEquals('/**/'.reset($query).'('.var_export($data, true).')', $response->getContent());
}
public static function handleDataProvider()
{
return [
'jQuery callback syntax' => [['callback' => 'jQuery171065827149929257_1343950463342']],
'YUI callback syntax' => [['callback' => 'YUI.Env.JSONP._12345']],
'jQuery custom syntax' => [['custom' => 'jQuery171065827149929257_1343950463342']],
'YUI custom syntax' => [['custom' => 'YUI.Env.JSONP._12345']],
];
}
/**
* @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
* @dataProvider getCallbackFailureDataProvider
*/
public function testGetCallbackFailure(Request $request)
{
$data = ['foo' => 'bar'];
$viewHandler = new ViewHandler($this->router, $this->serializer, $this->templating, $this->requestStack, ['jsonp' => false]);
$jsonpHandler = new JsonpHandler('callback');
$viewHandler->registerHandler('jsonp', [$jsonpHandler, 'createResponse']);
$this->serializer
->expects($this->once())
->method('serialize')
->will($this->returnValue(var_export($data, true)));
$data = ['foo' => 'bar'];
$view = new View($data);
$view->setFormat('jsonp');
$viewHandler->handle($view, $request);
}
public function getCallbackFailureDataProvider()
{
return [
'no callback' => [new Request()],
'incorrect callback param name' => [new Request(['foo' => 'bar'])],
];
}
}
Tests/View/ViewHandlerTest.php 0000666 00000050417 13052362416 0012356 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\View;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandler;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\Forms;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
/**
* View test.
*
* @author Victor Berchet
*/
class ViewHandlerTest extends \PHPUnit_Framework_TestCase
{
private $router;
private $serializer;
private $templating;
private $requestStack;
protected function setUp()
{
$this->router = $this->getMockBuilder('Symfony\Component\Routing\RouterInterface')->getMock();
$this->serializer = $this->getMockBuilder('FOS\RestBundle\Serializer\Serializer')->getMock();
$this->templating = $this->getMockBuilder('Symfony\Bundle\FrameworkBundle\Templating\EngineInterface')->getMock();
$this->requestStack = new RequestStack();
}
/**
* @dataProvider supportsFormatDataProvider
*/
public function testSupportsFormat($expected, $formats, $customFormatName)
{
$viewHandler = $this->createViewHandler($formats);
$viewHandler->registerHandler($customFormatName, function () {});
$this->assertEquals($expected, $viewHandler->supports('html'));
}
public static function supportsFormatDataProvider()
{
return [
'not supported' => [false, ['json' => false], 'xml'],
'html default' => [true, ['html' => true], 'xml'],
'html custom' => [true, ['json' => false], 'html'],
'html both' => [true, ['html' => true], 'html'],
];
}
public function testRegisterHandle()
{
$viewHandler = $this->createViewHandler();
$viewHandler->registerHandler('html', ($callback = function () {}));
$this->assertAttributeEquals(['html' => $callback], 'customHandlers', $viewHandler);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testRegisterHandleExpectsException()
{
$viewHandler = $this->createViewHandler();
$viewHandler->registerHandler('json', new \stdClass());
}
/**
* @dataProvider getStatusCodeDataProvider
*/
public function testGetStatusCode(
$expected,
$data,
$isSubmitted,
$isValid,
$isSubmittedCalled,
$isValidCalled,
$noContentCode,
$actualStatusCode = null
) {
$reflectionMethod = new \ReflectionMethod(ViewHandler::class, 'getStatusCode');
$reflectionMethod->setAccessible(true);
$form = $this->getMockBuilder('Symfony\Component\Form\Form')
->disableOriginalConstructor()
->setMethods(array('isSubmitted', 'isValid'))
->getMock();
$form
->expects($this->exactly($isSubmittedCalled))
->method('isSubmitted')
->will($this->returnValue($isSubmitted));
$form
->expects($this->exactly($isValidCalled))
->method('isValid')
->will($this->returnValue($isValid));
if ($data) {
$data = ['form' => $form];
}
$view = new View($data ?: null, $actualStatusCode ?: null);
$viewHandler = $this->createViewHandler([], $expected, $noContentCode);
$this->assertEquals($expected, $reflectionMethod->invoke($viewHandler, $view, $view->getData()));
}
public static function getStatusCodeDataProvider()
{
return [
'no data' => [Response::HTTP_OK, false, false, false, 0, 0, Response::HTTP_OK],
'no data with 204' => [Response::HTTP_NO_CONTENT, false, false, false, 0, 0, Response::HTTP_NO_CONTENT],
'no data, but custom response code' => [Response::HTTP_OK, false, false, false, 0, 0, Response::HTTP_NO_CONTENT, Response::HTTP_OK],
'form key form not bound' => [Response::HTTP_OK, true, false, true, 1, 0, Response::HTTP_OK],
'form key form is bound and invalid' => [Response::HTTP_FORBIDDEN, true, true, false, 1, 1, Response::HTTP_OK],
'form key form bound and valid' => [Response::HTTP_OK, true, true, true, 1, 1, Response::HTTP_OK],
'form key null form bound and valid' => [Response::HTTP_OK, true, true, true, 1, 1, Response::HTTP_OK],
];
}
/**
* @dataProvider createResponseWithLocationDataProvider
*/
public function testCreateResponseWithLocation($expected, $format, $forceRedirects, $noContentCode)
{
$viewHandler = $this->createViewHandler(['html' => true, 'json' => false, 'xml' => false], Response::HTTP_BAD_REQUEST, $noContentCode, false, $forceRedirects);
$view = new View();
$view->setLocation('foo');
$returnedResponse = $viewHandler->createResponse($view, new Request(), $format);
$this->assertEquals($expected, $returnedResponse->getStatusCode());
$this->assertEquals('foo', $returnedResponse->headers->get('location'));
}
public static function createResponseWithLocationDataProvider()
{
return [
'empty force redirects' => [200, 'xml', ['json' => 403], Response::HTTP_OK],
'empty force redirects with 204' => [204, 'xml', ['json' => 403], Response::HTTP_NO_CONTENT],
'force redirects response is redirect' => [200, 'json', [], Response::HTTP_OK],
'force redirects response not redirect' => [403, 'json', ['json' => 403], Response::HTTP_OK],
'html and redirect' => [301, 'html', ['html' => 301], Response::HTTP_OK],
];
}
public function testCreateResponseWithLocationAndData()
{
$testValue = ['naviter' => 'oudie'];
$this->setupMockedSerializer($testValue);
$viewHandler = $this->createViewHandler(['json' => false]);
$view = new View();
$view->setStatusCode(Response::HTTP_CREATED);
$view->setLocation('foo');
$view->setData($testValue);
$returnedResponse = $viewHandler->createResponse($view, new Request(), 'json');
$this->assertEquals('foo', $returnedResponse->headers->get('location'));
$this->assertEquals(var_export($testValue, true), $returnedResponse->getContent());
}
public function testCreateResponseWithRoute()
{
$doRoute = function ($name, $parameters) {
$route = '/';
foreach ($parameters as $name => $value) {
$route .= sprintf('%s/%s/', $name, $value);
}
return $route;
};
$this->router
->expects($this->any())
->method('generate')
->will($this->returnCallback($doRoute));
$viewHandler = $this->createViewHandler(['json' => false]);
$view = new View();
$view->setStatusCode(Response::HTTP_CREATED);
$view->setRoute('foo');
$view->setRouteParameters(['id' => 2]);
$returnedResponse = $viewHandler->createResponse($view, new Request(), 'json');
$this->assertEquals('/id/2/', $returnedResponse->headers->get('location'));
}
public function testShouldReturnErrorResponseWhenDataContainsFormAndFormIsNotValid()
{
$this->serializer
->expects($this->once())
->method('serialize')
->will($this->returnCallback(function ($data, $format, $context) {
return serialize(array($data, $context));
}));
//test
$viewHandler = $this->createViewHandler(null, $expectedFailedValidationCode = Response::HTTP_I_AM_A_TEAPOT);
$form = $this->getMockBuilder('Symfony\\Component\\Form\\Form')
->disableOriginalConstructor()
->setMethods(array('createView', 'getData', 'isValid', 'isSubmitted'))
->getMock();
$form
->expects($this->any())
->method('isValid')
->will($this->returnValue(false));
$form
->expects($this->any())
->method('isSubmitted')
->will($this->returnValue(true));
$view = new View($form);
$response = $viewHandler->createResponse($view, new Request(), 'json');
list($data, $context) = unserialize($response->getContent());
$this->assertInstanceOf('Symfony\\Component\\Form\\Form', $data);
$this->assertEquals($expectedFailedValidationCode, $context->getAttribute('status_code'));
}
/**
* @dataProvider createResponseWithoutLocationDataProvider
*/
public function testCreateResponseWithoutLocation($format, $expected, $createViewCalls = 0, $formIsValid = false, $form = false)
{
$viewHandler = $this->createViewHandler(['html' => true, 'json' => false]);
if ('html' === $format) {
$this->templating
->expects($this->once())
->method('render')
->will($this->returnValue(var_export($expected, true)));
} else {
$this->setupMockedSerializer($expected);
}
if ($form) {
$data = $this->getMockBuilder('Symfony\Component\Form\Form')
->disableOriginalConstructor()
->setMethods(array('createView', 'getData', 'isValid', 'isSubmitted'))
->getMock();
$data
->expects($this->exactly($createViewCalls))
->method('createView')
->will($this->returnValue(['bla' => 'toto']));
$data
->expects($this->exactly($createViewCalls))
->method('getData')
->will($this->returnValue(['bla' => 'toto']));
$data
->expects($this->any())
->method('isValid')
->will($this->returnValue($formIsValid));
$data
->expects($this->any())
->method('isSubmitted')
->will($this->returnValue(true));
} else {
$data = ['foo' => 'bar'];
}
$view = new View($data);
$response = $viewHandler->createResponse($view, new Request(), $format);
$this->assertEquals(var_export($expected, true), $response->getContent());
}
private function setupMockedSerializer($expected)
{
$this->serializer
->expects($this->once())
->method('serialize')
->will($this->returnValue(var_export($expected, true)));
}
public static function createResponseWithoutLocationDataProvider()
{
return [
'not templating aware no form' => ['json', ['foo' => 'bar']],
'templating aware no form' => ['html', ['foo' => 'bar']],
'templating aware and form' => ['html', ['data' => ['bla' => 'toto']], 1, true, true],
'not templating aware and invalid form' => ['json', ['data' => [0 => 'error', 1 => 'error']], 0, false, true],
];
}
/**
* @dataProvider createSerializeNullDataProvider
*/
public function testSerializeNull($expected, $serializeNull)
{
$viewHandler = $this->createViewHandler(['json' => false], 404, 200, $serializeNull);
if ($serializeNull) {
$this->serializer
->expects($this->once())
->method('serialize')
->will($this->returnValue(json_encode(null)));
} else {
$this->serializer
->expects($this->never())
->method('serialize');
}
$response = $viewHandler->createResponse(new View(), new Request(), 'json');
$this->assertEquals($expected, $response->getContent());
}
public static function createSerializeNullDataProvider()
{
return [
'should serialize null' => ['null', true],
'should not serialize null' => ['', false],
];
}
/**
* @dataProvider createSerializeNullDataValuesDataProvider
*/
public function testSerializeNullDataValues($expected, $serializeNull)
{
$viewHandler = $this->createViewHandler(['json' => false], 404, 200);
$viewHandler->setSerializeNullStrategy($serializeNull);
$contextMethod = new \ReflectionMethod($viewHandler, 'getSerializationContext');
$contextMethod->setAccessible(true);
$view = new View();
$context = $contextMethod->invoke($viewHandler, $view);
$this->assertEquals($expected, $context->getSerializeNull());
}
public static function createSerializeNullDataValuesDataProvider()
{
return [
'should serialize null values' => [true, true],
'should not serialize null values' => [false, false],
];
}
/**
* @dataProvider createResponseDataProvider
*/
public function testCreateResponse($expected, $formats)
{
$viewHandler = $this->createViewHandler($formats);
$viewHandler->registerHandler('html', function ($handler, $view) { return $view; });
$response = $viewHandler->handle(new View(null, $expected), new Request());
$this->assertEquals($expected, $response->getStatusCode());
}
public static function createResponseDataProvider()
{
return [
'no handler' => [Response::HTTP_UNSUPPORTED_MEDIA_TYPE, []],
'custom handler' => [200, []],
'transform called' => [200, ['json' => false]],
];
}
public function testHandle()
{
$viewHandler = $this->createViewHandler(['html' => true]);
$this->templating
->expects($this->once())
->method('render')
->will($this->returnValue(''));
$this->requestStack->push(new Request());
$data = ['foo' => 'bar'];
$view = new View($data);
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $viewHandler->handle($view));
}
public function testHandleCustom()
{
$viewHandler = $this->createViewHandler([]);
$viewHandler->registerHandler('html', ($callback = function () { return 'foo'; }));
$this->requestStack->push(new Request());
$data = ['foo' => 'bar'];
$view = new View($data);
$this->assertEquals('foo', $viewHandler->handle($view));
}
/**
* @expectedException \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function testHandleNotSupported()
{
$viewHandler = $this->createViewHandler([]);
$this->requestStack->push(new Request());
$data = ['foo' => 'bar'];
$view = new View($data);
$viewHandler->handle($view);
}
/**
* @dataProvider prepareTemplateParametersDataProvider
*/
public function testPrepareTemplateParametersWithProvider($viewData, $templateData, $expected)
{
$handler = $this->createViewHandler(['html' => true]);
$view = new View();
$view->setFormat('html');
$view->setData($viewData);
if (null !== $templateData) {
$view->setTemplateData($templateData);
}
$this->assertEquals($expected, $handler->prepareTemplateParameters($view));
}
public function prepareTemplateParametersDataProvider()
{
$object = new \stdClass();
$formView = new FormView();
$form = $this->getMockBuilder('Symfony\Component\Form\Form')
->setMethods(['createView', 'getData'])
->disableOriginalConstructor()
->getMock();
$form
->expects($this->once())
->method('createView')
->will($this->returnValue($formView));
$form
->expects($this->once())
->method('getData')
->will($this->returnValue($formView));
$self = $this;
return [
'assoc array does not change' => [['foo' => 'bar'], null, ['foo' => 'bar']],
'ordered array is wrapped as data key' => [['foo', 'bar'], null, ['data' => ['foo', 'bar']]],
'object is wrapped as data key' => [$object, null, ['data' => $object]],
'form is wrapped as form key' => [$form, null, ['form' => $formView, 'data' => $formView]],
'template data is added to data' => [['foo' => 'bar'], ['baz' => 'qux'], ['foo' => 'bar', 'baz' => 'qux']],
'lazy template data is added to data' => [
['foo' => 'bar'],
function () { return ['baz' => 'qux']; },
['foo' => 'bar', 'baz' => 'qux'],
],
'lazy template data have reference to viewhandler and view' => [
['foo' => 'bar'],
function ($handler, $view) use ($self) {
$self->assertInstanceOf('FOS\\RestBundle\\View\\ViewHandlerInterface', $handler);
$self->assertInstanceOf('FOS\\RestBundle\\View\\View', $view);
$self->assertTrue($handler->isFormatTemplating($view->getFormat()));
return ['format' => $view->getFormat()];
},
['foo' => 'bar', 'format' => 'html'],
],
];
}
public function testConfigurableViewHandlerInterface()
{
//test
$viewHandler = $this->createViewHandler();
$viewHandler->setExclusionStrategyGroups('bar');
$viewHandler->setExclusionStrategyVersion('1.1');
$viewHandler->setSerializeNullStrategy(true);
$contextMethod = new \ReflectionMethod($viewHandler, 'getSerializationContext');
$contextMethod->setAccessible(true);
$view = new View();
$context = $contextMethod->invoke($viewHandler, $view);
$this->assertEquals(['bar'], $context->getGroups());
$this->assertEquals('1.1', $context->getVersion());
$this->assertTrue($context->getSerializeNull());
}
/**
* @dataProvider exceptionWrapperSerializeResponseContentProvider
*
* @param string $format
*/
public function testCreateResponseWithFormErrorsAndSerializationGroups($format)
{
// BC hack for Symfony 2.7 where FormType's didn't yet get configured via the FQN
$formType = method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\TextType'
: 'text'
;
$form = Forms::createFormFactory()->createBuilder()
->add('name', $formType)
->add('description', $formType)
->getForm();
$form->get('name')->addError(new FormError('Invalid name'));
$exceptionWrapper = [
'status_code' => 400,
'message' => 'Validation Failed',
'errors' => $form,
];
$view = new View($exceptionWrapper);
$view->getContext()->addGroups(array('Custom'));
$translatorMock = $this->getMockBuilder('Symfony\Component\Translation\TranslatorInterface')
->setMethods(array('trans', 'transChoice', 'setLocale', 'getLocale'))
->getMock();
$translatorMock
->expects($this->any())
->method('trans')
->will($this->returnArgument(0));
$viewHandler = $this->createViewHandler([]);
$response = $viewHandler->createResponse($view, new Request(), $format);
$viewHandler = $this->createViewHandler([]);
$view2 = new View($exceptionWrapper);
$response2 = $viewHandler->createResponse($view2, new Request(), $format);
$this->assertEquals($response->getContent(), $response2->getContent());
}
/**
* @return array
*/
public function exceptionWrapperSerializeResponseContentProvider()
{
return [
'json' => ['json'],
'xml' => ['xml'],
];
}
private function createViewHandler($formats = null, $failedValidationCode = Response::HTTP_BAD_REQUEST, $emptyContentCode = Response::HTTP_NO_CONTENT, $serializeNull = false, $forceRedirects = null, $defaultEngine = 'twig')
{
return new ViewHandler(
$this->router,
$this->serializer,
$this->templating,
$this->requestStack,
$formats,
$failedValidationCode,
$emptyContentCode,
$serializeNull,
$forceRedirects,
$defaultEngine
);
}
}
Tests/View/ViewTest.php 0000666 00000011022 13052362416 0011045 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Tests\View;
use FOS\RestBundle\View\View;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Symfony\Component\HttpFoundation\Response;
/**
* View test.
*
* @author Victor Berchet
*/
class ViewTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \InvalidArgumentException
*/
public function testSetTemplateTemplateFormat()
{
$view = new View();
$view->setTemplate('foo');
$this->assertEquals('foo', $view->getTemplate());
$view->setTemplate($template = new TemplateReference());
$this->assertEquals($template, $view->getTemplate());
$view->setTemplate([]);
}
public function testSetLocation()
{
$url = 'users';
$code = 500;
$view = View::createRedirect($url, $code);
$this->assertAttributeEquals($url, 'location', $view);
$this->assertAttributeEquals(null, 'route', $view);
$this->assertEquals($code, $view->getResponse()->getStatusCode());
$view = new View();
$location = 'location';
$view->setLocation($location);
$this->assertEquals($location, $view->getLocation());
}
public function testSetRoute()
{
$routeName = 'users';
$view = View::createRouteRedirect($routeName, [], Response::HTTP_CREATED);
$this->assertAttributeEquals($routeName, 'route', $view);
$this->assertAttributeEquals(null, 'location', $view);
$this->assertEquals(Response::HTTP_CREATED, $view->getResponse()->getStatusCode());
$view->setLocation($routeName);
$this->assertAttributeEquals($routeName, 'location', $view);
$this->assertAttributeEquals(null, 'route', $view);
$view = new View();
$route = 'route';
$view->setRoute($route);
$this->assertEquals($route, $view->getRoute());
}
/**
* @dataProvider setDataDataProvider
*/
public function testSetData($data)
{
$view = new View();
$view->setData($data);
$this->assertEquals($data, $view->getData());
}
public static function setDataDataProvider()
{
return [
'null as data' => [null],
'array as data' => [['foo' => 'bar']],
];
}
/**
* @dataProvider setTemplateDataDataProvider
*/
public function testSetTemplateData($templateData)
{
$view = new View();
$view->setTemplateData($templateData);
$this->assertEquals($templateData, $view->getTemplateData());
}
public static function setTemplateDataDataProvider()
{
return [
'null as data' => [null],
'array as data' => [['foo' => 'bar']],
'function as data' => [function () {}],
];
}
public function testSetEngine()
{
$view = new View();
$engine = 'bar';
$view->setEngine($engine);
$this->assertEquals($engine, $view->getEngine());
}
public function testSetFormat()
{
$view = new View();
$format = 'bar';
$view->setFormat($format);
$this->assertEquals($format, $view->getFormat());
}
public function testSetHeaders()
{
$view = new View();
$headers = ['foo' => 'bar'];
$expected = ['foo' => ['bar'], 'cache-control' => ['no-cache']];
$view->setHeaders($headers);
$this->assertEquals($expected, $view->getHeaders());
}
public function testHeadersInConstructorAreAssignedToResponseObject()
{
$headers = ['foo' => 'bar'];
$expected = ['foo' => ['bar'], 'cache-control' => ['no-cache']];
$view = new View(null, null, $headers);
$this->assertEquals($expected, $view->getHeaders());
}
public function testSetStatusCode()
{
$view = new View();
$code = 404;
$view->setStatusCode($code);
$this->assertEquals($code, $view->getStatusCode());
$this->assertEquals($code, $view->getResponse()->getStatusCode());
}
public function testGetStatusCodeFromResponse()
{
$view = new View();
$this->assertNull($view->getStatusCode());
$this->assertEquals(Response::HTTP_OK, $view->getResponse()->getStatusCode()); // default code of the response.
}
}
UPGRADING-2.0.md 0000666 00000013113 13052362416 0006710 0 ustar 00 Upgrading From 1.x To 2.0
=========================
* The `RedirectView` and `RouteRedirect` view classes were removed. Use
`View::createRedirect()` and `View::createRouteRedirect()` instead.
**Note**: the default status code for a route redirect has changed from
HTTP_CREATED (201) to HTTP_FOUND (302).
* The `FOS\RestBundle\Util\ViolationFormatter` class and the
`FOS\RestBundle\Util\ViolationFormatterInterface` were removed.
Catch specialized exception classes instead of checking specific
exception messages.
* The `ViolationFormatterInterface` argument of the constructor of
the `ParamFetcher` class was removed.
* The SensioFrameworkExtraBundle view annotations must be enabled to
use the `ViewResponseListener`:
```yml
# app/config/config.yml
sensio_framework_extra:
view:
annotations: true
```
* dropped support for the legacy
`Symfony\Component\Validator\ValidatorInterface`
* removed `FOS\RestBundle\Util\Codes` in favor of
`Symfony\Component\HttpFoundation\Response` constants
* compatibility with Symfony <2.7, JMS Serializer/SerializerBundle <1.0
and SensioFrameworkExtraBundle <3.0 was dropped
* constructor signature of DisableCSRFExtension was changed
* constructor signatures of most of the classes which used the container
were changed
* removed `callback_filter` configuration option for the `jsonp_handler`
* removed `fos_rest.format_listener.media_type` configuration option.
Use the versioning section instead:
```yml
# config.yml
versioning: true
```
* the `exception_wrapper_handler` config option was removed. Use normalizers instead.
Before:
```yml
# config.yml
fos_rest:
view:
exception_wrapper_handler: AppBundle\ExceptionWrapperHandler
```
```php
namespace AppBundle;
class ExceptionWrapperHandler implements ExceptionWrapperHandlerInterface
{
public function wrap($data)
{
return new ExceptionWrapper(array('status_code' => 'foo'));
}
}
```
After (if you use the Symfony serializer):
```yml
# services.yml
services:
app_bundle.exception_normalizer:
class: AppBundle\Normalizer\ExceptionNormalizer
tags:
- { name: serializer.normalizer }
```
```php
namespace AppBundle\Normalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class ExceptionNormalizer implements NormalizerInterface
{
public function normalize($object, $format = null, array $context = array())
{
return array('status_code' => 'foo');
}
public function supportsNormalization($data, $format = null)
{
return $data instanceof \My\Exception;
}
}
```
* removed all `.class` parameters, instead overwriting services via
explicit Bundle configuration is preferred
* renamed `AbstractScalarParam::$array` to `AbstractScalarParam::$map`
Before:
```php
namespace AppBundle\Controller;
class MyController
{
/**
* @RequestParam(name="foo", array=true)
*/
public function myAction()
{
// ...
}
}
```
After:
```php
namespace AppBundle\Controller;
class MyController
{
/**
* @RequestParam(name="foo", map=true)
*/
public function myAction()
{
// ...
}
}
```
* added `ControllerTrait` for developers that prefer to use DI for their controllers instead of extending ``FOSRestController``
* when having an action called ``lockUserAction``, then it will have to
use the http method ``LOCK`` (RFC-2518) instead of ``PATCH``.
The following methods are affected by this change:
* `COPY`
* `LOCK`
* `MKCOL`
* `MOVE`
* `PROPFIND`
* `PROPPATCH`
* `UNLOCK`
* removed the ability of the `AccessDeniedListener` to render a response.
Use the FOSRestBundle or the twig exception controller in complement.
Before:
```yml
# config.yml
fos_rest:
access_denied_listener: true
```
After:
```yml
# config.yml
fos_rest:
access_denied_listener: true
exception: true # Activates the FOSRestBundle exception controller
```
* changed the priority of `RequestBodyParamConverter` to `-50`
* made silent the `RequestBodyParamConverter` when a parameter is
optional and it can't resolve it
* removed the `format_negotiator` option `exception_fallback_format`;
you can match the `ExceptionController` thanks to the `attributes`
option instead
Before:
```yml
# config.yml
fos_rest:
format_listener:
rules:
- { path: ^/, fallback_format: html, exception_fallback_format: json }
```
After:
```yml
# config.yml
fos_rest:
format_listener:
rules:
- { path: ^/, fallback_format: json, attributes: { _controller: FOS\RestBundle\Controller\ExceptionController } }
- { path: ^/, fallback_format: html } }
```
* `View::setSerializationContext` and `View::getSerializationContext`
have been removed. Use `View::setContext` and `View::getContext`
together with the new Context class instead.
Before:
```php
use JMS\Serializer\SerializationContext;
$view = new View();
$context = new SerializationContext();
$view->setSerializationContext($context);
$context = $view->getSerializationContext();
```
After:
```php
use FOS\RestBundle\Context\Context;
$view = new View();
$context = new Context();
$view->setContext($context);
$context = $view->getContext();
```
Util/ExceptionValueMap.php 0000666 00000003065 13052362416 0011575 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Util;
/**
* Stores map of values mapped to exception class
* Resolves value by exception.
*
* @author Mikhail Shamin
*/
class ExceptionValueMap
{
/**
* Map of values mapped to exception class
* key => exception class
* value => value associated with exception.
*
* @var array
*/
private $map;
/**
* @param array $map
*/
public function __construct(array $map)
{
$this->map = $map;
}
/**
* Resolves the value corresponding to an exception object.
*
* @param \Exception $exception
*
* @return mixed|false Value found or false is not found
*/
public function resolveException(\Exception $exception)
{
return $this->doResolveClass(get_class($exception));
}
/**
* Resolves the value corresponding to an exception class.
*
* @param string $class
*
* @return mixed|false if not found
*/
private function doResolveClass($class)
{
foreach ($this->map as $mapClass => $value) {
if (!$value) {
continue;
}
if ($class === $mapClass || is_subclass_of($class, $mapClass)) {
return $value;
}
}
return false;
}
}
Util/ResolverTrait.php 0000666 00000003320 13052362416 0011003 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Util;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @author Ener-Getick
*
* @internal do not use this trait or its functions in your code
*/
trait ResolverTrait
{
/**
* @param ContainerInterface $container
* @param mixed $value
*
* @return mixed
*/
private function resolveValue(ContainerInterface $container, $value)
{
if (is_array($value)) {
foreach ($value as $key => $val) {
$value[$key] = $this->resolveValue($container, $val);
}
return $value;
}
if (!is_string($value)) {
return $value;
}
$escapedValue = preg_replace_callback('/%%|%([^%\s]++)%/', function ($match) use ($container, $value) {
// skip %%
if (!isset($match[1])) {
return '%%';
}
$resolved = $container->getParameter($match[1]);
if (is_string($resolved) || is_numeric($resolved)) {
return (string) $resolved;
}
throw new \RuntimeException(sprintf(
'The container parameter "%s" must be a string or numeric, but it is of type %s.',
$match[1],
$value,
gettype($resolved)
)
);
}, $value);
return str_replace('%%', '%', $escapedValue);
}
}
Util/StopFormatListenerException.php 0000666 00000000670 13052362416 0013666 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Util;
/**
* Thrown to stop the format negotiator from continuing so that no format is set.
*/
class StopFormatListenerException extends \Exception
{
}
Validator/Constraints/Regex.php 0000666 00000002047 13052362416 0012574 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Validator\Constraints;
use FOS\RestBundle\Util\ResolverTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraints\Regex as BaseRegex;
/**
* @Annotation
*
* @author Ener-Getick
*/
class Regex extends BaseRegex implements ResolvableConstraintInterface
{
use ResolverTrait;
private $resolved;
/**
* {@inheritdoc}
*/
public function validatedBy()
{
return 'Symfony\Component\Validator\Constraints\RegexValidator';
}
public function resolve(ContainerInterface $container)
{
if ($this->resolved) {
return;
}
$this->pattern = $this->resolveValue($container, $this->pattern);
$this->resolved = true;
}
}
Validator/Constraints/ResolvableConstraintInterface.php 0000666 00000000735 13052362416 0017510 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Validator\Constraints;
use Symfony\Component\DependencyInjection\ContainerInterface;
interface ResolvableConstraintInterface
{
public function resolve(ContainerInterface $container);
}
Version/ChainVersionResolver.php 0000666 00000002416 13052362416 0013025 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Version;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Ener-Getick
*/
class ChainVersionResolver implements VersionResolverInterface
{
/**
* @var VersionResolverInterface[]
*/
private $resolvers = [];
/**
* @var VersionResolverInterface[]
*/
public function __construct(array $resolvers)
{
foreach ($resolvers as $resolver) {
$this->addResolver($resolver);
}
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request)
{
foreach ($this->resolvers as $resolver) {
$version = $resolver->resolve($request);
if ($version !== false) {
return $version;
}
}
return false;
}
/**
* Adds a resolver.
*
* @param VersionResolverInterface $resolver
*/
public function addResolver(VersionResolverInterface $resolver)
{
$this->resolvers[] = $resolver;
}
}
Version/Resolver/HeaderVersionResolver.php 0000666 00000002030 13052362416 0014764 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Version\Resolver;
use FOS\RestBundle\Version\VersionResolverInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Ener-Getick
*/
class HeaderVersionResolver implements VersionResolverInterface
{
/**
* @var string
*/
private $headerName;
/**
* @param string $headerName
*/
public function __construct($headerName)
{
$this->headerName = $headerName;
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request)
{
if (!$request->headers->has($this->headerName)) {
return false;
}
$header = $request->headers->get($this->headerName);
return is_scalar($header) ? $header : strval($header);
}
}
Version/Resolver/MediaTypeVersionResolver.php 0000666 00000002045 13052362416 0015463 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Version\Resolver;
use FOS\RestBundle\Version\VersionResolverInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Ener-Getick
*/
class MediaTypeVersionResolver implements VersionResolverInterface
{
/**
* @var string
*/
private $regex;
/**
* @param string $regex
*/
public function __construct($regex)
{
$this->regex = $regex;
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request)
{
if (!$request->attributes->has('media_type') || false === preg_match($this->regex, $request->attributes->get('media_type'), $matches)) {
return false;
}
return isset($matches['version']) ? $matches['version'] : false;
}
}
Version/Resolver/QueryParameterVersionResolver.php 0000666 00000002075 13052362416 0016553 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Version\Resolver;
use FOS\RestBundle\Version\VersionResolverInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Ener-Getick
*/
class QueryParameterVersionResolver implements VersionResolverInterface
{
/**
* @var string
*/
private $parameterName;
/**
* @param string $parameterName
*/
public function __construct($parameterName)
{
$this->parameterName = $parameterName;
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request)
{
if (!$request->query->has($this->parameterName)) {
return false;
}
$parameter = $request->query->get($this->parameterName);
return is_scalar($parameter) ? $parameter : strval($parameter);
}
}
Version/VersionResolverInterface.php 0000666 00000001214 13052362416 0013676 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\Version;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Ener-Getick
*/
interface VersionResolverInterface
{
/**
* Resolves the version of a request.
*
* @param Request $request
*
* @return scalar|false Current version or false if not resolved
*/
public function resolve(Request $request);
}
View/ConfigurableViewHandlerInterface.php 0000666 00000001735 13052362416 0014555 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\View;
/**
* Specialized ViewInterface that allows dynamic configuration of JMS serializer context aspects.
*
* @author Lukas K. Smith
*/
interface ConfigurableViewHandlerInterface extends ViewHandlerInterface
{
/**
* Set the default serialization groups.
*
* @param array $groups
*/
public function setExclusionStrategyGroups($groups);
/**
* Set the default serialization version.
*
* @param string $version
*/
public function setExclusionStrategyVersion($version);
/**
* If nulls should be serialized.
*
* @param bool $isEnabled
*/
public function setSerializeNullStrategy($isEnabled);
}
View/JsonpHandler.php 0000666 00000003455 13052362416 0010573 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\View;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
/**
* Implements a custom handler for JSONP leveraging the ViewHandler.
*
* @author Lukas K. Smith
*/
class JsonpHandler
{
protected $callbackParam;
public function __construct($callbackParam)
{
$this->callbackParam = $callbackParam;
}
protected function getCallback(Request $request)
{
$callback = $request->query->get($this->callbackParam);
$validator = new \JsonpCallbackValidator();
if (!$validator->validate($callback)) {
throw new BadRequestHttpException('Invalid JSONP callback value');
}
return $callback;
}
/**
* Handles wrapping a JSON response into a JSONP response.
*
* @param ViewHandler $handler
* @param View $view
* @param Request $request
* @param string $format
*
* @return Response
*/
public function createResponse(ViewHandler $handler, View $view, Request $request, $format)
{
$response = $handler->createResponse($view, $request, 'json');
if ($response->isSuccessful()) {
$callback = $this->getCallback($request);
$response->setContent(sprintf('/**/%s(%s)', $callback, $response->getContent()));
$response->headers->set('Content-Type', $request->getMimeType($format));
}
return $response;
}
}
View/View.php 0000666 00000022455 13052362416 0007117 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\View;
use FOS\RestBundle\Context\Context;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\TemplateReferenceInterface;
/**
* Default View implementation.
*
* @author Johannes M. Schmitt
* @author Lukas K. Smith
*/
class View
{
/**
* @var mixed|null
*/
private $data;
/**
* @var int|null
*/
private $statusCode;
/**
* @var mixed|null
*/
private $templateData = [];
/**
* @var TemplateReference|string|null
*/
private $template;
/**
* @var string|null
*/
private $templateVar;
/**
* @var string|null
*/
private $engine;
/**
* @var string|null
*/
private $format;
/**
* @var string|null
*/
private $location;
/**
* @var string|null
*/
private $route;
/**
* @var array|null
*/
private $routeParameters;
/**
* @var Context
*/
private $context;
/**
* @var Response
*/
private $response;
/**
* Convenience method to allow for a fluent interface.
*
* @param mixed $data
* @param int $statusCode
* @param array $headers
*
* @return static
*/
public static function create($data = null, $statusCode = null, array $headers = [])
{
return new static($data, $statusCode, $headers);
}
/**
* Convenience method to allow for a fluent interface while creating a redirect to a
* given url.
*
* @param string $url
* @param int $statusCode
* @param array $headers
*
* @return static
*/
public static function createRedirect($url, $statusCode = Response::HTTP_FOUND, array $headers = [])
{
$view = static::create(null, $statusCode, $headers);
$view->setLocation($url);
return $view;
}
/**
* Convenience method to allow for a fluent interface while creating a redirect to a
* given route.
*
* @param string $route
* @param array $parameters
* @param int $statusCode
* @param array $headers
*
* @return static
*/
public static function createRouteRedirect(
$route,
array $parameters = [],
$statusCode = Response::HTTP_FOUND,
array $headers = []
) {
$view = static::create(null, $statusCode, $headers);
$view->setRoute($route);
$view->setRouteParameters($parameters);
return $view;
}
/**
* Constructor.
*
* @param mixed $data
* @param int $statusCode
* @param array $headers
*/
public function __construct($data = null, $statusCode = null, array $headers = [])
{
$this->setData($data);
$this->setStatusCode($statusCode);
$this->setTemplateVar('data');
if (!empty($headers)) {
$this->getResponse()->headers->replace($headers);
}
}
/**
* Sets the data.
*
* @param mixed $data
*
* @return View
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Set template variable.
*
* @param array|callable $data
*
* @return View
*/
public function setTemplateData($data = [])
{
$this->templateData = $data;
return $this;
}
/**
* Sets a header.
*
* @param string $name
* @param string $value
*
* @return View
*/
public function setHeader($name, $value)
{
$this->getResponse()->headers->set($name, $value);
return $this;
}
/**
* Sets the headers.
*
* @param array $headers
*
* @return View
*/
public function setHeaders(array $headers)
{
$this->getResponse()->headers->replace($headers);
return $this;
}
/**
* Sets the HTTP status code.
*
* @param int|null $code
*
* @return View
*/
public function setStatusCode($code)
{
if (null !== $code) {
$this->statusCode = $code;
}
return $this;
}
/**
* Sets the serialization context.
*
* @param Context $context
*
* @return View
*/
public function setContext(Context $context)
{
$this->context = $context;
return $this;
}
/**
* Sets template to use for the encoding.
*
* @param string|TemplateReferenceInterface $template
*
* @return View
*
* @throws \InvalidArgumentException if the template is neither a string nor an instance of TemplateReferenceInterface
*/
public function setTemplate($template)
{
if (!(is_string($template) || $template instanceof TemplateReferenceInterface)) {
throw new \InvalidArgumentException('The template should be a string or implement TemplateReferenceInterface');
}
$this->template = $template;
return $this;
}
/**
* Sets template variable name to be used in templating formats.
*
* @param string $templateVar
*
* @return View
*/
public function setTemplateVar($templateVar)
{
$this->templateVar = $templateVar;
return $this;
}
/**
* Sets the engine.
*
* @param string $engine
*
* @return View
*/
public function setEngine($engine)
{
$this->engine = $engine;
return $this;
}
/**
* Sets the format.
*
* @param string $format
*
* @return View
*/
public function setFormat($format)
{
$this->format = $format;
return $this;
}
/**
* Sets the location (implicitly removes the route).
*
* @param string $location
*
* @return View
*/
public function setLocation($location)
{
$this->location = $location;
$this->route = null;
return $this;
}
/**
* Sets the route (implicitly removes the location).
*
* @param string $route
*
* @return View
*/
public function setRoute($route)
{
$this->route = $route;
$this->location = null;
return $this;
}
/**
* Sets route data.
*
* @param array $parameters
*
* @return View
*/
public function setRouteParameters($parameters)
{
$this->routeParameters = $parameters;
return $this;
}
/**
* Sets the response.
*
* @param Response $response
*
* @return View
*/
public function setResponse(Response $response)
{
$this->response = $response;
return $this;
}
/**
* Gets the data.
*
* @return mixed|null
*/
public function getData()
{
return $this->data;
}
/**
* Gets the template data.
*
* @return mixed|null
*/
public function getTemplateData()
{
return $this->templateData;
}
/**
* Gets the HTTP status code.
*
* @return int|null
*/
public function getStatusCode()
{
return $this->statusCode;
}
/**
* Gets the headers.
*
* @return array
*/
public function getHeaders()
{
return $this->getResponse()->headers->all();
}
/**
* Gets the template.
*
* @return TemplateReferenceInterface|string|null
*/
public function getTemplate()
{
return $this->template;
}
/**
* Gets the template variable name.
*
* @return string|null
*/
public function getTemplateVar()
{
return $this->templateVar;
}
/**
* Gets the engine.
*
* @return string|null
*/
public function getEngine()
{
return $this->engine;
}
/**
* Gets the format.
*
* @return string|null
*/
public function getFormat()
{
return $this->format;
}
/**
* Gets the location.
*
* @return string|null
*/
public function getLocation()
{
return $this->location;
}
/**
* Gets the route.
*
* @return string|null
*/
public function getRoute()
{
return $this->route;
}
/**
* Gets route parameters.
*
* @return array|null
*/
public function getRouteParameters()
{
return $this->routeParameters;
}
/**
* Gets the response.
*
* @return Response
*/
public function getResponse()
{
if (null === $this->response) {
$this->response = new Response();
if (null !== ($code = $this->getStatusCode())) {
$this->response->setStatusCode($code);
}
}
return $this->response;
}
/**
* Gets the serialization context.
*
* @return Context
*/
public function getContext()
{
if (null === $this->context) {
$this->context = new Context();
}
return $this->context;
}
}
View/ViewHandler.php 0000666 00000034702 13052362416 0010413 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\View;
use FOS\RestBundle\Context\Context;
use FOS\RestBundle\Serializer\Serializer;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Templating\TemplateReferenceInterface;
/**
* View may be used in controllers to build up a response in a format agnostic way
* The View class takes care of encoding your data in json, xml, or renders a
* template for html via the Serializer component.
*
* @author Jordi Boggiano
* @author Lukas K. Smith
*/
class ViewHandler implements ConfigurableViewHandlerInterface
{
/**
* Key format, value a callable that returns a Response instance.
*
* @var array
*/
protected $customHandlers = [];
/**
* The supported formats as keys and if the given formats
* uses templating is denoted by a true value.
*
* @var array
*/
protected $formats;
/**
* HTTP response status code for a failed validation.
*
* @var int
*/
protected $failedValidationCode;
/**
* HTTP response status code when the view data is null.
*
* @var int
*/
protected $emptyContentCode;
/**
* Whether or not to serialize null view data.
*
* @var bool
*/
protected $serializeNull;
/**
* If to force a redirect for the given key format,
* with value being the status code to use.
*
* @var array
*/
protected $forceRedirects;
/**
* @var string
*/
protected $defaultEngine;
/**
* @var array
*/
protected $exclusionStrategyGroups = [];
/**
* @var string
*/
protected $exclusionStrategyVersion;
/**
* @var bool
*/
protected $serializeNullStrategy;
private $urlGenerator;
private $serializer;
private $templating;
private $requestStack;
/**
* Constructor.
*
* @param UrlGeneratorInterface $urlGenerator The URL generator
* @param Serializer $serializer
* @param EngineInterface $templating The configured templating engine
* @param RequestStack $requestStack The request stack
* @param array $formats the supported formats as keys and if the given formats uses templating is denoted by a true value
* @param int $failedValidationCode The HTTP response status code for a failed validation
* @param int $emptyContentCode HTTP response status code when the view data is null
* @param bool $serializeNull Whether or not to serialize null view data
* @param array $forceRedirects If to force a redirect for the given key format, with value being the status code to use
* @param string $defaultEngine default engine (twig, php ..)
*/
public function __construct(
UrlGeneratorInterface $urlGenerator,
Serializer $serializer,
EngineInterface $templating,
RequestStack $requestStack,
array $formats = null,
$failedValidationCode = Response::HTTP_BAD_REQUEST,
$emptyContentCode = Response::HTTP_NO_CONTENT,
$serializeNull = false,
array $forceRedirects = null,
$defaultEngine = 'twig'
) {
$this->urlGenerator = $urlGenerator;
$this->serializer = $serializer;
$this->templating = $templating;
$this->requestStack = $requestStack;
$this->formats = (array) $formats;
$this->failedValidationCode = $failedValidationCode;
$this->emptyContentCode = $emptyContentCode;
$this->serializeNull = $serializeNull;
$this->forceRedirects = (array) $forceRedirects;
$this->defaultEngine = $defaultEngine;
}
/**
* Sets the default serialization groups.
*
* @param array|string $groups
*/
public function setExclusionStrategyGroups($groups)
{
$this->exclusionStrategyGroups = (array) $groups;
}
/**
* Sets the default serialization version.
*
* @param string $version
*/
public function setExclusionStrategyVersion($version)
{
$this->exclusionStrategyVersion = $version;
}
/**
* If nulls should be serialized.
*
* @param bool $isEnabled
*/
public function setSerializeNullStrategy($isEnabled)
{
$this->serializeNullStrategy = $isEnabled;
}
/**
* {@inheritdoc}
*/
public function supports($format)
{
return isset($this->customHandlers[$format]) || isset($this->formats[$format]);
}
/**
* Registers a custom handler.
*
* The handler must have the following signature: handler(ViewHandler $viewHandler, View $view, Request $request, $format)
* It can use the public methods of this class to retrieve the needed data and return a
* Response object ready to be sent.
*
* @param string $format
* @param callable $callable
*
* @throws \InvalidArgumentException
*/
public function registerHandler($format, $callable)
{
if (!is_callable($callable)) {
throw new \InvalidArgumentException('Registered view callback must be callable.');
}
$this->customHandlers[$format] = $callable;
}
/**
* Gets a response HTTP status code from a View instance.
*
* By default it will return 200. However if there is a FormInterface stored for
* the key 'form' in the View's data it will return the failed_validation
* configuration if the form instance has errors.
*
* @param View $view
* @param mixed $content
*
* @return int HTTP status code
*/
protected function getStatusCode(View $view, $content = null)
{
$form = $this->getFormFromView($view);
if ($form && $form->isSubmitted() && !$form->isValid()) {
return $this->failedValidationCode;
}
$statusCode = $view->getStatusCode();
if (null !== $statusCode) {
return $statusCode;
}
return null !== $content ? Response::HTTP_OK : $this->emptyContentCode;
}
/**
* If the given format uses the templating system for rendering.
*
* @param string $format
*
* @return bool
*/
public function isFormatTemplating($format)
{
return !empty($this->formats[$format]);
}
/**
* Gets or creates a JMS\Serializer\SerializationContext and initializes it with
* the view exclusion strategies, groups & versions if a new context is created.
*
* @param View $view
*
* @return Context
*/
protected function getSerializationContext(View $view)
{
$context = $view->getContext();
$groups = $context->getGroups();
if (empty($groups) && $this->exclusionStrategyGroups) {
$context->addGroups($this->exclusionStrategyGroups);
}
if (null === $context->getVersion() && $this->exclusionStrategyVersion) {
$context->setVersion($this->exclusionStrategyVersion);
}
if (null === $context->getSerializeNull() && null !== $this->serializeNullStrategy) {
$context->setSerializeNull($this->serializeNullStrategy);
}
return $context;
}
/**
* Handles a request with the proper handler.
*
* Decides on which handler to use based on the request format.
*
* @param View $view
* @param Request $request
*
* @throws UnsupportedMediaTypeHttpException
*
* @return Response
*/
public function handle(View $view, Request $request = null)
{
if (null === $request) {
$request = $this->requestStack->getCurrentRequest();
}
$format = $view->getFormat() ?: $request->getRequestFormat();
if (!$this->supports($format)) {
$msg = "Format '$format' not supported, handler must be implemented";
throw new UnsupportedMediaTypeHttpException($msg);
}
if (isset($this->customHandlers[$format])) {
return call_user_func($this->customHandlers[$format], $this, $view, $request, $format);
}
return $this->createResponse($view, $request, $format);
}
/**
* Creates the Response from the view.
*
* @param View $view
* @param string $location
* @param string $format
*
* @return Response
*/
public function createRedirectResponse(View $view, $location, $format)
{
$content = null;
if (($view->getStatusCode() === Response::HTTP_CREATED || $view->getStatusCode() === Response::HTTP_ACCEPTED) && $view->getData() !== null) {
$response = $this->initResponse($view, $format);
} else {
$response = $view->getResponse();
if ('html' === $format && isset($this->forceRedirects[$format])) {
$redirect = new RedirectResponse($location);
$content = $redirect->getContent();
$response->setContent($content);
}
}
$code = isset($this->forceRedirects[$format])
? $this->forceRedirects[$format] : $this->getStatusCode($view, $content);
$response->setStatusCode($code);
$response->headers->set('Location', $location);
return $response;
}
/**
* Renders the view data with the given template.
*
* @param View $view
* @param string $format
*
* @return string
*/
public function renderTemplate(View $view, $format)
{
$data = $this->prepareTemplateParameters($view);
$template = $view->getTemplate();
if ($template instanceof TemplateReferenceInterface) {
if (null === $template->get('format')) {
$template->set('format', $format);
}
if (null === $template->get('engine')) {
$engine = $view->getEngine() ?: $this->defaultEngine;
$template->set('engine', $engine);
}
}
return $this->templating->render($template, $data);
}
/**
* Prepares view data for use by templating engine.
*
* @param View $view
*
* @return array
*/
public function prepareTemplateParameters(View $view)
{
$data = $view->getData();
if ($data instanceof FormInterface) {
$data = [$view->getTemplateVar() => $data->getData(), 'form' => $data];
} elseif (empty($data) || !is_array($data) || is_numeric((key($data)))) {
$data = [$view->getTemplateVar() => $data];
}
if (isset($data['form']) && $data['form'] instanceof FormInterface) {
$data['form'] = $data['form']->createView();
}
$templateData = $view->getTemplateData();
if (is_callable($templateData)) {
$templateData = call_user_func($templateData, $this, $view);
}
return array_merge($data, $templateData);
}
/**
* Handles creation of a Response using either redirection or the templating/serializer service.
*
* @param View $view
* @param Request $request
* @param string $format
*
* @return Response
*/
public function createResponse(View $view, Request $request, $format)
{
$route = $view->getRoute();
$location = $route
? $this->urlGenerator->generate($route, (array) $view->getRouteParameters(), UrlGeneratorInterface::ABSOLUTE_URL)
: $view->getLocation();
if ($location) {
return $this->createRedirectResponse($view, $location, $format);
}
$response = $this->initResponse($view, $format);
if (!$response->headers->has('Content-Type')) {
$response->headers->set('Content-Type', $request->getMimeType($format));
}
return $response;
}
/**
* Initializes a response object that represents the view and holds the view's status code.
*
* @param View $view
* @param string $format
*
* @return Response
*/
private function initResponse(View $view, $format)
{
$content = null;
if ($this->isFormatTemplating($format)) {
$content = $this->renderTemplate($view, $format);
} elseif ($this->serializeNull || null !== $view->getData()) {
$data = $this->getDataFromView($view);
if ($data instanceof FormInterface && $data->isSubmitted() && !$data->isValid()) {
$view->getContext()->setAttribute('status_code', $this->failedValidationCode);
}
$context = $this->getSerializationContext($view);
$context->setAttribute('template_data', $view->getTemplateData());
$content = $this->serializer->serialize($data, $format, $context);
}
$response = $view->getResponse();
$response->setStatusCode($this->getStatusCode($view, $content));
if (null !== $content) {
$response->setContent($content);
}
return $response;
}
/**
* Returns the form from the given view if present, false otherwise.
*
* @param View $view
*
* @return bool|FormInterface
*/
protected function getFormFromView(View $view)
{
$data = $view->getData();
if ($data instanceof FormInterface) {
return $data;
}
if (is_array($data) && isset($data['form']) && $data['form'] instanceof FormInterface) {
return $data['form'];
}
return false;
}
/**
* Returns the data from a view.
*
* @param View $view
*
* @return mixed|null
*/
private function getDataFromView(View $view)
{
$form = $this->getFormFromView($view);
if (false === $form) {
return $view->getData();
}
return $form;
}
}
View/ViewHandlerInterface.php 0000666 00000005120 13052362416 0012224 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\RestBundle\View;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* ViewHandlerInterface.
*
* @author Jordi Boggiano
* @author Lukas K. Smith
*/
interface ViewHandlerInterface
{
/**
* Verifies whether the given format is supported by this view.
*
* @param string $format
*
* @return bool
*/
public function supports($format);
/**
* Registers a custom handler.
*
* The handler must have the following signature: handler($viewObject, $request, $response)
* It can use the methods of this class to retrieve the needed data and return a
* Response object ready to be sent.
*
* @param string $format
* @param callable $callable
*/
public function registerHandler($format, $callable);
/**
* If the given format uses the templating system for rendering.
*
* @param string $format
*
* @return bool
*/
public function isFormatTemplating($format);
/**
* Handles a request with the proper handler.
*
* Decides on which handler to use based on the request format
*
* @param View $view
* @param Request $request
*
* @return Response
*/
public function handle(View $view, Request $request = null);
/**
* Create the Response from the view.
*
* @param View $view
* @param string $location
* @param string $format
*
* @return Response
*/
public function createRedirectResponse(View $view, $location, $format);
/**
* Render the view data with the given template.
*
* @param View $view
* @param string $format
*
* @return string
*/
public function renderTemplate(View $view, $format);
/**
* Prepare view data for use by templating engine.
*
* @param View $view
*
* @return array
*/
public function prepareTemplateParameters(View $view);
/**
* Handles creation of a Response using either redirection or the templating/serializer service.
*
* @param View $view
* @param Request $request
* @param string $format
*
* @return Response
*/
public function createResponse(View $view, Request $request, $format);
}
composer.json 0000666 00000006533 13052362416 0007303 0 ustar 00 {
"name": "friendsofsymfony/rest-bundle",
"type": "symfony-bundle",
"description": "This Bundle provides various tools to rapidly develop RESTful API's with Symfony",
"keywords": ["rest"],
"homepage": "http://friendsofsymfony.github.com",
"license": "MIT",
"authors": [
{
"name": "Lukas Kahwe Smith",
"email": "smith@pooteeweet.org"
},
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com"
},
{
"name": "FriendsOfSymfony Community",
"homepage": "https://github.com/friendsofsymfony/FOSRestBundle/contributors"
}
],
"require": {
"php": "^5.5.9|~7.0",
"psr/log": "^1.0",
"symfony/config": "^2.7|^3.0",
"symfony/debug": "^2.7|^3.0",
"symfony/dependency-injection": "^2.7|^3.0",
"symfony/event-dispatcher": "^2.7|^3.0",
"symfony/finder": "^2.7|^3.0",
"symfony/framework-bundle": "^2.7|^3.0",
"symfony/http-foundation": "^2.7|^3.0",
"symfony/http-kernel": "^2.7|^3.0",
"symfony/routing": "^2.7|^3.0",
"symfony/security-core": "^2.7|^3.0",
"symfony/templating": "^2.7|^3.0",
"doctrine/inflector": "^1.0",
"willdurand/negotiation": "^2.0",
"willdurand/jsonp-callback-validator": "^1.0"
},
"require-dev": {
"sensio/framework-extra-bundle": "^3.0.13",
"symfony/phpunit-bridge": "~2.7|^3.0",
"symfony/form": "^2.7|^3.0",
"symfony/validator": "^2.7|^3.0",
"symfony/serializer": "^2.7.11|^3.0.4",
"symfony/yaml": "^2.7|^3.0",
"symfony/security-bundle": "^2.7|^3.0",
"symfony/web-profiler-bundle": "^2.7|^3.0",
"symfony/twig-bundle": "^2.7|^3.0",
"symfony/browser-kit": "^2.7|^3.0",
"symfony/dependency-injection": "^2.7|^3.0",
"symfony/expression-language": "~2.7|^3.0",
"symfony/css-selector": "^2.7|^3.0",
"phpoption/phpoption": "^1.1",
"jms/serializer-bundle": "^1.0",
"psr/http-message": "^1.0"
},
"suggest": {
"sensio/framework-extra-bundle": "Add support for route annotations and the view response listener, requires ^3.0",
"jms/serializer-bundle": "Add support for advanced serialization capabilities, recommended, requires ^1.0",
"symfony/expression-language": "Add support for using the expression language in the routing, requires ^2.7|^3.0",
"symfony/serializer": "Add support for basic serialization capabilities and xml decoding, requires ^2.7|^3.0",
"symfony/validator": "Add support for validation capabilities in the ParamFetcher, requires ^2.7|^3.0"
},
"autoload":{
"psr-4":{
"FOS\\RestBundle\\": ""
}
},
"conflict": {
"sensio/framework-extra-bundle": "<3.0.13",
"jms/serializer": "1.3.0"
},
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
}
}
phpunit.xml.dist 0000666 00000001204 13052362416 0007722 0 ustar 00
./Tests././Routing/Loader/RestRouteLoader20.php./Resources./Tests./vendor