ヤミRoot VoidGate
User / IP
:
216.73.216.81
Host / Server
:
146.88.233.70 / dev.loger.cm
System
:
Linux hybrid1120.fr.ns.planethoster.net 3.10.0-957.21.2.el7.x86_64 #1 SMP Wed Jun 5 14:26:44 UTC 2019 x86_64
Command
|
Upload
|
Create
Mass Deface
|
Jumping
|
Symlink
|
Reverse Shell
Ping
|
Port Scan
|
DNS Lookup
|
Whois
|
Header
|
cURL
:
/
home
/
logercm
/
dev.loger.cm
/
fixtures
/
assert
/
Viewing: friendsofsymfony.tar
rest-bundle/.github/workflows/continuous-integration.yml 0000644 00000005576 15120163066 0017654 0 ustar 00 # from doctrine/instantiator: # https://github.com/doctrine/instantiator/blob/97aa11bb71ad6259a8c5a1161b4de2d6cdcc5501/.github/workflows/continuous-integration.yml name: "CI" on: pull_request: branches: - "*.x" push: branches: - "*.x" jobs: phpunit: name: "PHPUnit" runs-on: "ubuntu-20.04" continue-on-error: ${{ matrix.can-fail }} strategy: matrix: include: - php-version: 7.2 composer-flags: "--prefer-lowest " can-fail: false - php-version: 7.3 composer-flags: "" can-fail: false - php-version: 7.4 composer-flags: "" symfony-require: "4.4.*" can-fail: false - php-version: 7.4 composer-flags: "" symfony-require: "5.3.*" can-fail: false coverage: yes - php-version: 7.4 composer-flags: "" can-fail: false symfony-require: "5.4.*" - php-version: 8.0 composer-flags: "" can-fail: false symfony-require: "6.0.*" - php-version: 8.1 composer-flags: "" can-fail: false symfony-require: "6.1.*" - php-version: 8.1 composer-flags: "" can-fail: false steps: - name: "Checkout" uses: "actions/checkout@v3" - name: "Install PHP with XDebug" uses: "shivammathur/setup-php@v2" if: "${{ matrix.coverage != '' }}" with: php-version: "${{ matrix.php-version }}" coverage: "xdebug" tools: "composer:v2,flex" - name: "Install PHP without coverage" uses: "shivammathur/setup-php@v2" if: "${{ matrix.coverage == '' }}" with: php-version: "${{ matrix.php-version }}" coverage: "none" tools: "composer:v2,flex" - name: "Cache dependencies installed with composer" uses: "actions/cache@v3" with: path: "~/.composer/cache" key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" restore-keys: "php-${{ matrix.php-version }}-composer-locked-" - name: "Install dependencies with composer" env: SYMFONY_REQUIRE: "${{ matrix.symfony-require }}" run: | composer remove friendsofphp/php-cs-fixer --dev --no-update composer update --no-interaction --no-progress ${{ matrix.composer-flags }} - name: "Run PHPUnit" if: "${{ matrix.coverage != '' }}" run: | XDEBUG_MODE=coverage ./phpunit --coverage-clover=coverage.clover wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover - name: "Run PHPUnit" if: "${{ matrix.coverage == '' }}" run: "./phpunit" rest-bundle/Context/Context.php 0000644 00000007022 15120163066 0012552 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Context; use JMS\Serializer\Exclusion\ExclusionStrategyInterface; /** * Stores the serialization or deserialization context (groups, version, ...). * * @author Ener-Getick <egetick@gmail.com> */ final class Context { private $attributes = []; private $version; private $groups; private $isMaxDepthEnabled; private $serializeNull; /** * @var ExclusionStrategyInterface[] */ private $exclusionStrategies = []; public function setAttribute(string $key, $value): self { $this->attributes[$key] = $value; return $this; } public function hasAttribute(string $key): bool { return isset($this->attributes[$key]); } public function getAttribute(string $key) { if (isset($this->attributes[$key])) { return $this->attributes[$key]; } } public function getAttributes(): array { return $this->attributes; } public function setVersion(string $version): self { $this->version = $version; return $this; } public function getVersion(): ?string { return $this->version; } /** * Adds a normalization group. */ public function addGroup(string $group): self { if (null === $this->groups) { $this->groups = []; } if (!in_array($group, $this->groups)) { $this->groups[] = $group; } return $this; } /** * Adds normalization groups. * * @param string[] $groups */ public function addGroups(array $groups): self { foreach ($groups as $group) { $this->addGroup($group); } return $this; } /** * Gets the normalization groups. * * @return string[]|null */ public function getGroups(): ?array { return $this->groups; } /** * Set the normalization groups. * * @param string[]|null $groups */ public function setGroups(array $groups = null): self { $this->groups = $groups; return $this; } public function enableMaxDepth(): self { $this->isMaxDepthEnabled = true; return $this; } public function disableMaxDepth(): self { $this->isMaxDepthEnabled = false; return $this; } public function isMaxDepthEnabled(): ?bool { return $this->isMaxDepthEnabled; } public function setSerializeNull(?bool $serializeNull): self { $this->serializeNull = $serializeNull; return $this; } public function getSerializeNull(): ?bool { return $this->serializeNull; } /** * Gets the array of exclusion strategies. * * Notice: This method only applies to the JMS serializer adapter. * * @return ExclusionStrategyInterface[] */ public function getExclusionStrategies(): array { return $this->exclusionStrategies; } /** * Adds an exclusion strategy. * * Notice: This method only applies to the JMS serializer adapter. */ public function addExclusionStrategy(ExclusionStrategyInterface $exclusionStrategy): void { $this->exclusionStrategies[] = $exclusionStrategy; } } rest-bundle/Controller/Annotations/AbstractParam.php 0000644 00000003376 15120163066 0016656 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; use Symfony\Component\Validator\Constraints; /** * {@inheritdoc} * * @author Jordi Boggiano <j.boggiano@seld.be> * @author Boris Guéry <guery.b@gmail.com> * @author Ener-Getick <egetick@gmail.com> */ 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 = []; /** {@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 = []; 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; } } rest-bundle/Controller/Annotations/AbstractScalarParam.php 0000644 00000005654 15120163066 0020005 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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; use Symfony\Component\Validator\Constraints\NotNull; /** * {@inheritdoc} * * @author Ener-Getick <egetick@gmail.Com> */ 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([ '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([ 'pattern' => '#^(?:'.$this->requirements['rule'].')$#xsu', 'message' => $this->requirements['error_message'], ]); } elseif (is_array($this->requirements)) { foreach ($this->requirements as $index => $requirement) { if ($requirement instanceof Constraint) { $constraints[] = $requirement; } else { throw new \TypeError(sprintf('Expected the requirements to be an array of %s instances but got %s at position %d.', Constraint::class, is_object($requirement) ? get_class($requirement) : gettype($requirement), $index)); } } } if (false === $this->allowBlank) { $notBlank = new NotBlank(); if (property_exists(NotBlank::class, 'allowNull')) { $notBlank->allowNull = $this->nullable; } $constraints[] = $notBlank; } // If the user wants to map the value, apply all constraints to every // value of the map if ($this->map) { $constraints = [ new All(['constraints' => $constraints]), ]; if (false === $this->nullable) { $constraints[] = new NotNull(); } } return $constraints; } } rest-bundle/Controller/Annotations/Copy.php 0000644 00000001207 15120163066 0015033 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * COPY Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") * * @author Maximilian Bosch <maximilian.bosch.27@gmail.com> */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Copy extends Route { public function getMethod() { return 'COPY'; } } rest-bundle/Controller/Annotations/Delete.php 0000644 00000001116 15120163066 0015322 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * DELETE Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Delete extends Route { public function getMethod() { return 'DELETE'; } } rest-bundle/Controller/Annotations/FileParam.php 0000644 00000005051 15120163066 0015762 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints\File; use Symfony\Component\Validator\Constraints\Image; /** * Represents a file that must be present. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") * * @author Ener-Getick <egetick@gmail.com> */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class FileParam extends AbstractParam { /** @var bool */ public $strict = true; /** @var mixed */ public $requirements = null; /** @var bool */ public $image = false; /** @var bool */ public $map = false; /** * @param mixed $requirements * @param mixed $default */ public function __construct( string $name = '', bool $strict = true, $requirements = null, bool $image = false, bool $map = false, ?string $key = null, $default = null, string $description = '', bool $nullable = false ) { $this->strict = $strict; $this->requirements = $requirements; $this->image = $image; $this->map = $map; $this->name = $name; $this->key = $key; $this->default = $default; $this->description = $description; $this->nullable = $nullable; } /** * {@inheritdoc} */ public function getConstraints() { $constraints = parent::getConstraints(); if ($this->requirements instanceof Constraint) { $constraints[] = $this->requirements; } $options = is_array($this->requirements) ? $this->requirements : []; if ($this->image) { $constraints[] = new Image($options); } else { $constraints[] = new File($options); } // If the user wants to map the value if ($this->map) { $constraints = [ new All(['constraints' => $constraints]), ]; } return $constraints; } /** * {@inheritdoc} */ public function getValue(Request $request, $default = null) { return $request->files->get($this->getKey(), $default); } } rest-bundle/Controller/Annotations/Get.php 0000644 00000001105 15120163066 0014635 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * GET Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Get extends Route { public function getMethod() { return 'GET'; } } rest-bundle/Controller/Annotations/Head.php 0000644 00000001110 15120163066 0014753 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * HEAD Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Head extends Route { public function getMethod() { return 'HEAD'; } } rest-bundle/Controller/Annotations/Link.php 0000644 00000001110 15120163066 0015007 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * LINK Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Link extends Route { public function getMethod() { return 'LINK'; } } rest-bundle/Controller/Annotations/Lock.php 0000644 00000001207 15120163066 0015011 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * LOCK Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") * * @author Maximilian Bosch <maximilian.bosch.27@gmail.com> */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Lock extends Route { public function getMethod() { return 'LOCK'; } } rest-bundle/Controller/Annotations/Mkcol.php 0000644 00000001212 15120163066 0015162 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * MKCOL Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") * * @author Maximilian Bosch <maximilian.bosch.27@gmail.com> */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Mkcol extends Route { public function getMethod() { return 'MKCOL'; } } rest-bundle/Controller/Annotations/Move.php 0000644 00000001207 15120163066 0015027 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * MOVE Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") * * @author Maximilian Bosch <maximilian.bosch.27@gmail.com> */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Move extends Route { public function getMethod() { return 'MOVE'; } } rest-bundle/Controller/Annotations/Options.php 0000644 00000001121 15120163066 0015547 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * OPTIONS Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Options extends Route { public function getMethod() { return 'OPTIONS'; } } rest-bundle/Controller/Annotations/ParamInterface.php 0000644 00000002410 15120163066 0016777 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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 <egetick@gmail.com> */ 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 mixed $default value * * @return mixed */ public function getValue(Request $request, $default); } rest-bundle/Controller/Annotations/Patch.php 0000644 00000001113 15120163066 0015154 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * PATCH Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Patch extends Route { public function getMethod() { return 'PATCH'; } } rest-bundle/Controller/Annotations/Post.php 0000644 00000001110 15120163066 0015037 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * POST Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Post extends Route { public function getMethod() { return 'POST'; } } rest-bundle/Controller/Annotations/PropFind.php 0000644 00000001223 15120163066 0015640 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * PROPFIND Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") * * @author Maximilian Bosch <maximilian.bosch.27@gmail.com> */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class PropFind extends Route { public function getMethod() { return 'PROPFIND'; } } rest-bundle/Controller/Annotations/PropPatch.php 0000644 00000001226 15120163066 0016022 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * PROPPATCH Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") * * @author Maximilian Bosch <maximilian.bosch.27@gmail.com> */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class PropPatch extends Route { public function getMethod() { return 'PROPPATCH'; } } rest-bundle/Controller/Annotations/Put.php 0000644 00000001105 15120163066 0014666 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * PUT Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Put extends Route { public function getMethod() { return 'PUT'; } } rest-bundle/Controller/Annotations/QueryParam.php 0000644 00000003205 15120163066 0016207 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; use Symfony\Component\HttpFoundation\Request; /** * Represents a parameter that must be present in GET data. * * @Annotation * @NamedArgumentConstructor * @Target({"CLASS", "METHOD"}) * * @author Alexander <iam.asm89@gmail.com> */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class QueryParam extends AbstractScalarParam { /** * @param mixed $requirements * @param mixed $default */ public function __construct( string $name = '', ?string $key = null, $requirements = null, $default = null, array $incompatibles = [], string $description = '', bool $strict = false, bool $map = false, bool $nullable = false, bool $allowBlank = true ) { $this->name = $name; $this->key = $key; $this->requirements = $requirements; $this->default = $default; $this->incompatibles = $incompatibles; $this->description = $description; $this->strict = $strict; $this->map = $map; $this->nullable = $nullable; $this->allowBlank = $allowBlank; } /** * {@inheritdoc} */ public function getValue(Request $request, $default = null) { return $request->query->all()[$this->getKey()] ?? $default; } } rest-bundle/Controller/Annotations/RequestParam.php 0000644 00000003307 15120163066 0016535 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; use Symfony\Component\HttpFoundation\Request; /** * Represents a parameter that must be present in POST data. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") * * @author Jordi Boggiano <j.boggiano@seld.be> * @author Boris Guéry <guery.b@gmail.com> */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class RequestParam extends AbstractScalarParam { /** @var bool */ public $strict = true; /** * @param mixed $requirements * @param mixed $default */ public function __construct( string $name = '', ?string $key = null, $requirements = null, $default = null, string $description = '', array $incompatibles = [], bool $strict = true, bool $map = false, bool $nullable = false, bool $allowBlank = true ) { $this->name = $name; $this->key = $key; $this->requirements = $requirements; $this->default = $default; $this->description = $description; $this->incompatibles = $incompatibles; $this->strict = $strict; $this->map = $map; $this->nullable = $nullable; $this->allowBlank = $allowBlank; } /** * {@inheritdoc} */ public function getValue(Request $request, $default = null) { return $request->request->all()[$this->getKey()] ?? $default; } } rest-bundle/Controller/Annotations/Route.php 0000644 00000010652 15120163066 0015223 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; use Symfony\Component\Routing\Annotation\Route as BaseRoute; /** * Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target({"CLASS", "METHOD"}) */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Route extends BaseRoute { public function __construct( $data = [], $path = null, string $name = null, array $requirements = [], array $options = [], array $defaults = [], string $host = null, $methods = [], $schemes = [], string $condition = null, int $priority = null, string $locale = null, string $format = null, bool $utf8 = null, bool $stateless = null, string $env = null ) { // BC layer for symfony < 5.2 // Before symfony/routing 5.2 the constructor only had one parameter $method = new \ReflectionMethod(BaseRoute::class, '__construct'); if (1 === $method->getNumberOfParameters()) { if (\is_string($data)) { $path = $data; $data = []; } elseif (!\is_array($data)) { throw new \TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, get_debug_type($data))); } $data['path'] = $path; $data['name'] = $name; $data['requirements'] = $requirements; $data['options'] = $options; $data['defaults'] = $defaults; $data['host'] = $host; $data['methods'] = $methods; $data['schemes'] = $schemes; $data['condition'] = $condition; parent::__construct($data); } else { // BC layer for symfony < 6.0 // The constructor parameter $data has been removed since symfony 6.0 if ('data' === $method->getParameters()[0]->getName()) { parent::__construct( $data, $path, $name, $requirements, $options, $defaults, $host, $methods, $schemes, $condition, $priority, $locale, $format, $utf8, $stateless, $env ); } else { if (\is_string($data)) { $data = ['path' => $data]; } elseif (!\is_array($data)) { throw new \TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, get_debug_type($data))); } elseif (0 !== count($data) && [] === \array_intersect(\array_keys($data), ['path', 'name', 'requirements', 'options', 'defaults', 'host', 'methods', 'schemes', 'condition', 'priority', 'locale', 'format', 'utf8', 'stateless', 'env'])) { $localizedPaths = $data; $data = ['path' => $localizedPaths]; } parent::__construct( $data['path'] ?? $path, $data['name'] ?? $name, $data['requirements'] ?? $requirements, $data['options'] ?? $options, $data['defaults'] ?? $defaults, $data['host'] ?? $host, $data['methods'] ?? $methods, $data['schemes'] ?? $schemes, $data['condition'] ?? $condition, $data['priority'] ?? $priority, $data['locale'] ?? $locale, $data['format'] ?? $format, $data['utf8'] ?? $utf8, $data['stateless'] ?? $stateless, $data['env'] ?? $env ); } } if (!$this->getMethods()) { $this->setMethods((array) $this->getMethod()); } } /** * @return string|null */ public function getMethod() { return; } } rest-bundle/Controller/Annotations/Unlink.php 0000644 00000001116 15120163066 0015360 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * UNLINK Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Unlink extends Route { public function getMethod() { return 'UNLINK'; } } rest-bundle/Controller/Annotations/Unlock.php 0000644 00000001215 15120163066 0015353 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; /** * UNLOCK Route annotation class. * * @Annotation * @NamedArgumentConstructor * @Target("METHOD") * * @author Maximilian Bosch <maximilian.bosch.27@gmail.com> */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] class Unlock extends Route { public function getMethod() { return 'UNLOCK'; } } rest-bundle/Controller/Annotations/View.php 0000644 00000004660 15120163066 0015041 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller\Annotations; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; /** * View annotation class. * * @Annotation * @Target({"METHOD","CLASS"}) */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class View extends Template { /** * @var int|null */ protected $statusCode; /** * @var array */ protected $serializerGroups; /** * @var bool */ protected $serializerEnableMaxDepthChecks; /** * @param array|string $data */ public function __construct( $data = [], array $vars = [], bool $isStreamable = false, array $owner = [], ?int $statusCode = null, array $serializerGroups = [], bool $serializerEnableMaxDepthChecks = false ) { parent::__construct($data, $vars, $isStreamable, $owner); $values = is_array($data) ? $data : []; $this->statusCode = $values['statusCode'] ?? $statusCode; $this->serializerGroups = $values['serializerGroups'] ?? $serializerGroups; $this->serializerEnableMaxDepthChecks = $values['serializerEnableMaxDepthChecks'] ?? $serializerEnableMaxDepthChecks; } /** * @param int $statusCode */ public function setStatusCode($statusCode) { $this->statusCode = $statusCode; } /** * @return int|null */ 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 $serializerEnableMaxDepthChecks */ public function setSerializerEnableMaxDepthChecks($serializerEnableMaxDepthChecks) { $this->serializerEnableMaxDepthChecks = $serializerEnableMaxDepthChecks; } /** * @return bool */ public function getSerializerEnableMaxDepthChecks() { return $this->serializerEnableMaxDepthChecks; } } rest-bundle/Controller/AbstractFOSRestController.php 0000644 00000004077 15120163067 0016652 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Controller; use FOS\RestBundle\View\ViewHandlerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; // Does the AbstractController::getSubscribedServices() method have a return type hint? if (null !== (new \ReflectionMethod(AbstractController::class, 'getSubscribedServices'))->getReturnType()) { /** * Compat class for Symfony 6.0 and newer support. * * @internal */ abstract class BaseAbstractFOSRestController extends AbstractController { /** * {@inheritdoc} */ public static function getSubscribedServices(): array { $subscribedServices = parent::getSubscribedServices(); $subscribedServices['fos_rest.view_handler'] = ViewHandlerInterface::class; return $subscribedServices; } } } else { /** * Compat class for Symfony 5.4 and older support. * * @internal */ abstract class BaseAbstractFOSRestController extends AbstractController { /** * @return array */ public static function getSubscribedServices() { $subscribedServices = parent::getSubscribedServices(); $subscribedServices['fos_rest.view_handler'] = ViewHandlerInterface::class; return $subscribedServices; } } } /** * Controllers using the View functionality of FOSRestBundle. */ abstract class AbstractFOSRestController extends BaseAbstractFOSRestController { use ControllerTrait; /** * @return ViewHandlerInterface */ protected function getViewHandler() { if (!$this->viewhandler instanceof ViewHandlerInterface) { $this->viewhandler = $this->container->get('fos_rest.view_handler'); } return $this->viewhandler; } } rest-bundle/Controller/ControllerTrait.php 0000644 00000004154 15120163067 0014760 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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 <kontakt@beberlei.de> * @author Lukas Kahwe Smith <smith@pooteeweet.org> */ trait ControllerTrait { private $viewhandler; public function setViewHandler(ViewHandlerInterface $viewhandler) { $this->viewhandler = $viewhandler; } 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; } /** * @return View */ protected function view($data = null, ?int $statusCode = null, array $headers = []) { return View::create($data, $statusCode, $headers); } /** * @return View */ protected function redirectView(string $url, int $statusCode = Response::HTTP_FOUND, array $headers = []) { return View::createRedirect($url, $statusCode, $headers); } /** * @return View */ protected function routeRedirectView(string $route, array $parameters = [], int $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". * * @return Response */ protected function handleView(View $view) { return $this->getViewHandler()->handle($view); } } rest-bundle/Decoder/ContainerDecoderProvider.php 0000644 00000002505 15120163067 0015774 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Decoder; use Psr\Container\ContainerInterface; /** * Provides encoders through the Symfony DIC. * * @author Igor Wiedler <igor@wiedler.ch> */ final class ContainerDecoderProvider implements DecoderProviderInterface { private $container; private $decoders; /** * @param array<string,string> $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(string $format): bool { return isset($this->decoders[$format]); } /** * {@inheritdoc} */ public function getDecoder(string $format): DecoderInterface { if (!$this->supports($format)) { throw new \InvalidArgumentException(sprintf("Format '%s' is not supported by ContainerDecoderProvider.", $format)); } return $this->container->get($this->decoders[$format]); } } rest-bundle/Decoder/DecoderInterface.php 0000644 00000001123 15120163067 0014232 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Decoder; /** * Defines the interface of decoders. * * @author Jordi Boggiano <j.boggiano@seld.be> */ interface DecoderInterface { /** * Decodes a string into PHP data. * * @return mixed False in case the content could not be decoded */ public function decode(string $data); } rest-bundle/Decoder/DecoderProviderInterface.php 0000644 00000001317 15120163067 0015752 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Decoder; /** * Defines the interface of decoder providers. * * @author Igor Wiedler <igor@wiedler.ch> */ interface DecoderProviderInterface { /** * Checks if a certain format is supported. * * @return bool */ public function supports(string $format); /** * Provides decoders, possibly lazily. * * @return DecoderInterface */ public function getDecoder(string $format); } rest-bundle/Decoder/JsonDecoder.php 0000644 00000001064 15120163067 0013247 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Decoder; /** * Decodes JSON data. * * @author Jordi Boggiano <j.boggiano@seld.be> */ final class JsonDecoder implements DecoderInterface { /** * {@inheritdoc} */ public function decode(string $data) { return @json_decode($data, true); } } rest-bundle/Decoder/JsonToFormDecoder.php 0000644 00000003261 15120163067 0014377 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Decoder; /** * Decodes JSON data and make it compliant with application/x-www-form-encoded style. * * @author Kévin Dunglas <dunglas@gmail.com> */ final class JsonToFormDecoder implements DecoderInterface { /** * Makes data decoded from JSON application/x-www-form-encoded compliant. */ private function xWwwFormEncodedLike(array &$data): void { 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(string $data) { $decodedData = @json_decode($data, true); if (is_array($decodedData)) { $this->xWwwFormEncodedLike($decodedData); } return $decodedData; } } rest-bundle/Decoder/XmlDecoder.php 0000644 00000001754 15120163067 0013104 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Decoder; use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Exception\UnexpectedValueException; /** * Decodes XML data. * * @author Jordi Boggiano <j.boggiano@seld.be> * @author John Wards <jwards@whiteoctober.co.uk> * @author Fabian Vogler <fabian@equivalence.ch> */ final class XmlDecoder implements DecoderInterface { private $encoder; public function __construct() { $this->encoder = new XmlEncoder(); } /** * {@inheritdoc} */ public function decode(string $data) { try { return $this->encoder->decode($data, 'xml'); } catch (UnexpectedValueException $e) { return; } } } rest-bundle/DependencyInjection/Compiler/ConfigurationCheckPass.php 0000644 00000002332 15120163067 0021571 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener; /** * Checks if the SensioFrameworkExtraBundle views annotations are disabled when using the View Response listener. * * @author Eriksen Costa <eriksencosta@gmail.com> * * @internal */ final class ConfigurationCheckPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { if ($container->has('fos_rest.converter.request_body') && !($container->has('sensio_framework_extra.converter.listener') || $container->has(ParamConverterListener::class))) { throw new \RuntimeException('You need to enable the parameter converter listeners in SensioFrameworkExtraBundle when using the FOSRestBundle RequestBodyParamConverter'); } } } rest-bundle/DependencyInjection/Compiler/FormatListenerRulesPass.php 0000644 00000010250 15120163067 0021773 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpFoundation\ChainRequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher\AttributesRequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher; /** * @author Eduardo Gulias Davis <me@egulias.com> * * @internal */ final class FormatListenerRulesPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { 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' => [], '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): void { $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, ?string $path = null, ?string $host = null, ?array $methods = null, array $attributes = []): Reference { $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 if (!class_exists(ChainRequestMatcher::class)) { $container->setDefinition($id, new Definition(RequestMatcher::class, $arguments)); } else { $matchers = []; if (!is_null($path)) { $matchers[] = new Definition(PathRequestMatcher::class, [$path]); } if (!is_null($host)) { $matchers[] = new Definition(HostRequestMatcher::class, [$host]); } if (!is_null($methods)) { $matchers[] = new Definition(MethodRequestMatcher::class, [$methods]); } if ([] !== $attributes) { $matchers[] = new Definition(AttributesRequestMatcher::class, [$attributes]); } $container ->setDefinition($id, new Definition(ChainRequestMatcher::class)) ->setArguments([$matchers]); } } return new Reference($id); } } rest-bundle/DependencyInjection/Compiler/HandlerRegistryDecorationPass.php 0000644 00000004776 15120163067 0023160 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\DependencyInjection\Compiler; use FOS\RestBundle\Serializer\JMSHandlerRegistry; use FOS\RestBundle\Serializer\JMSHandlerRegistryV2; use JMS\Serializer\Visitor\SerializationVisitorInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; /** * Decorates the handler registry from JMSSerializerBundle. * * The logic is borrowed from the core Symfony DecoratorServicePass, but is implemented here to respect the fact that * custom handlers are registered in JMSSerializerBundle in a compiler pass that is executed after decorated services * have been resolved. * * @author Christian Flothmann <christian.flothmann@sensiolabs.de> * * @internal */ class HandlerRegistryDecorationPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { // skip if JMSSerializerBundle is not installed or if JMSSerializerBundle >= 4.0 if (!$container->has('fos_rest.serializer.jms_handler_registry') || $container->has('jms_serializer.handler_registry.service_locator')) { return; } $jmsHandlerRegistry = $container->findDefinition('fos_rest.serializer.jms_handler_registry'); $public = $jmsHandlerRegistry->isPublic(); $jmsHandlerRegistry->setPublic(false); $container->setDefinition('fos_rest.serializer.jms_handler_registry.inner', $jmsHandlerRegistry); $fosRestHandlerRegistry = $container->register('jms_serializer.handler_registry', interface_exists(SerializationVisitorInterface::class) ? JMSHandlerRegistryV2::class : JMSHandlerRegistry::class) ->setPublic($public) ->addArgument(new Reference('fos_rest.serializer.jms_handler_registry.inner')); // remap existing aliases (they have already been replaced with the actual definition by Symfony's ReplaceAliasByActualDefinitionPass) foreach ($container->getDefinitions() as $id => $definition) { if ('fos_rest.serializer.jms_handler_registry.inner' !== $id && $definition === $jmsHandlerRegistry) { $container->setDefinition($id, $fosRestHandlerRegistry); } } } } rest-bundle/DependencyInjection/Compiler/JMSFormErrorHandlerPass.php 0000644 00000002343 15120163067 0021613 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use FOS\RestBundle\Serializer\Normalizer\FormErrorHandler; use Symfony\Component\DependencyInjection\Reference; /** * Decorates the JMS FormErrorHandler. * * @author Guilhem Niot <guilhem.niot@gmail.com> * @author Christian Flothmann <christian.flothmann@sensiolabs.de> * * @internal */ final class JMSFormErrorHandlerPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { if (!$container->has('jms_serializer.form_error_handler')) { return; } $container->register('fos_rest.serializer.form_error_handler', FormErrorHandler::class) ->setDecoratedService('jms_serializer.form_error_handler') ->addArgument(new Reference('fos_rest.serializer.form_error_handler.inner')); } } rest-bundle/DependencyInjection/Compiler/JMSHandlersPass.php 0000644 00000002703 15120163067 0020140 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Alias; 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 <christian.flothmann@xabbuh.de> * * @internal */ final class JMSHandlersPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { if ($container->has('jms_serializer.handler_registry')) { // perform the aliasing only when jms-serializer-bundle < 4.0 if (!$container->has('jms_serializer.handler_registry.service_locator')) { // the public alias prevents the handler registry definition from being removed $container->setAlias('fos_rest.serializer.jms_handler_registry', new Alias('jms_serializer.handler_registry', true)); } return; } $container->removeDefinition('fos_rest.serializer.handler_registry'); $container->getParameterBag()->remove('jms_serializer.form_error_handler.class'); } } rest-bundle/DependencyInjection/Compiler/SerializerConfigurationPass.php 0000644 00000005403 15120163067 0022667 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\DependencyInjection\Compiler; use FOS\RestBundle\Serializer\Serializer; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Serializer\SerializerInterface; /** * Checks if a serializer is either set or can be auto-configured. * * @author Christian Flothmann <christian.flothmann@xabbuh.de> * @author Florian Voutzinos <florian@voutzinos.com> * * @internal */ final class SerializerConfigurationPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { if ($container->has('fos_rest.serializer')) { $class = $container->getParameterBag()->resolveValue( $container->findDefinition('fos_rest.serializer')->getClass() ); if (!is_subclass_of($class, Serializer::class)) { throw new \InvalidArgumentException(sprintf('"fos_rest.serializer" must implement %s (instance of "%s" given).', Serializer::class, $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('jms_serializer.serializer')) { $container->setAlias('fos_rest.serializer', 'fos_rest.serializer.jms'); return; } // As there is no `jms_serializer.serializer` service, there is a `serializer` service $class = $container->getParameterBag()->resolveValue( $container->findDefinition('serializer')->getClass() ); if (is_subclass_of($class, SerializerInterface::class)) { $container->setAlias('fos_rest.serializer', 'fos_rest.serializer.symfony'); } elseif (is_subclass_of($class, Serializer::class)) { $container->setAlias('fos_rest.serializer', 'serializer'); } else { throw new \InvalidArgumentException(sprintf('The class of the "serializer" service in use is not supported (instance of "%s" given). Please make it implement %s or configure the service "fos_rest.serializer" with a class implementing %s.', $class, Serializer::class, Serializer::class)); } } } rest-bundle/DependencyInjection/Configuration.php 0000644 00000053154 15120163067 0016242 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\DependencyInjection; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Serializer\Encoder\XmlEncoder; /** * This class contains the configuration information for the bundle. * * This information is solely responsible for how the different configuration * sections are normalized, and merged. * * @author Lukas Kahwe Smith <smith@pooteeweet.org> * * @internal */ final class Configuration implements ConfigurationInterface { private $debug; public function __construct(bool $debug) { $this->debug = $debug; } public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('fos_rest'); $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() ->scalarNode('disable_csrf_role')->defaultNull()->end() ->scalarNode('unauthorized_challenge')->defaultNull()->end() ->arrayNode('param_fetcher_listener') ->beforeNormalization() ->ifString() ->then(function ($v) { return ['enabled' => in_array($v, ['force', 'true']), 'force' => 'force' === $v]; }) ->end() ->canBeEnabled() ->children() ->booleanNode('force')->defaultFalse()->end() ->scalarNode('service')->defaultNull()->end() ->end() ->end() ->scalarNode('cache_dir')->cannotBeEmpty()->defaultValue('%kernel.cache_dir%/fos_rest')->end() ->arrayNode('allowed_methods_listener') ->canBeEnabled() ->children() ->scalarNode('service')->defaultNull()->end() ->end() ->end() ->booleanNode('routing_loader') ->defaultValue(false) ->validate() ->ifTrue() ->thenInvalid('only "false" is supported') ->end() ->end() ->arrayNode('body_converter') ->canBeEnabled() ->children() ->scalarNode('validate') ->defaultFalse() ->beforeNormalization() ->ifTrue() ->then(function ($value) { if (!class_exists(OptionsResolver::class)) { throw new InvalidConfigurationException("'body_converter.validate: true' requires OptionsResolver component installation ( composer require symfony/options-resolver )"); } return $value; }) ->end() ->end() ->scalarNode('validation_errors_argument')->defaultValue('validationErrors')->end() ->end() ->end() ->arrayNode('service') ->addDefaultsIfNotSet() ->children() ->scalarNode('serializer')->defaultNull()->end() ->scalarNode('view_handler')->defaultValue('fos_rest.view_handler.default')->end() ->scalarNode('validator')->defaultValue('validator')->end() ->end() ->end() ->arrayNode('serializer') ->addDefaultsIfNotSet() ->children() ->scalarNode('version')->defaultNull()->end() ->arrayNode('groups') ->prototype('scalar')->end() ->end() ->booleanNode('serialize_null')->defaultFalse()->end() ->end() ->end() ->arrayNode('zone') ->cannotBeOverwritten() ->prototype('array') ->fixXmlConfig('ip') ->children() ->scalarNode('path') ->defaultNull() ->info('use the urldecoded format') ->example('^/path to resource/') ->end() ->scalarNode('host')->defaultNull()->end() ->arrayNode('methods') ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() ->prototype('scalar')->end() ->end() ->arrayNode('ips') ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() ->prototype('scalar')->end() ->end() ->end() ->end() ->end() ->end(); $this->addViewSection($rootNode); $this->addExceptionSection($rootNode); $this->addBodyListenerSection($rootNode); $this->addFormatListenerSection($rootNode); $this->addVersioningSection($rootNode); return $treeBuilder; } private function addViewSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() ->arrayNode('view') ->fixXmlConfig('format', 'formats') ->fixXmlConfig('mime_type', 'mime_types') ->addDefaultsIfNotSet() ->children() ->arrayNode('mime_types') ->canBeEnabled() ->beforeNormalization() ->ifArray()->then(function ($v) { if (!empty($v) && empty($v['formats'])) { unset($v['enabled']); $v = ['enabled' => true, 'formats' => $v]; } return $v; }) ->end() ->fixXmlConfig('format', 'formats') ->children() ->scalarNode('service')->defaultNull()->end() ->arrayNode('formats') ->useAttributeAsKey('name') ->prototype('array') ->beforeNormalization() ->ifString() ->then(function ($v) { return [$v]; }) ->end() ->prototype('scalar')->end() ->end() ->end() ->end() ->end() ->arrayNode('formats') ->useAttributeAsKey('name') ->defaultValue(['json' => true, 'xml' => true]) ->prototype('boolean')->end() ->end() ->arrayNode('view_response_listener') ->beforeNormalization() ->ifString() ->then(function ($v) { return ['enabled' => in_array($v, ['force', 'true']), 'force' => 'force' === $v]; }) ->end() ->canBeEnabled() ->children() ->booleanNode('force')->defaultFalse()->end() ->scalarNode('service')->defaultNull()->end() ->end() ->end() ->scalarNode('failed_validation')->defaultValue(Response::HTTP_BAD_REQUEST)->end() ->scalarNode('empty_content')->defaultValue(Response::HTTP_NO_CONTENT)->end() ->booleanNode('serialize_null')->defaultFalse()->end() ->arrayNode('jsonp_handler') ->canBeUnset() ->children() ->scalarNode('callback_param')->defaultValue('callback')->end() ->scalarNode('mime_type')->defaultValue('application/javascript+jsonp')->end() ->end() ->end() ->end() ->end() ->end(); } private function addBodyListenerSection(ArrayNodeDefinition $rootNode): void { $decodersDefaultValue = ['json' => 'fos_rest.decoder.json']; if (class_exists(XmlEncoder::class)) { $decodersDefaultValue['xml'] = 'fos_rest.decoder.xml'; } $rootNode ->children() ->arrayNode('body_listener') ->fixXmlConfig('decoder', 'decoders') ->addDefaultsIfNotSet() ->canBeUnset() ->canBeEnabled() ->children() ->scalarNode('service')->defaultNull()->end() ->scalarNode('default_format')->defaultNull()->end() ->booleanNode('throw_exception_on_unsupported_content_type') ->defaultFalse() ->end() ->arrayNode('decoders') ->useAttributeAsKey('name') ->defaultValue($decodersDefaultValue) ->prototype('scalar')->end() ->end() ->arrayNode('array_normalizer') ->addDefaultsIfNotSet() ->beforeNormalization() ->ifString()->then(function ($v) { return ['service' => $v]; }) ->end() ->children() ->scalarNode('service')->defaultNull()->end() ->booleanNode('forms')->defaultFalse()->end() ->end() ->end() ->end() ->end() ->end(); } private function addFormatListenerSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() ->arrayNode('format_listener') ->fixXmlConfig('rule', 'rules') ->addDefaultsIfNotSet() ->canBeUnset() ->beforeNormalization() ->ifTrue(function ($v) { // check if we got an assoc array in rules return isset($v['rules']) && is_array($v['rules']) && array_keys($v['rules']) !== range(0, count($v['rules']) - 1); }) ->then(function ($v) { $v['rules'] = [$v['rules']]; return $v; }) ->end() ->canBeEnabled() ->children() ->scalarNode('service')->defaultNull()->end() ->arrayNode('rules') ->performNoDeepMerging() ->prototype('array') ->fixXmlConfig('priority', 'priorities') ->fixXmlConfig('attribute', 'attributes') ->children() ->scalarNode('path')->defaultNull()->info('URL path info')->end() ->scalarNode('host')->defaultNull()->info('URL host name')->end() ->variableNode('methods')->defaultNull()->info('Method for URL')->end() ->arrayNode('attributes') ->useAttributeAsKey('name') ->prototype('variable')->end() ->end() ->booleanNode('stop')->defaultFalse()->end() ->booleanNode('prefer_extension')->defaultTrue()->end() ->scalarNode('fallback_format')->defaultValue('html')->end() ->arrayNode('priorities') ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() ->prototype('scalar')->end() ->end() ->end() ->end() ->end() ->end() ->end() ->end(); } private function addVersioningSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() ->arrayNode('versioning') ->canBeEnabled() ->children() ->scalarNode('default_version')->defaultNull()->end() ->arrayNode('resolvers') ->addDefaultsIfNotSet() ->children() ->arrayNode('query') ->canBeDisabled() ->children() ->scalarNode('parameter_name')->defaultValue('version')->end() ->end() ->end() ->arrayNode('custom_header') ->canBeDisabled() ->children() ->scalarNode('header_name')->defaultValue('X-Accept-Version')->end() ->end() ->end() ->arrayNode('media_type') ->canBeDisabled() ->children() ->scalarNode('regex')->defaultValue('/(v|version)=(?P<version>[0-9\.]+)/')->end() ->end() ->end() ->end() ->end() ->arrayNode('guessing_order') ->defaultValue(['query', 'custom_header', 'media_type']) ->validate() ->ifTrue(function ($v) { foreach ($v as $resolver) { if (!in_array($resolver, ['query', 'custom_header', 'media_type'])) { return true; } } }) ->thenInvalid('Versioning guessing order can only contain "query", "custom_header", "media_type".') ->end() ->prototype('scalar')->end() ->end() ->end() ->end() ->end(); } private function addExceptionSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() ->arrayNode('exception') ->fixXmlConfig('code', 'codes') ->fixXmlConfig('message', 'messages') ->addDefaultsIfNotSet() ->canBeEnabled() ->validate() ->always() ->then(function ($v) { if (!$v['enabled']) { return $v; } if ($v['exception_listener']) { @trigger_error('Enabling the "fos_rest.exception.exception_listener" option is deprecated since FOSRestBundle 2.8.', E_USER_DEPRECATED); } if ($v['serialize_exceptions']) { @trigger_error('Enabling the "fos_rest.exception.serialize_exceptions" option is deprecated since FOSRestBundle 2.8.', E_USER_DEPRECATED); } return $v; }) ->end() ->children() ->booleanNode('map_exception_codes') ->defaultFalse() ->info('Enables an event listener that maps exception codes to response status codes based on the map configured with the "fos_rest.exception.codes" option.') ->end() ->booleanNode('exception_listener') ->defaultValue(false) ->validate() ->ifTrue() ->thenInvalid('only "false" is supported') ->end() ->end() ->booleanNode('serialize_exceptions') ->defaultValue(false) ->validate() ->ifTrue() ->thenInvalid('only "false" is supported') ->end() ->end() ->enumNode('flatten_exception_format') ->defaultValue('legacy') ->values(['legacy', 'rfc7807']) ->end() ->booleanNode('serializer_error_renderer')->defaultValue(false)->end() ->arrayNode('codes') ->useAttributeAsKey('name') ->beforeNormalization() ->ifArray() ->then(function (array $items) { foreach ($items as &$item) { if (is_int($item)) { continue; } if (!defined(sprintf('%s::%s', Response::class, $item))) { throw new InvalidConfigurationException(sprintf('Invalid HTTP code in fos_rest.exception.codes, see %s for all valid codes.', Response::class)); } $item = constant(sprintf('%s::%s', Response::class, $item)); } return $items; }) ->end() ->prototype('integer')->end() ->validate() ->ifArray() ->then(function (array $items) { foreach ($items as $class => $code) { $this->testExceptionExists($class); } return $items; }) ->end() ->end() ->arrayNode('messages') ->useAttributeAsKey('name') ->prototype('boolean')->end() ->validate() ->ifArray() ->then(function (array $items) { foreach ($items as $class => $nomatter) { $this->testExceptionExists($class); } return $items; }) ->end() ->end() ->booleanNode('debug') ->defaultValue($this->debug) ->end() ->end() ->end() ->end(); } private function testExceptionExists(string $throwable): void { if (!is_a($throwable, \Throwable::class, true)) { throw new InvalidConfigurationException(sprintf('FOSRestBundle exception mapper: Could not load class "%s" or the class does not extend from "%s". Most probably this is a configuration problem.', $throwable, \Throwable::class)); } } } rest-bundle/DependencyInjection/FOSRestExtension.php 0000644 00000045443 15120163067 0016617 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\DependencyInjection; use FOS\RestBundle\ErrorRenderer\SerializerErrorRenderer; use FOS\RestBundle\EventListener\ResponseStatusCodeListener; use FOS\RestBundle\View\ViewHandler; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\HttpFoundation\ChainRequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher\IpsRequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Validator\Constraint; /** * @internal */ class FOSRestExtension extends Extension { /** * {@inheritdoc} */ public function getConfiguration(array $config, ContainerBuilder $container): Configuration { return new Configuration($container->getParameter('kernel.debug')); } public function load(array $configs, ContainerBuilder $container): void { $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('request.xml'); $loader->load('serializer.xml'); foreach ($config['service'] as $key => $service) { if ('validator' === $service && empty($config['body_converter']['validate'])) { continue; } if (null !== $service) { if ('view_handler' === $key) { $container->setAlias('fos_rest.'.$key, new Alias($service, true)); } else { $container->setAlias('fos_rest.'.$key, $service); } } } $this->loadForm($config, $loader, $container); $this->loadException($config, $loader, $container); $this->loadBodyConverter($config, $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->loadZoneMatcherListener($config, $loader, $container); // Needs RequestBodyParamConverter and View Handler loaded. $this->loadSerializer($config, $container); } private function loadForm(array $config, XmlFileLoader $loader, ContainerBuilder $container): void { 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']); $definition->addTag('form.type_extension', ['extended_type' => FormType::class]); } } private function loadAllowedMethodsListener(array $config, XmlFileLoader $loader, ContainerBuilder $container): void { 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): void { 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', [$config['body_listener']['default_format']]); $container->getDefinition('fos_rest.decoder_provider')->replaceArgument(1, $config['body_listener']['decoders']); $decoderServicesMap = []; foreach ($config['body_listener']['decoders'] as $id) { $decoderServicesMap[$id] = new Reference($id); } $decodersServiceLocator = ServiceLocatorTagPass::register($container, $decoderServicesMap); $container->getDefinition('fos_rest.decoder_provider')->replaceArgument(0, $decodersServiceLocator); $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): void { 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): void { if (!empty($config['versioning']['enabled'])) { $loader->load('versioning.xml'); $versionListener = $container->getDefinition('fos_rest.versioning.listener'); $versionListener->replaceArgument(1, $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): void { if ($config['param_fetcher_listener']['enabled']) { if (!class_exists(Constraint::class)) { throw new \LogicException('Enabling the fos_rest.param_fetcher_listener option when the Symfony Validator component is not installed is not supported. Try installing the symfony/validator package.'); } $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, XmlFileLoader $loader, ContainerBuilder $container): void { if (!$this->isConfigEnabled($container, $config['body_converter'])) { return; } $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']); } } private function loadView(array $config, XmlFileLoader $loader, ContainerBuilder $container): void { if (!empty($config['view']['jsonp_handler'])) { $handler = new ChildDefinition($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; } } if (!is_numeric($config['view']['failed_validation'])) { $config['view']['failed_validation'] = constant(sprintf('%s::%s', Response::class, $config['view']['failed_validation'])); } if (!is_numeric($config['view']['empty_content'])) { $config['view']['empty_content'] = constant(sprintf('%s::%s', Response::class, $config['view']['empty_content'])); } $defaultViewHandler = $container->getDefinition('fos_rest.view_handler.default'); $defaultViewHandler->setFactory([ViewHandler::class, 'create']); $defaultViewHandler->setArguments([ new Reference('router'), new Reference('fos_rest.serializer'), new Reference('request_stack'), $formats, $config['view']['failed_validation'], $config['view']['empty_content'], $config['view']['serialize_null'], ]); } private function loadException(array $config, XmlFileLoader $loader, ContainerBuilder $container): void { if ($config['exception']['enabled']) { $loader->load('exception.xml'); if ($config['exception']['map_exception_codes']) { $container->register('fos_rest.exception.response_status_code_listener', ResponseStatusCodeListener::class) ->setArguments([ new Reference('fos_rest.exception.codes_map'), ]) ->addTag('kernel.event_subscriber'); } $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.serializer.flatten_exception_handler') ->replaceArgument(2, $config['exception']['debug']); $container->getDefinition('fos_rest.serializer.flatten_exception_handler') ->replaceArgument(3, 'rfc7807' === $config['exception']['flatten_exception_format']); $container->getDefinition('fos_rest.serializer.flatten_exception_normalizer') ->replaceArgument(2, $config['exception']['debug']); $container->getDefinition('fos_rest.serializer.flatten_exception_normalizer') ->replaceArgument(3, 'rfc7807' === $config['exception']['flatten_exception_format']); if ($config['exception']['serializer_error_renderer']) { $format = new Definition(); $format->setFactory([SerializerErrorRenderer::class, 'getPreferredFormat']); $format->setArguments([ new Reference('request_stack'), ]); $debug = new Definition(); $debug->setFactory([SerializerErrorRenderer::class, 'isDebug']); $debug->setArguments([ new Reference('request_stack'), '%kernel.debug%', ]); $container->register('fos_rest.error_renderer.serializer', SerializerErrorRenderer::class) ->setArguments([ new Reference('fos_rest.serializer'), $format, new Reference('error_renderer.html', ContainerInterface::NULL_ON_INVALID_REFERENCE), $debug, ]); $container->setAlias('error_renderer', 'fos_rest.error_renderer.serializer'); } } } private function loadSerializer(array $config, ContainerBuilder $container): void { $bodyConverter = $container->hasDefinition('fos_rest.converter.request_body') ? $container->getDefinition('fos_rest.converter.request_body') : null; $viewHandler = $container->getDefinition('fos_rest.view_handler.default'); $options = []; if (!empty($config['serializer']['version'])) { if ($bodyConverter) { $bodyConverter->replaceArgument(2, $config['serializer']['version']); } $options['exclusionStrategyVersion'] = $config['serializer']['version']; } if (!empty($config['serializer']['groups'])) { if ($bodyConverter) { $bodyConverter->replaceArgument(1, $config['serializer']['groups']); } $options['exclusionStrategyGroups'] = $config['serializer']['groups']; } $options['serializeNullStrategy'] = $config['serializer']['serialize_null']; $viewHandler->addArgument($options); } private function loadZoneMatcherListener(array $config, XmlFileLoader $loader, ContainerBuilder $container): void { 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', [$matcher]); } } } private function createZoneRequestMatcher(ContainerBuilder $container, ?string $path = null, ?string $host = null, array $methods = [], array $ips = null): Reference { if ($methods) { $methods = array_map('strtoupper', (array) $methods); } $serialized = serialize([$path, $host, $methods, $ips]); $id = 'fos_rest.zone_request_matcher.'.md5($serialized).sha1($serialized); // only add arguments that are necessary $arguments = [$path, $host, $methods, $ips]; while (count($arguments) > 0 && !end($arguments)) { array_pop($arguments); } if (!class_exists(ChainRequestMatcher::class)) { $container->setDefinition($id, new Definition(RequestMatcher::class, $arguments)); } else { $matchers = []; if (!is_null($path)) { $matchers[] = new Definition(PathRequestMatcher::class, [$path]); } if (!is_null($host)) { $matchers[] = new Definition(HostRequestMatcher::class, [$host]); } if (!is_null($methods)) { $matchers[] = new Definition(MethodRequestMatcher::class, [$methods]); } if (!is_null($ips)) { $matchers[] = new Definition(IpsRequestMatcher::class, [$ips]); } $container ->setDefinition($id, new Definition(ChainRequestMatcher::class)) ->setArguments([$matchers]); } return new Reference($id); } } rest-bundle/ErrorRenderer/SerializerErrorRenderer.php 0000644 00000007521 15120163067 0017101 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\ErrorRenderer; use FOS\RestBundle\Context\Context; use FOS\RestBundle\Serializer\Serializer; use JMS\Serializer\Exception\UnsupportedFormatException; use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Serializer\Exception\NotEncodableValueException; /** * @internal */ final class SerializerErrorRenderer implements ErrorRendererInterface { private $serializer; private $format; private $fallbackErrorRenderer; private $debug; /** * @param string|callable(FlattenException) $format * @param string|bool $debug */ public function __construct(Serializer $serializer, $format, ErrorRendererInterface $fallbackErrorRenderer = null, $debug = false) { if (!is_string($format) && !is_callable($format)) { throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a string or a callable, "%s" given.', __METHOD__, \is_object($format) ? \get_class($format) : \gettype($format))); } if (!is_bool($debug) && !is_callable($debug)) { throw new \TypeError(sprintf('Argument 4 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \is_object($debug) ? \get_class($debug) : \gettype($debug))); } $this->serializer = $serializer; $this->format = $format; $this->fallbackErrorRenderer = $fallbackErrorRenderer; $this->debug = $debug; } public function render(\Throwable $exception): FlattenException { $flattenException = FlattenException::createFromThrowable($exception); try { $format = is_callable($this->format) ? ($this->format)($flattenException) : $this->format; $context = new Context(); $context->setAttribute('exception', $exception); $context->setAttribute('debug', is_callable($this->debug) ? ($this->debug)($exception) : $this->debug); $headers = [ 'Content-Type' => Request::getMimeTypes($format)[0] ?? $format, 'Vary' => 'Accept', ]; return $flattenException->setAsString($this->serializer->serialize($flattenException, $format, $context))->setHeaders($flattenException->getHeaders() + $headers); } catch (NotEncodableValueException|UnsupportedFormatException $e) { return $this->fallbackErrorRenderer->render($exception); } } /** * @see \Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer::getPreferredFormat */ public static function getPreferredFormat(RequestStack $requestStack): \Closure { return static function () use ($requestStack) { if (!$request = $requestStack->getCurrentRequest()) { throw class_exists(NotEncodableValueException::class) ? new NotEncodableValueException() : new UnsupportedFormatException(); } return $request->getPreferredFormat(); }; } /** * @see \Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer::isDebug */ public static function isDebug(RequestStack $requestStack, bool $debug): \Closure { return static function () use ($requestStack, $debug): bool { if (!$request = $requestStack->getCurrentRequest()) { return $debug; } return $debug && $request->attributes->getBoolean('showException', true); }; } } rest-bundle/EventListener/AllowedMethodsListener.php 0000644 00000002447 15120163067 0016721 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\EventListener; use FOS\RestBundle\FOSRestBundle; use FOS\RestBundle\Response\AllowedMethodsLoader\AllowedMethodsLoaderInterface; use Symfony\Component\HttpKernel\Event\ResponseEvent; /** * Listener to append Allow-ed methods for a given route/resource. * * @author Boris Guéry <guery.b@gmail.com> * * @internal */ class AllowedMethodsListener { private $loader; public function __construct(AllowedMethodsLoaderInterface $loader) { $this->loader = $loader; } public function onKernelResponse(ResponseEvent $event): void { $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')])); } } } rest-bundle/EventListener/BodyListener.php 0000644 00000011554 15120163067 0014702 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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\InputBag; use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; /** * This listener handles Request body decoding. * * @author Lukas Kahwe Smith <smith@pooteeweet.org> * * @internal */ class BodyListener { private $decoderProvider; private $throwExceptionOnUnsupportedContentType; private $defaultFormat; private $arrayNormalizer; private $normalizeForms; public function __construct( DecoderProviderInterface $decoderProvider, bool $throwExceptionOnUnsupportedContentType = false, ArrayNormalizerInterface $arrayNormalizer = null, bool $normalizeForms = false ) { $this->decoderProvider = $decoderProvider; $this->throwExceptionOnUnsupportedContentType = $throwExceptionOnUnsupportedContentType; $this->arrayNormalizer = $arrayNormalizer; $this->normalizeForms = $normalizeForms; } public function setDefaultFormat(?string $defaultFormat): void { $this->defaultFormat = $defaultFormat; } public function onKernelRequest(RequestEvent $event): void { $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 (null === $format || !$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)) { if (class_exists(InputBag::class)) { $request->request = new InputBag($data); } else { $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()); } if (class_exists(InputBag::class)) { $request->request = new InputBag($data); } else { $request->request = new ParameterBag($data); } } } private function isNotAnEmptyDeleteRequestWithNoSetContentType(string $method, $content, ?string $contentType): bool { return false === ('DELETE' === $method && empty($content) && empty($contentType)); } private function isDecodeable(Request $request): bool { if (!in_array($request->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE'])) { return false; } return !$this->isFormRequest($request); } private function isFormRequest(Request $request): bool { $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; } } rest-bundle/EventListener/FormatListener.php 0000644 00000004077 15120163067 0015237 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\EventListener; use FOS\RestBundle\FOSRestBundle; use FOS\RestBundle\Util\StopFormatListenerException; use FOS\RestBundle\Negotiation\FormatNegotiator; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; /** * This listener handles Accept header format negotiations. * * @author Lukas Kahwe Smith <smith@pooteeweet.org> * * @internal */ class FormatListener { private $formatNegotiator; public function __construct(FormatNegotiator $formatNegotiator) { $this->formatNegotiator = $formatNegotiator; } public function onKernelRequest(RequestEvent $event): void { $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 (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) { throw new NotAcceptableHttpException('No matching accepted Response format could be determined'); } return; } $request->setRequestFormat($format); } catch (StopFormatListenerException $e) { // nothing to do } } } rest-bundle/EventListener/MimeTypeListener.php 0000644 00000002700 15120163067 0015527 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\EventListener; use FOS\RestBundle\FOSRestBundle; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; /** * This listener handles registering custom mime types. * * @author Lukas Kahwe Smith <smith@pooteeweet.org> * * @internal */ class MimeTypeListener { private $mimeTypes; /** * @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; } public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) { return; } if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) { foreach ($this->mimeTypes as $format => $mimeTypes) { $mimeTypes = array_merge($mimeTypes, Request::getMimeTypes($format)); $request->setFormat($format, $mimeTypes); } } } } rest-bundle/EventListener/ParamFetcherListener.php 0000644 00000006405 15120163067 0016345 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\EventListener; use FOS\RestBundle\FOSRestBundle; use FOS\RestBundle\Request\ParamFetcherInterface; use Symfony\Component\HttpKernel\Event\ControllerEvent; /** * 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 <smith@pooteeweet.org> * * @internal */ class ParamFetcherListener { private $paramFetcher; private $setParamsAsAttributes; public function __construct(ParamFetcherInterface $paramFetcher, bool $setParamsAsAttributes = false) { $this->paramFetcher = $paramFetcher; $this->setParamsAsAttributes = $setParamsAsAttributes; } public function onKernelController(ControllerEvent $event): void { $request = $event->getRequest(); if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) { return; } $controller = $event->getController(); if (is_callable($controller) && (is_object($controller) || is_string($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); } } } private function getAttributeName(callable $controller): string { 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'; } private function isParamFetcherType(\ReflectionParameter $controllerParam): bool { $type = $controllerParam->getType(); foreach ($type instanceof \ReflectionUnionType ? $type->getTypes() : [$type] as $type) { if (null === $type || $type->isBuiltin() || !$type instanceof \ReflectionNamedType) { continue; } $class = new \ReflectionClass($type->getName()); if ($class->implementsInterface(ParamFetcherInterface::class)) { return true; } } return false; } } rest-bundle/EventListener/ResponseStatusCodeListener.php 0000644 00000004441 15120163067 0017577 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\EventListener; use FOS\RestBundle\FOSRestBundle; use FOS\RestBundle\Util\ExceptionValueMap; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Event\KernelEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; /** * @author Christian Flothmann <christian.flothmann@sensiolabs.de> */ class ResponseStatusCodeListener implements EventSubscriberInterface { private $exceptionValueMap; private $responseStatusCode; public function __construct(ExceptionValueMap $exceptionValueMap) { $this->exceptionValueMap = $exceptionValueMap; } public static function getSubscribedEvents(): array { return [ KernelEvents::EXCEPTION => 'getResponseStatusCodeFromThrowable', KernelEvents::RESPONSE => 'setResponseStatusCode', ]; } public function getResponseStatusCodeFromThrowable(ExceptionEvent $event): void { if (!$this->isMainRequest($event)) { return; } $request = $event->getRequest(); if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) { return; } $statusCode = $this->exceptionValueMap->resolveFromClassName(get_class($event->getThrowable())); if (is_int($statusCode)) { $this->responseStatusCode = $statusCode; } } public function setResponseStatusCode(ResponseEvent $event): void { if (!$this->isMainRequest($event)) { return; } if (null !== $this->responseStatusCode) { $event->getResponse()->setStatusCode($this->responseStatusCode); $this->responseStatusCode = null; } } private function isMainRequest(KernelEvent $event): bool { if (method_exists($event, 'isMainRequest')) { return $event->isMainRequest(); } return $event->isMasterRequest(); } } rest-bundle/EventListener/VersionExclusionListener.php 0000644 00000002314 15120163067 0017316 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\EventListener; use FOS\RestBundle\FOSRestBundle; use FOS\RestBundle\View\ConfigurableViewHandlerInterface; use FOS\RestBundle\View\ViewHandlerInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; /** * @internal */ class VersionExclusionListener { private $viewHandler; public function __construct(ViewHandlerInterface $viewHandler) { $this->viewHandler = $viewHandler; } public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) { return; } if (!$request->attributes->has('version')) { return; } $version = $request->attributes->get('version'); if ($this->viewHandler instanceof ConfigurableViewHandlerInterface) { $this->viewHandler->setExclusionStrategyVersion($version); } } } rest-bundle/EventListener/VersionListener.php 0000644 00000002473 15120163067 0015432 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\EventListener; use FOS\RestBundle\FOSRestBundle; use FOS\RestBundle\Version\VersionResolverInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; /** * @internal */ class VersionListener { private $versionResolver; private $defaultVersion; public function __construct(VersionResolverInterface $versionResolver, ?string $defaultVersion = null) { $this->versionResolver = $versionResolver; $this->defaultVersion = $defaultVersion; } public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) { return; } $version = $this->versionResolver->resolve($request); if (null === $version && null !== $this->defaultVersion) { $version = $this->defaultVersion; } // Return if nothing to do if (null === $version) { return; } $request->attributes->set('version', $version); } } rest-bundle/EventListener/ViewResponseListener.php 0000644 00000006075 15120163067 0016440 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\EventListener; use FOS\RestBundle\Controller\Annotations\View as ViewAnnotation; use FOS\RestBundle\FOSRestBundle; use FOS\RestBundle\View\View; use FOS\RestBundle\View\ViewHandlerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ViewEvent; use Symfony\Component\HttpKernel\KernelEvents; /** * The ViewResponseListener class handles the View core event as well as the "@extra:Template" annotation. * * @author Lukas Kahwe Smith <smith@pooteeweet.org> * * @internal */ class ViewResponseListener implements EventSubscriberInterface { private $viewHandler; private $forceView; public function __construct(ViewHandlerInterface $viewHandler, bool $forceView) { $this->viewHandler = $viewHandler; $this->forceView = $forceView; } public function onKernelView(ViewEvent $event): void { $request = $event->getRequest(); if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) { return; } $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 (null !== $configuration->getStatusCode() && (null === $view->getStatusCode() || Response::HTTP_OK === $view->getStatusCode())) { $view->setStatusCode($configuration->getStatusCode()); } $context = $view->getContext(); if ($configuration->getSerializerGroups()) { if (null === $context->getGroups()) { $context->setGroups($configuration->getSerializerGroups()); } else { $context->setGroups(array_merge($context->getGroups(), $configuration->getSerializerGroups())); } } if (true === $configuration->getSerializerEnableMaxDepthChecks()) { $context->enableMaxDepth(); } elseif (false === $configuration->getSerializerEnableMaxDepthChecks()) { $context->disableMaxDepth(); } } if (null === $view->getFormat()) { $view->setFormat($request->getRequestFormat()); } $response = $this->viewHandler->handle($view, $request); $event->setResponse($response); } public static function getSubscribedEvents(): array { // Must be executed before SensioFrameworkExtraBundle's listener return [ KernelEvents::VIEW => ['onKernelView', 30], ]; } } rest-bundle/EventListener/ZoneMatcherListener.php 0000644 00000002461 15120163067 0016221 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\EventListener; use FOS\RestBundle\FOSRestBundle; use Symfony\Component\HttpFoundation\RequestMatcherInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; /** * Matches FOSRest's zones. * * @author Florian Voutzinos <florian@voutzinos.com> * * @internal */ class ZoneMatcherListener { private $requestMatchers = []; public function addRequestMatcher(RequestMatcherInterface $requestMatcher) { $this->requestMatchers[] = $requestMatcher; } /** * Adds an optional "_fos_rest_zone" request attribute to be checked for existence by other listeners. */ public function onKernelRequest(RequestEvent $event): void { $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); } } rest-bundle/Exception/InvalidParameterException.php 0000644 00000003601 15120163067 0016546 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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"; } $invalidValue = $violation->getInvalidValue(); $message .= sprintf( 'Parameter "%s" of value "%s" violated a constraint "%s"', $parameter->getName(), is_scalar($invalidValue) ? $invalidValue : var_export($invalidValue, true), $violation->getMessage() ); } return self::withViolationsAndMessage($parameter, $violations, $message); } /** * Do not use this method. It will be removed in 2.0. * * @internal */ public static function withViolationsAndMessage(ParamInterface $parameter, ConstraintViolationListInterface $violations, string $message): self { $exception = new self($message); $exception->parameter = $parameter; $exception->violations = $violations; return $exception; } } rest-bundle/Form/Extension/DisableCSRFExtension.php 0000644 00000003074 15120163067 0016303 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Form\Extension; 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 * * @internal */ class DisableCSRFExtension extends AbstractTypeExtension { private $tokenStorage; private $role; private $authorizationChecker; public function __construct(TokenStorageInterface $tokenStorage, string $role, AuthorizationCheckerInterface $authorizationChecker) { $this->tokenStorage = $tokenStorage; $this->role = $role; $this->authorizationChecker = $authorizationChecker; } public function configureOptions(OptionsResolver $resolver): void { if (!$this->tokenStorage->getToken()) { return; } if (!$this->authorizationChecker->isGranted($this->role)) { return; } $resolver->setDefaults([ 'csrf_protection' => false, ]); } public static function getExtendedTypes(): iterable { return [FormType::class]; } } rest-bundle/Form/Transformer/EntityToIdObjectTransformer.php 0000644 00000004431 15120163067 0020317 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Form\Transformer; use Doctrine\Common\Persistence\ObjectManager as LegacyObjectManager; use Doctrine\Persistence\ObjectManager; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; /** * Class EntityToIdObjectTransformer. * * @author Marc Juchli <mail@marcjuch.li> * * @internal */ class EntityToIdObjectTransformer implements DataTransformerInterface { private $om; private $entityName; public function __construct($om, string $entityName) { if (!$om instanceof ObjectManager && !$om instanceof LegacyObjectManager) { throw new \TypeError(sprintf('The first argument of %s() must be an instance of "%s" ("%s" given).', __METHOD__, ObjectManager::class, is_object($om) ? get_class($om) : gettype($om))); } $this->entityName = $entityName; $this->om = $om; } /** * Do nothing. * * @param object|null $object */ public function transform($object): string { 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 */ public function reverseTransform($idObject): ?object { if (!is_array($idObject)) { return null; } $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; } } rest-bundle/Negotiation/FormatNegotiator.php 0000644 00000011272 15120163067 0015251 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Negotiation; use FOS\RestBundle\Util\StopFormatListenerException; use Negotiation\Accept; use Negotiation\AcceptHeader; use Negotiation\Negotiator as BaseNegotiator; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcherInterface; use Symfony\Component\HttpFoundation\RequestStack; /** * @author Guilhem Niot <guilhem@gniot.fr> */ final class FormatNegotiator extends BaseNegotiator { private $map = []; private $requestStack; private $mimeTypes; public function __construct(RequestStack $requestStack, array $mimeTypes = []) { $this->requestStack = $requestStack; $this->mimeTypes = $mimeTypes; } public function add(RequestMatcherInterface $requestMatcher, array $options = []): void { $this->map[] = [$requestMatcher, $options]; } public function getBest($header, array $priorities = [], $strict = false): ?AcceptHeader { $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 ($extensionHeader) { $header = $extensionHeader.'; q='.$options['prefer_extension'].($header ? ','.$header : ''); } } } if ($header) { $mimeTypes = $this->normalizePriorities( $request, empty($priorities) ? $options['priorities'] : $priorities ); $mimeType = parent::getBest($header, $mimeTypes); if (null !== $mimeType) { 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 null; } // stop looking at rules since we have a fallback defined return new Accept($request->getMimeType($options['fallback_format'])); } } return null; } private function sanitize(array $values): array { return array_map(function ($value) { return preg_replace('/\s+/', '', strtolower($value)); }, $values); } /** * @param string[] $priorities * * @return string[] formatted priorities */ private function normalizePriorities(Request $request, array $priorities): array { $priorities = $this->sanitize($priorities); $mimeTypes = []; foreach ($priorities as $priority) { if (strpos($priority, '/')) { $mimeTypes[] = $priority; continue; } $mimeTypes = array_merge($mimeTypes, Request::getMimeTypes($priority)); if (isset($this->mimeTypes[$priority])) { foreach ($this->mimeTypes[$priority] as $mimeType) { $mimeTypes[] = $mimeType; } } } return $mimeTypes; } private function getRequest(): Request { $request = $this->requestStack->getCurrentRequest(); if (null === $request) { throw new \RuntimeException('There is no current request.'); } return $request; } } rest-bundle/Normalizer/Exception/NormalizationException.php 0000644 00000000743 15120163067 0020273 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Normalizer\Exception; /** * Exception thrown when the normalization failed. * * @author Florian Voutzinos <florian@voutzinos.com> */ class NormalizationException extends \RuntimeException { } rest-bundle/Normalizer/ArrayNormalizerInterface.php 0000644 00000001151 15120163067 0016564 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Normalizer; /** * Normalizes arrays. * * @author Florian Voutzinos <florian@voutzinos.com> */ interface ArrayNormalizerInterface { /** * Normalizes the array. * * @throws Exception\NormalizationException * * @return array The normalized array */ public function normalize(array $data); } rest-bundle/Normalizer/CamelKeysNormalizer.php 0000644 00000003472 15120163067 0015552 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Normalizer; use FOS\RestBundle\Normalizer\Exception\NormalizationException; /** * Normalizes the array by changing its keys from underscore to camel case. * * @author Florian Voutzinos <florian@voutzinos.com> * * @internal */ class CamelKeysNormalizer implements ArrayNormalizerInterface { /** * {@inheritdoc} */ public function normalize(array $data) { $this->normalizeArray($data); return $data; } private function normalizeArray(array &$data) { $normalizedData = []; 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. * * @return string */ protected function normalizeString(string $string) { if (false === strpos($string, '_')) { return $string; } return preg_replace_callback('/_([a-zA-Z0-9])/', function ($matches) { return strtoupper($matches[1]); }, $string); } } rest-bundle/Normalizer/CamelKeysNormalizerWithLeadingUnderscore.php 0000644 00000002201 15120163067 0021711 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Normalizer; /** * Normalizes the array by changing its keys from underscore to camel case, while * leaving leading underscores unchanged. * * @author Lukas Kahwe Smith <smith@pooteeweet.org> * * @internal */ class CamelKeysNormalizerWithLeadingUnderscore extends CamelKeysNormalizer { /** * Normalizes a string while leaving leading underscores unchanged. * * @return string */ protected function normalizeString(string $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); } } rest-bundle/Request/ParamFetcher.php 0000644 00000014324 15120163067 0013477 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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 <iam.asm89@gmail.com> * @author Lukas Kahwe Smith <smith@pooteeweet.org> * @author Jordi Boggiano <j.boggiano@seld.be> * @author Boris Guéry <guery.b@gmail.com> */ final class ParamFetcher implements ParamFetcherInterface { use ResolverTrait; private $container; private $parameterBag; private $requestStack; private $validator; public function __construct(ContainerInterface $container, ParamReaderInterface $paramReader, RequestStack $requestStack, ValidatorInterface $validator) { $this->container = $container; $this->requestStack = $requestStack; $this->validator = $validator; $this->parameterBag = new ParameterBag($paramReader); } /** * {@inheritdoc} */ public function setController(callable $controller): void { $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. */ public function addParam(ParamInterface $param): void { $this->parameterBag->addParam($this->getRequest(), $param); } /** * @return ParamInterface[] */ public function getParams(): array { return $this->parameterBag->getParams($this->getRequest()); } /** * {@inheritdoc} */ public function get(string $name, ?bool $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); } private function cleanParamWithRequirements(ParamInterface $param, $paramValue, bool $strict, $default) { $this->checkNotIncompatibleParams($param); if (null !== $default && $default === $paramValue) { return $paramValue; } $constraints = $param->getConstraints(); $this->resolveConstraints($constraints); if (empty($constraints)) { return $paramValue; } try { $errors = $this->validator->validate($paramValue, $constraints); } catch (ValidatorException $e) { $violation = new ConstraintViolation( $e->getMessage(), $e->getMessage(), [], $paramValue, '', null, null, $e->getCode() ); $errors = new ConstraintViolationList([$violation]); } if (0 < count($errors)) { if ($strict) { throw InvalidParameterException::withViolations($param, $errors); } return null === $default ? '' : $default; } return $paramValue; } /** * {@inheritdoc} */ public function all(?bool $strict = null): array { $configuredParams = $this->getParams(); $params = []; foreach ($configuredParams as $name => $param) { $params[$name] = $this->get($name, $strict); } return $params; } private function checkNotIncompatibleParams(ParamInterface $param): void { if (null === $param->getValue($this->getRequest(), null)) { return; } $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 (null !== $incompatibleParam->getValue($this->getRequest(), 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): void { foreach ($constraints as $constraint) { if ($constraint instanceof ResolvableConstraintInterface) { $constraint->resolve($this->container); } } } private function getRequest(): Request { $request = $this->requestStack->getCurrentRequest(); if (null === $request) { throw new \RuntimeException('There is no current request.'); } return $request; } } rest-bundle/Request/ParamFetcherInterface.php 0000644 00000001703 15120163067 0015315 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Request; /** * Helper interface to validate query parameters from the active request. * * @author Alexander <iam.asm89@gmail.com> * @author Lukas Kahwe Smith <smith@pooteeweet.org> */ interface ParamFetcherInterface { public function setController(callable $controller); /** * @param bool|null $strict Whether a requirement mismatch should cause an exception * * @return mixed Value of the parameter */ public function get(string $name, ?bool $strict = null); /** * @param bool $strict Whether a requirement mismatch should cause an exception * * @return array */ public function all(bool $strict = false); } rest-bundle/Request/ParamReader.php 0000644 00000006416 15120163067 0013324 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Request; use Doctrine\Common\Annotations\Reader; use FOS\RestBundle\Controller\Annotations\ParamInterface; /** * Class loading "@ParamInterface" annotations from methods. * * @author Alexander <iam.asm89@gmail.com> * @author Lukas Kahwe Smith <smith@pooteeweet.org> * @author Boris Guéry <guery.b@gmail.com> */ final class ParamReader implements ParamReaderInterface { /** * @var Reader|null */ private $annotationReader; public function __construct(?Reader $annotationReader = null) { $this->annotationReader = $annotationReader; } /** * {@inheritdoc} */ public function read(\ReflectionClass $reflection, string $method): array { 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): array { $annotations = []; if (\PHP_VERSION_ID >= 80000) { $annotations = $this->getParamsFromAttributes($method); } if (null !== $this->annotationReader) { $annotations = array_merge( $annotations, $this->annotationReader->getMethodAnnotations($method) ?? [] ); } return $this->getParamsFromAnnotationArray($annotations); } /** * {@inheritdoc} */ public function getParamsFromClass(\ReflectionClass $class): array { $annotations = []; if (\PHP_VERSION_ID >= 80000) { $annotations = $this->getParamsFromAttributes($class); } if (null !== $this->annotationReader) { $annotations = array_merge( $annotations, $this->annotationReader->getClassAnnotations($class) ?? [] ); } return $this->getParamsFromAnnotationArray($annotations); } /** * @return ParamInterface[] */ private function getParamsFromAnnotationArray(array $annotations): array { $params = []; foreach ($annotations as $annotation) { if ($annotation instanceof ParamInterface) { $params[$annotation->getName()] = $annotation; } } return $params; } /** * @param \ReflectionClass|\ReflectionMethod $reflection * * @return ParamInterface[] */ private function getParamsFromAttributes($reflection): array { $params = []; foreach ($reflection->getAttributes(ParamInterface::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { $param = $attribute->newInstance(); $params[$param->getName()] = $param; } return $params; } } rest-bundle/Request/ParamReaderInterface.php 0000644 00000002114 15120163067 0015134 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Request; use FOS\RestBundle\Controller\Annotations\ParamInterface; /** * Interface for loading query parameters for a method. * * @author Alexander <iam.asm89@gmail.com> * @author Lukas Kahwe Smith <smith@pooteeweet.org> */ interface ParamReaderInterface { /** * @return ParamInterface[] Param annotation objects of the method. Indexed by parameter name */ public function read(\ReflectionClass $reflection, string $method); /** * @return ParamInterface[] Param annotation objects of the method. Indexed by parameter name */ public function getParamsFromMethod(\ReflectionMethod $method); /** * @return ParamInterface[] Param annotation objects of the class. Indexed by parameter name */ public function getParamsFromClass(\ReflectionClass $class); } rest-bundle/Request/ParameterBag.php 0000644 00000004657 15120163067 0013500 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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 = []; public function __construct(ParamReaderInterface $paramReader) { $this->paramReader = $paramReader; } public function getParams(Request $request): array { $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 (null === $this->params[$requestId]['params']) { return $this->initParams($requestId); } return $this->params[$requestId]['params']; } public function addParam(Request $request, ParamInterface $param): void { $requestId = spl_object_hash($request); $this->getParams($request); $this->params[$requestId]['params'][$param->getName()] = $param; } public function setController(Request $request, callable $controller): void { $requestId = spl_object_hash($request); $this->params[$requestId] = [ 'controller' => $controller, 'params' => null, ]; } /** * @return ParamInterface[] */ private function initParams(string $requestId): array { $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)'); } $class = class_exists(ClassUtils::class) ? ClassUtils::getClass($controller[0]) : get_class($controller[0]); return $this->params[$requestId]['params'] = $this->paramReader->read( new \ReflectionClass($class), $controller[1] ); } } rest-bundle/Request/RequestBodyParamConverter.php 0000644 00000013001 15120163067 0016244 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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 <tyler@tylerstroud.com> */ final class RequestBodyParamConverter implements ParamConverterInterface { private $serializer; private $context = []; private $validator; private $validationErrorsArgument; /** * @param string[]|null $groups */ public function __construct( Serializer $serializer, ?array $groups = null, ?string $version = null, ValidatorInterface $validator = null, ?string $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): bool { $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); $format = method_exists(Request::class, 'getContentTypeFormat') ? $request->getContentTypeFormat() : $request->getContentType(); if (null === $format) { return $this->throwException(new UnsupportedMediaTypeHttpException(), $configuration); } try { $object = $this->serializer->deserialize( $request->getContent(), $configuration->getClass(), $format, $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 && (!isset($options['validate']) || $options['validate'])) { $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): bool { return null !== $configuration->getClass() && 'fos_rest.request_body' === $configuration->getConverter(); } private function configureContext(Context $context, array $options): void { foreach ($options as $key => $value) { if ('groups' === $key) { $context->addGroups($options['groups']); } elseif ('version' === $key) { $context->setVersion($options['version']); } elseif ('enableMaxDepth' === $key) { $context->enableMaxDepth($options['enableMaxDepth']); } elseif ('serializeNull' === $key) { $context->setSerializeNull($options['serializeNull']); } else { $context->setAttribute($key, $value); } } } private function throwException(\Exception $exception, ParamConverter $configuration): bool { if ($configuration->isOptional()) { return false; } throw $exception; } private function getValidatorOptions(array $options): array { $resolver = new OptionsResolver(); $resolver->setDefaults([ 'groups' => null, 'traverse' => false, 'deep' => false, ]); return $resolver->resolve(isset($options['validator']) ? $options['validator'] : []); } } rest-bundle/Resources/config/allowed_methods_listener.xml 0000644 00000001743 15120163067 0020016 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.allowed_methods_listener" class="FOS\RestBundle\EventListener\AllowedMethodsListener"> <tag name="kernel.event_listener" event="kernel.response" method="onKernelResponse" /> <argument type="service" id="fos_rest.allowed_methods_loader" /> </service> <service id="fos_rest.allowed_methods_loader" class="FOS\RestBundle\Response\AllowedMethodsLoader\AllowedMethodsRouterLoader" public="false"> <tag name="kernel.cache_warmer" /> <argument type="service" id="router" /> <argument /> <!-- cache dir --> <argument>%kernel.debug%</argument> </service> </services> </container> rest-bundle/Resources/config/body_listener.xml 0000644 00000002704 15120163067 0015577 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.normalizer.camel_keys" class="FOS\RestBundle\Normalizer\CamelKeysNormalizer" /> <service id="fos_rest.normalizer.camel_keys_with_leading_underscore" class="FOS\RestBundle\Normalizer\CamelKeysNormalizerWithLeadingUnderscore" /> <service id="fos_rest.decoder.json" class="FOS\RestBundle\Decoder\JsonDecoder" /> <service id="fos_rest.decoder.jsontoform" class="FOS\RestBundle\Decoder\JsonToFormDecoder" /> <service id="fos_rest.decoder.xml" class="FOS\RestBundle\Decoder\XmlDecoder" /> <service id="fos_rest.decoder_provider" class="FOS\RestBundle\Decoder\ContainerDecoderProvider"> <argument type="service" id="service_container" /> <argument type="collection" /> <!-- decoders --> </service> <service id="fos_rest.body_listener" class="FOS\RestBundle\EventListener\BodyListener"> <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="10" /> <argument type="service" id="fos_rest.decoder_provider" /> <argument /> <!-- exception on unsupported content type --> </service> </services> </container> rest-bundle/Resources/config/exception.xml 0000644 00000003475 15120163067 0014741 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.exception.codes_map" class="FOS\RestBundle\Util\ExceptionValueMap" public="false"> <argument type="collection"/> <!-- exception codes --> </service> <service id="fos_rest.exception.messages_map" class="FOS\RestBundle\Util\ExceptionValueMap" public="false"> <argument type="collection"/> <!-- exception messages --> </service> <service id="fos_rest.serializer.flatten_exception_handler" class="FOS\RestBundle\Serializer\Normalizer\FlattenExceptionHandler" public="false"> <argument type="service" id="fos_rest.exception.codes_map" /> <!-- exception messages --> <argument type="service" id="fos_rest.exception.messages_map" /> <!-- exception messages --> <argument /><!-- show exception message --> <argument /><!-- render according to RFC 7807 --> <tag name="jms_serializer.subscribing_handler" /> </service> <service id="fos_rest.serializer.flatten_exception_normalizer" class="FOS\RestBundle\Serializer\Normalizer\FlattenExceptionNormalizer" public="false"> <argument type="service" id="fos_rest.exception.codes_map" /> <!-- exception messages --> <argument type="service" id="fos_rest.exception.messages_map" /> <!-- exception messages --> <argument /><!-- show exception message --> <argument /><!-- render according to RFC 7807 --> <tag name="serializer.normalizer" priority="-10" /> </service> </services> </container> rest-bundle/Resources/config/format_listener.xml 0000644 00000001425 15120163067 0016131 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.format_listener" class="FOS\RestBundle\EventListener\FormatListener"> <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="34" /> <argument type="service" id="fos_rest.format_negotiator" /> </service> <service id="fos_rest.format_negotiator" class="FOS\RestBundle\Negotiation\FormatNegotiator"> <argument type="service" id="request_stack" /> </service> </services> </container> rest-bundle/Resources/config/forms.xml 0000644 00000001200 15120163067 0014051 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.form.extension.csrf_disable" class="FOS\RestBundle\Form\Extension\DisableCSRFExtension"> <argument type="service" id="security.token_storage" /> <argument /> <!-- disable CSRF role --> <argument type="service" id="security.authorization_checker" /> </service> </services> </container> rest-bundle/Resources/config/mime_type_listener.xml 0000644 00000001113 15120163067 0016623 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.mime_type_listener" class="FOS\RestBundle\EventListener\MimeTypeListener"> <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="200" /> <argument type="collection" /> </service> </services> </container> rest-bundle/Resources/config/param_fetcher_listener.xml 0000644 00000001235 15120163067 0017440 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.param_fetcher_listener" class="FOS\RestBundle\EventListener\ParamFetcherListener"> <tag name="kernel.event_listener" event="kernel.controller" method="onKernelController" priority="5"/> <argument type="service" id="fos_rest.request.param_fetcher"/> <argument>false</argument> </service> </services> </container> rest-bundle/Resources/config/request.xml 0000644 00000002006 15120163067 0014420 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.request.param_fetcher" class="FOS\RestBundle\Request\ParamFetcher"> <argument type="service" id="service_container" /> <argument type="service" id="fos_rest.request.param_fetcher.reader"/> <argument type="service" id="request_stack"/> <argument type="service" id="validator" on-invalid="null"/> </service> <service id="FOS\RestBundle\Request\ParamFetcherInterface" alias="fos_rest.request.param_fetcher" /> <service id="fos_rest.request.param_fetcher.reader" class="FOS\RestBundle\Request\ParamReader" public="false"> <argument type="service" id="annotation_reader" on-invalid="null"/> </service> </services> </container> rest-bundle/Resources/config/request_body_param_converter.xml 0000644 00000001665 15120163067 0020716 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.converter.request_body" class="FOS\RestBundle\Request\RequestBodyParamConverter" public="false"> <argument type="service" id="fos_rest.serializer"/> <argument type="collection" /> <!-- serializer exclusion strategy groups --> <argument /> <!-- serializer exclusion strategy version --> <argument type="service" id="fos_rest.validator" on-invalid="ignore" /> <argument>null</argument> <!-- request body validation errors argument --> <tag name="request.param_converter" converter="fos_rest.request_body" priority="-50"/> </service> </services> </container> rest-bundle/Resources/config/serializer.xml 0000644 00000002260 15120163067 0015103 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.serializer.jms" class="FOS\RestBundle\Serializer\JMSSerializerAdapter" public="false"> <argument type="service" id="jms_serializer.serializer" /> <argument type="service" id="jms_serializer.serialization_context_factory" /> <argument type="service" id="jms_serializer.deserialization_context_factory" /> </service> <service id="fos_rest.serializer.symfony" class="FOS\RestBundle\Serializer\SymfonySerializerAdapter" public="false"> <argument type="service" id="serializer" /> </service> <!-- Normalizes FormInterface when using the symfony serializer --> <service id="fos_rest.serializer.form_error_normalizer" class="FOS\RestBundle\Serializer\Normalizer\FormErrorNormalizer" public="false"> <tag name="serializer.normalizer" priority="-10" /> </service> </services> </container> rest-bundle/Resources/config/versioning.xml 0000644 00000003500 15120163067 0015113 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.versioning.listener" class="FOS\RestBundle\EventListener\VersionListener"> <argument type="service" id="fos_rest.versioning.chain_resolver" /> <argument /> <!-- default media type version --> <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="33" /> </service> <service id="fos_rest.versioning.exclusion_listener" class="FOS\RestBundle\EventListener\VersionExclusionListener"> <argument type="service" id="fos_rest.view_handler" /> <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="31" /> </service> <service id="fos_rest.versioning.chain_resolver" class="FOS\RestBundle\Version\ChainVersionResolver" public="false"> <argument type="collection" /> <!-- resolvers --> </service> <service id="fos_rest.versioning.header_resolver" class="FOS\RestBundle\Version\Resolver\HeaderVersionResolver" public="false"> <argument /> <!-- request header name --> </service> <service id="fos_rest.versioning.media_type_resolver" class="FOS\RestBundle\Version\Resolver\MediaTypeVersionResolver" public="false"> <argument /> <!-- regex --> </service> <service id="fos_rest.versioning.query_parameter_resolver" class="FOS\RestBundle\Version\Resolver\QueryParameterVersionResolver" public="false"> <argument /> <!-- request parameter name --> </service> </services> </container> rest-bundle/Resources/config/view.xml 0000644 00000001405 15120163067 0013704 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.view_handler.default" class="FOS\RestBundle\View\ViewHandler" public="false"> <tag name="kernel.reset" method="reset" /> </service> <service id="FOS\RestBundle\View\ViewHandlerInterface" alias="fos_rest.view_handler" /> <service id="fos_rest.view_handler.jsonp" class="FOS\RestBundle\View\JsonpHandler" public="false"> <argument /> <!-- JSONP callback parameter --> </service> </services> </container> rest-bundle/Resources/config/view_response_listener.xml 0000644 00000001133 15120163067 0017525 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.view_response_listener" class="FOS\RestBundle\EventListener\ViewResponseListener"> <tag name="kernel.event_subscriber" /> <argument type="service" id="fos_rest.view_handler" /> <argument /> <!-- force view --> </service> </services> </container> rest-bundle/Resources/config/zone_matcher_listener.xml 0000644 00000001065 15120163067 0017317 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="fos_rest.zone_matcher_listener" class="FOS\RestBundle\EventListener\ZoneMatcherListener"> <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="248" /> </service> </services> </container> rest-bundle/Resources/doc/examples/RssHandler.php 0000644 00000006532 15120163067 0016112 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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:<br/><pre>%s</pre>', $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); } } rest-bundle/Resources/doc/examples/chaplin_backbone.md 0000644 00000002004 15120163067 0017106 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/) rest-bundle/Resources/doc/1-setting_up_the_bundle.rst 0000644 00000003540 15120163067 0016754 0 ustar 00 Step 1: Setting up the bundle ============================= A) Download the Bundle ---------------------- Assuming you already have `set up a Symfony project`_, you can add the FOSRestBundle to it. 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. .. caution:: If you are not using Flex, you also have to enable the bundle by adding the following line in the ``app/AppKernel.php``:: // app/AppKernel.php class AppKernel extends Kernel { public function registerBundles() { $bundles = [ // ... new FOS\RestBundle\FOSRestBundle(), ]; // ... } } B) 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! .. _`set up a Symfony project`: https://symfony.com/download .. _`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 rest-bundle/Resources/doc/2-the-view-layer.rst 0000644 00000025177 15120163067 0015257 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 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 <?php namespace AppBundle\Controller; use FOS\RestBundle\Controller\AbstractFOSRestController; class UsersController extends AbstractFOSRestController { public function getUsersAction() { $data = ...; // get data, in this case list of users. $view = $this->view($data, 200); 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:: 2.0 The ``ControllerTrait`` trait was added in 2.0. 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. 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. 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. 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. * ``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 in use. * ``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``. 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, or - 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. - An invalid form will be wrapped in 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 `Symfony form component`_ to handle submission of view data. In fact, the `Symfony form builder`_ 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}} 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. 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`` setting determines which formats are supported by the serializer. Any format listed in ``formats`` will use the serializer for rendering. 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 when HTML is enabled, but this needs to be enabled for other formats as 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. 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 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 fos_rest: view: jsonp_handler: callback_param: mycallback Finally the filter can also be disabled by setting it to false. .. code-block:: yaml 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 .. _`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 .. _`Symfony form component`: https://symfony.com/doc/current/components/form.html .. _`Symfony form builder`: https://symfony.com/doc/current/forms.html#building-forms rest-bundle/Resources/doc/3-listener-support.rst 0000644 00000017034 15120163067 0015746 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 to 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 will now 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 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 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 <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 <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 <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 <format_listener>`. Versioning ---------- This listener attempts 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: application/json; version=1.0`` * a custom header ``X-Accept-Version: v1`` For details see :doc:`Versioning <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 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 <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 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 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 rest-bundle/Resources/doc/4-exception-controller-support.rst 0000644 00000004104 15120163067 0020273 0 ustar 00 Step 4: ExceptionController support =================================== 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 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 <?php namespace AppBundle\Controller; use Symfony\Component\HttpKernel\Exception\HttpException; class UsersController extends Controller { public function postUserCommentsAction($slug) { if (!$this->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 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/ rest-bundle/Resources/doc/annotations-reference.rst 0000644 00000010613 15120163067 0016534 0 ustar 00 Full default annotations ======================== Param fetcher ------------- QueryParam ~~~~~~~~~~ .. tabs:: .. tab:: Annotations .. code-block:: php use FOS\RestBundle\Controller\Annotations\QueryParam; /** * @QueryParam( * name="", * key=null, * requirements="", * incompatibles={}, * default=null, * description="", * strict=false, * map=false, * nullable=false * ) */ .. tab:: Attributes .. code-block:: php use FOS\RestBundle\Controller\Annotations\QueryParam; #[QueryParam( name: '', key: null, requirements: '', incompatibles: [], default: null, description: '', strict: false, map: false, nullable: false )] RequestParam ~~~~~~~~~~~~ .. tabs:: .. tab:: Annotations .. code-block:: php use FOS\RestBundle\Controller\Annotations\RequestParam; /** * @RequestParam( * name="", * key=null, * requirements="", * default=null, * description="", * strict=true, * map=false, * nullable=false * ) */ .. tab:: Attributes .. code-block:: php use FOS\RestBundle\Controller\Annotations\RequestParam; #[RequestParam( name: '', key: null, requirements: '', default: null, description: '', strict: true, map: false, nullable: false )] FileParam ~~~~~~~~~ .. tabs:: .. tab:: Annotations .. code-block:: php use FOS\RestBundle\Controller\Annotations\FileParam; /** * @FileParam( * name="", * key=null, * requirements={}, * default=null, * description="", * strict=true, * nullable=false, * image=false * ) */ .. tab:: Attributes .. code-block:: php use FOS\RestBundle\Controller\Annotations\FileParam; #[FileParam( name: '', key: null, requirements: [], default: null, description: '', strict: true, nullable: false, image: false )] View ---- .. tabs:: .. tab:: Annotations .. code-block:: php use FOS\RestBundle\Controller\Annotations\View; /** * @View( * statusCode=null, * serializerGroups={}, * serializerEnableMaxDepthChecks=false * ) */ .. tab:: Attributes .. code-block:: php use FOS\RestBundle\Controller\Annotations\View; #[View( statusCode: null, serializerGroups: [], serializerEnableMaxDepthChecks: false )] Routing ------- Route ~~~~~ RestBundle extends the `@Route Symfony annotation`_. The following are shortcuts to define routes limited to a specific HTTP method: ``@Delete``, ``@Get``, ``@Head``, ``@Link``, ``@Patch``, ``@Post``, ``@Put``, ``@Unlink``, ``@Lock``, ``@Unlock``, ``@PropFind``, ``@PropPatch``, ``@Move``, ``@Mkcol``, ``@Copy``. All of them have the same options as ``@Route``. Example: .. tabs:: .. tab:: Annotations .. code-block:: php // src/Controller/BlogController.php namespace App\Controller; use FOS\RestBundle\Controller\AbstractFOSRestController; use FOS\RestBundle\Controller\Annotations as Rest; class BlogController extends AbstractFOSRestController { /** * @Rest\Get("/blog", name="blog_list") */ public function list() { // ... } } .. tab:: Attributes .. code-block:: php // src/Controller/BlogController.php namespace App\Controller; use FOS\RestBundle\Controller\AbstractFOSRestController; use FOS\RestBundle\Controller\Annotations as Rest; class BlogController extends AbstractFOSRestController { #[Rest\Get('/blog', name: 'blog_list')] public function list() { // ... } } .. _`@Route Symfony annotation`: https://symfony.com/doc/current/routing.html rest-bundle/Resources/doc/body_listener.rst 0000644 00000007433 15120163067 0015113 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 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 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 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 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 rest-bundle/Resources/doc/conf.py 0000644 00000000672 15120163067 0013014 0 ustar 00 # -*- coding: utf-8 -*- import sys, os from sphinx.highlighting import lexers from pygments.lexers.web import PhpLexer project = 'FriendsOfSymfony / FOSRestBundle' author = 'FriendsOfSymfony community' extensions = [ 'sphinx_tabs.tabs' ] # This will be used when using the shorthand notation highlight_language = 'php' # enable highlighting for PHP code not between <?php ... ?> by default lexers['php'] = PhpLexer(startinline=True) rest-bundle/Resources/doc/empty-content-status-code.rst 0000644 00000001471 15120163067 0017304 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. rest-bundle/Resources/doc/format_listener.rst 0000644 00000011562 15120163067 0015444 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 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 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 rest-bundle/Resources/doc/index.rst 0000644 00000003510 15120163067 0013350 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 annotations-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 listed sections 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>` Config reference ---------------- - Run ``bin/console config:dump-reference fos_rest`` for a reference of the available configuration options - :doc:`Annotations reference <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 `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. .. _`FOSCommentBundle`: https://github.com/FriendsOfSymfony/FOSCommentBundle .. _`Symfony2 Rest Edition`: https://github.com/gimler/symfony-rest-edition rest-bundle/Resources/doc/param_fetcher_listener.rst 0000644 00000022162 15120163067 0016752 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 fos_rest: param_fetcher_listener: true .. code-block:: php <?php namespace AppBundle\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; use Acme\FooBundle\Validation\Constraints\MyComplexConstraint; class FooController extends Controller { /** * @QueryParam(name="page", requirements="\d+", default="1", description="Page of the overview.") * Will look for a page query parameter, ie. ?page=XX * If not passed it will be automatically be set to the default of "1" * If passed but doesn't match the requirement "\d+" it will be also be set to the default of "1" * Note that if the value matches the default then no validation is run. * So make sure the default value really matches your expectations. * * @QueryParam(name="count", requirements="\d+", strict=true, nullable=true, description="Item count limit") * In some case you also want to have a strict requirements but accept a null value, this is possible * thanks to the nullable option. * If ?count= parameter is set, the requirements will be checked strictly, if not, the null value will be used. * If you set the strict parameter without a nullable option, this will result in an error if the parameter is * missing from the query. * * @QueryParam(name="sort", requirements="(asc|desc)", allowBlank=false, default="asc", description="Sort direction") * Will check if a blank value, e.g an empty string is passed and if so, it will set to the default of asc. * * @RequestParam(name="nullableSample", nullable=true, description="An example for a nullable entry") * @RequestParam(name="firstname", requirements="[a-z]+", description="Firstname.") * Will look for a firstname request parameters, ie. firstname=foo in POST data. * If not passed it will error out when read out of the ParamFetcher since RequestParam defaults to strict=true * If passed but doesn't match the requirement "[a-z]+" it will also error out (400 Bad Request) * Note that if the value matches the default then no validation is run. * So make sure the default value really matches your expectations. * * @RequestParam(name="simpleEmail", requirements=@Constraints\Email) * @RequestParam(name="complexEmail", requirements={@Constraints\Email, @Constraints\NotEqualTo("joe@example.org")}) * You can use one or multiple Symfony Validator constraints for more complex requirements checking. The first * example above checks for a correctly formatted e-mail address. The second example also ensures that it does not * matches some default example value. * * @RequestParam(name="search", requirements="[a-z]+", description="search") * @RequestParam(name="byauthor", requirements="[a-z]+", description="by author", incompatibles={"search"}) * Imagine you have an api for a blog with to get Articles with two ways of filtering * 1 by filtering the text * 2 by filtering the author * and you don't have yet implemented the possibility to filter by both at the same time. * In order to prevent clients from doing a request with both (which will produce not the expected * result and is likely to be considered as a bug) you can precise the parameters can't be present * at the same time by doing * * @QueryParam(map=true, name="ids", requirements="\d+", default="1", description="List of ids") * If you want to map the value as an array (apply the requirements to each element): ie. ?ids[]=1&ids[]=2&ids[]=1337. * (works with QueryParam and RequestParam) * * It will validate each entries of ids with your requirement, by this way, if an entry is invalid, * this one will be replaced by default value. * * ie: ?ids[]=1337&ids[]=notinteger will return array(1337, 1); * If ids is not defined, array(1) will be given * * Array must have a single depth if you use a regex. It's difficult to validate with * preg_match each deeps of array, if you want to deal with that, you can use a constraint: * * @QueryParam(map=true, name="filters", requirements=@MyComplexConstraint, description="List of complex filters") * In this example, the ParamFetcher will validate each value of the array with the constraint, returning the * default value if you are in safe mode or throw a BadRequestHttpResponse containing the constraint violation * messages in the message. * * @FileParam(name="fooFile", incompatibles={"firstName"}) * In this example, if firstName and fooFile are defined at the time, * ParamFetcher::get("fooFile") will throw an error. * Otherwise, if the file is valid, it will return a Symfony\Component\HttpFoundation\File\UploadedFile. * See http://api.symfony.com/2.7/Symfony/Component/HttpFoundation/File/UploadedFile.html * * @FileParam(name="myJsonFile", requirements={"mimeTypes"="application/json", "maxSize"="2k"}, strict=true) * The ParamFetcher will throw an error if the file passed is not a json file or is bigger than 2,000 bytes. * See all file possible requirements: http://symfony.com/doc/current/reference/constraints/File.html * * @FileParam(name="avatar", requirements={"mimeTypes"="image/jpeg", "minWidth"="200"}, image=true) * The ParamFetcher will throw an error if the file passed is not an image and is not larger than 200px. * See all image possible requirements: http://symfony.com/doc/current/reference/constraints/Image.html * * @FileParam(name="identityCard", image=true, default="noPicture") * If "identityCard" is not an image, paramFetcher will return "noPicture" * * @param ParamFetcher $paramFetcher */ public function getArticlesAction(ParamFetcher $paramFetcher) { // ParamFetcher params can be dynamically added during runtime instead of only compile time annotations. $dynamicRequestParam = new RequestParam(); $dynamicRequestParam->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 fos_rest: param_fetcher_listener: force .. code-block:: php <?php namespace AppBundle\Controller; class FooController extends Controller { /** * @QueryParam(name="page", requirements="\d+", default="1", description="Page of the overview.") * * @param string $page */ public function getArticlesAction($page) { $articles = array('bim', 'bam', 'bingo'); return array('articles' => $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 <?php namespace AppBundle\Controller; class FooController extends Controller { /** * Use the "locale" parameter as the default value * @QueryParam(name="language", default="%locale%") * * The "baz" container parameter is used here as requirements * Can be used for complex or auto-generated regex * @QueryParam(name="foo", requirements="%baz%") * * The percent sign must be escaped * @QueryParam(name="val", default="75 %%") */ public function getArticlesAction(ParamFetcher $paramFetcher) { ... } rest-bundle/Resources/doc/request_body_converter_listener.rst 0000644 00000005407 15120163067 0020751 0 ustar 00 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: .. code-block:: yaml sensio_framework_extra: request: { converters: true } To enable the Request body converter, add the following configuration: .. code-block:: yaml fos_rest: body_converter: enabled: true Now, in the following example, the request body will be deserialized into a new instance of ``Post`` and injected into the ``$post`` variable: .. code-block:: php use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; // ... /** * @ParamConverter("post", converter="fos_rest.request_body") */ public function putPostAction(Post $post) { // ... } You can configure the context used by the serializer during deserialization via the ``deserializationContext`` option: .. code-block:: php /** * @ParamConverter("post", converter="fos_rest.request_body", options={"deserializationContext"={"groups"={"group1", "group2"}, "version"="1.0"}}) */ public function putPostAction(Post $post) { // ... } Validation ~~~~~~~~~~ If you would like to validate the deserialized object, you can do so by enabling validation: .. code-block:: yaml fos_rest: body_converter: enabled: true validate: true validation_errors_argument: validationErrors # This is the default value The validation errors will be set on the ``validationErrors`` controller argument: .. code-block:: php /** * @ParamConverter("post", converter="fos_rest.request_body") */ public function putPostAction(Post $post, ConstraintViolationListInterface $validationErrors) { if (count($validationErrors) > 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 rest-bundle/Resources/doc/requirements.txt 0000644 00000000035 15120163067 0014772 0 ustar 00 sphinx_rtd_theme sphinx-tabs rest-bundle/Resources/doc/versioning.rst 0000644 00000011272 15120163067 0014430 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 # config/routes.yaml 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: .. code-block:: yaml 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 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<version>[0-9\.]+)/' You can also choose the guessing order: .. code-block:: yaml 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 <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 Symfony config: .. code-block:: yaml imports: - { resource: fos_rest_mime_types.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'" rest-bundle/Resources/doc/view_response_listener.rst 0000644 00000006310 15120163067 0017037 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 <?php namespace AppBundle\Controller; use FOS\RestBundle\View\View; class UsersController { public function getUsersAction() { $view = View::create(); // ... $view->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 <?php namespace AppBundle\Controller; use FOS\RestBundle\Controller\Annotations\View; class UsersController { /** * @View() */ public function getUsersAction() { // ... return $data; } } The status code of the view can also be configured: .. code-block:: php <?php /** * @View(statusCode=204) */ public function deleteUserAction() { // ... } The groups for the serializer can be configured as follows: .. code-block:: php <?php /** * @View(serializerGroups={"group1", "group2"}) */ public function getUsersAction() { // ... } Enabling the MaxDepth exclusion strategy support for the serializer can be configured as follows: .. code-block:: php <?php /** * @View(serializerEnableMaxDepthChecks=true) */ public function getUsersAction() { // ... } You can also define your serializer options dynamically: .. code-block:: php <?php use FOS\RestBundle\Controller\Annotations\View as ViewAnnotation; use FOS\RestBundle\View\View; use FOS\RestBundle\Context\Context; /** * @ViewAnnotation() */ public function getUsersAction() { $view = View::create(); $context = new Context(); $context->setVersion('1.0'); $context->addGroup('user'); $view->setContext($context); // ... $view ->setData($data) ; return $view; } .. _`SensioFrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html rest-bundle/Response/AllowedMethodsLoader/AllowedMethodsLoaderInterface.php 0000644 00000001304 15120163067 0023223 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Response\AllowedMethodsLoader; /** * AllowedMethodsLoaderInterface. * * @author Boris Guéry <guery.b@gmail.com> */ interface AllowedMethodsLoaderInterface { /** * Returns the allowed http methods. * * array( * 'some_route' => array('GET', 'POST'), * 'another_route' => array('DELETE', 'PUT'), * ); * * @return array */ public function getAllowedMethods(); } rest-bundle/Response/AllowedMethodsLoader/AllowedMethodsRouterLoader.php 0000644 00000005050 15120163067 0022605 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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 <guery.b@gmail.com> */ final class AllowedMethodsRouterLoader implements AllowedMethodsLoaderInterface, CacheWarmerInterface { private $router; private $cache; public function __construct(RouterInterface $router, string $cacheDir, bool $isDebug) { $this->router = $router; $this->cache = new ConfigCache(sprintf('%s/allowed_methods.cache.php', $cacheDir), $isDebug); } /** * {@inheritdoc} */ public function getAllowedMethods(): array { if (!$this->cache->isFresh()) { $this->warmUp(null); } return require $this->cache->getPath(); } /** * {@inheritdoc} */ public function isOptional(): bool { return true; } /** * {@inheritdoc} */ public function warmUp($cacheDir): void { $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('<?php return %s;', var_export($allowedMethods, true)), $routeCollection->getResources() ); } } rest-bundle/Serializer/Normalizer/FlattenExceptionHandler.php 0000644 00000010765 15120163067 0020520 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Serializer\Normalizer; use FOS\RestBundle\Util\ExceptionValueMap; use JMS\Serializer\Context; use JMS\Serializer\GraphNavigatorInterface; use JMS\Serializer\Handler\SubscribingHandlerInterface; use JMS\Serializer\JsonSerializationVisitor; use JMS\Serializer\XmlSerializationVisitor; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\Response; /** * @author Christian Flothmann <christian.flothmann@sensiolabs.de> * * @internal */ class FlattenExceptionHandler implements SubscribingHandlerInterface { private $statusCodeMap; private $messagesMap; private $debug; private $rfc7807; public function __construct(ExceptionValueMap $statusCodeMap, ExceptionValueMap $messagesMap, bool $debug, bool $rfc7807) { $this->statusCodeMap = $statusCodeMap; $this->messagesMap = $messagesMap; $this->debug = $debug; $this->rfc7807 = $rfc7807; } public static function getSubscribingMethods(): array { return [ [ 'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION, 'format' => 'json', 'type' => FlattenException::class, 'method' => 'serializeToJson', ], [ 'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION, 'format' => 'xml', 'type' => FlattenException::class, 'method' => 'serializeToXml', ], ]; } public function serializeToJson(JsonSerializationVisitor $visitor, FlattenException $exception, array $type, Context $context) { if ($this->rfc7807) { $exception->setHeaders($exception->getHeaders() + ['Content-Type' => 'application/problem+json']); } return $visitor->visitArray($this->convertToArray($exception, $context), $type, $context); } public function serializeToXml(XmlSerializationVisitor $visitor, FlattenException $exception, array $type, Context $context) { if ($this->rfc7807) { $exception->setHeaders($exception->getHeaders() + ['Content-Type' => 'application/problem+xml']); } $rootName = $this->rfc7807 ? 'response' : 'result'; $data = $this->convertToArray($exception, $context); if (method_exists($visitor, 'setDefaultRootName')) { $visitor->setDefaultRootName($rootName); } $document = $visitor->getDocument(true); if (!$visitor->getCurrentNode()) { $visitor->createRoot(null, $rootName); } foreach ($data as $key => $value) { $entryNode = $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(); } } private function convertToArray(FlattenException $exception, Context $context): array { if ($context->hasAttribute('status_code')) { $statusCode = $context->getAttribute('status_code'); } elseif (null === $statusCode = $this->statusCodeMap->resolveFromClassName($exception->getClass())) { $statusCode = $exception->getStatusCode(); } $showMessage = $this->messagesMap->resolveFromClassName($exception->getClass()); if ($showMessage || $this->debug) { $message = $exception->getMessage(); } else { $message = Response::$statusTexts[$statusCode] ?? 'error'; } if ($this->rfc7807) { return [ 'type' => $context->hasAttribute('type') ? $context->getAttribute('type') : 'https://tools.ietf.org/html/rfc2616#section-10', 'title' => $context->hasAttribute('title') ? $context->getAttribute('title') : 'An error occurred', 'status' => $statusCode, 'detail' => $message, ]; } else { return [ 'code' => $statusCode, 'message' => $message, ]; } } } rest-bundle/Serializer/Normalizer/FlattenExceptionNormalizer.php 0000644 00000006043 15120163067 0021257 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Serializer\Normalizer; use FOS\RestBundle\Serializer\Serializer; use FOS\RestBundle\Util\ExceptionValueMap; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** * @author Christian Flothmann <christian.flothmann@sensiolabs.de> * * @internal */ final class FlattenExceptionNormalizer implements NormalizerInterface { private $statusCodeMap; private $messagesMap; private $debug; private $rfc7807; public function __construct(ExceptionValueMap $statusCodeMap, ExceptionValueMap $messagesMap, bool $debug, bool $rfc7807) { $this->statusCodeMap = $statusCodeMap; $this->messagesMap = $messagesMap; $this->debug = $debug; $this->rfc7807 = $rfc7807; } public function normalize($exception, $format = null, array $context = []): array { if (isset($context['status_code'])) { $statusCode = $context['status_code']; } elseif (null === $statusCode = $this->statusCodeMap->resolveFromClassName($exception->getClass())) { $statusCode = $exception->getStatusCode(); } $showMessage = $this->messagesMap->resolveFromClassName($exception->getClass()); if ($showMessage || $this->debug) { $message = $exception->getMessage(); } else { $message = Response::$statusTexts[$statusCode] ?? 'error'; } if ($this->rfc7807) { if ('json' === $format) { $exception->setHeaders($exception->getHeaders() + ['Content-Type' => 'application/problem+json']); } elseif ('xml' === $format) { $exception->setHeaders($exception->getHeaders() + ['Content-Type' => 'application/problem+xml']); } return [ 'type' => $context['type'] ?? 'https://tools.ietf.org/html/rfc2616#section-10', 'title' => $context['title'] ?? 'An error occurred', 'status' => $statusCode, 'detail' => $message, ]; } else { return [ 'code' => $statusCode, 'message' => $message, ]; } } public function supportsNormalization($data, $format = null, array $context = []): bool { if (!($data instanceof FlattenException)) { return false; } // we are in fos rest context if (!empty($context[Serializer::FOS_BUNDLE_SERIALIZATION_CONTEXT])) { return true; } // we are in messenger context if (!empty($context['messenger_serialization'])) { // Serializer::MESSENGER_SERIALIZATION_CONTEXT return false; } return true; } } rest-bundle/Serializer/Normalizer/FormErrorHandler.php 0000644 00000010316 15120163067 0017151 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Serializer\Normalizer; use JMS\Serializer\Context; use JMS\Serializer\Handler\FormErrorHandler as JMSFormErrorHandler; use JMS\Serializer\Handler\SubscribingHandlerInterface; use JMS\Serializer\JsonSerializationVisitor; use JMS\Serializer\Visitor\SerializationVisitorInterface; use JMS\Serializer\XmlSerializationVisitor; use Symfony\Component\Form\Form; use JMS\Serializer\YamlSerializationVisitor; /** * Extend the JMS FormErrorHandler to include more information when using the ViewHandler. * * @internal */ class FormErrorHandler implements SubscribingHandlerInterface { private $formErrorHandler; public function __construct(JMSFormErrorHandler $formErrorHandler) { $this->formErrorHandler = $formErrorHandler; } public static function getSubscribingMethods(): array { return JMSFormErrorHandler::getSubscribingMethods(); } public function serializeFormToXml(XmlSerializationVisitor $visitor, Form $form, array $type, Context $context = null) { if ($context) { if ($context->hasAttribute('status_code')) { $document = $visitor->getDocument(true); if (!$visitor->getCurrentNode()) { $visitor->createRoot(); } $codeNode = $document->createElement('code'); $visitor->getCurrentNode()->appendChild($codeNode); $codeNode->appendChild($context->getNavigator()->accept($context->getAttribute('status_code'), null, $context)); $messageNode = $document->createElement('message'); $visitor->getCurrentNode()->appendChild($messageNode); $messageNode->appendChild($context->getNavigator()->accept('Validation Failed', null, $context)); $errorsNode = $document->createElement('errors'); $visitor->getCurrentNode()->appendChild($errorsNode); $visitor->setCurrentNode($errorsNode); $errorNodes = $this->formErrorHandler->serializeFormToXml($visitor, $form, $type); $errorsNode->appendChild($errorNodes); $visitor->revertCurrentNode(); return $visitor->getCurrentNode(); } } return $this->formErrorHandler->serializeFormToXml($visitor, $form, $type); } public function serializeFormToJson(JsonSerializationVisitor $visitor, Form $form, array $type, Context $context = null) { $isRoot = !interface_exists(SerializationVisitorInterface::class) && null === $visitor->getRoot(); $result = $this->adaptFormArray($this->formErrorHandler->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($this->formErrorHandler->serializeFormToYml($visitor, $form, $type), $context); if ($isRoot) { $visitor->setRoot($result); } return $result; } public function __call($name, $arguments) { return call_user_func_array([$this->formErrorHandler, $name], $arguments); } 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; } if ($context->hasAttribute('status_code')) { return $context->getAttribute('status_code'); } } } rest-bundle/Serializer/Normalizer/FormErrorNormalizer.php 0000644 00000003507 15120163067 0017722 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Serializer\Normalizer; use Symfony\Component\Form\FormInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** * Normalizes invalid Form instances. * * @author Guilhem N. <guilhem.niot@gmail.com> * * @internal */ class FormErrorNormalizer implements NormalizerInterface { /** * {@inheritdoc} */ public function normalize($object, $format = null, array $context = []): array { return [ 'code' => isset($context['status_code']) ? $context['status_code'] : null, 'message' => 'Validation Failed', 'errors' => $this->convertFormToArray($object), ]; } /** * {@inheritdoc} */ public function supportsNormalization($data, $format = null, array $context = []): bool { return $data instanceof FormInterface && $data->isSubmitted() && !$data->isValid(); } /** * This code has been taken from JMSSerializer. */ private function convertFormToArray(FormInterface $data): array { $form = $errors = []; foreach ($data->getErrors() as $error) { $errors[] = $error->getMessage(); } 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; } } rest-bundle/Serializer/JMSHandlerRegistry.php 0000644 00000003024 15120163067 0015272 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Serializer; use JMS\Serializer\Handler\SubscribingHandlerInterface; use JMS\Serializer\Handler\HandlerRegistryInterface; /** * Search in the class parents to find an adapted handler. * * @author Ener-Getick <egetick@gmail.com> * * @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): void { $this->registry->registerSubscribingHandler($handler); } /** * {@inheritdoc} */ public function registerHandler($direction, $typeName, $format, $handler): void { $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)); } } rest-bundle/Serializer/JMSHandlerRegistryV2.php 0000644 00000003100 15120163067 0015475 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Serializer; use JMS\Serializer\Handler\SubscribingHandlerInterface; use JMS\Serializer\Handler\HandlerRegistryInterface; /** * Search in the class parents to find an adapted handler. * * @author Ener-Getick <egetick@gmail.com> * * @internal do not depend on this class directly */ final class JMSHandlerRegistryV2 implements HandlerRegistryInterface { private $registry; public function __construct(HandlerRegistryInterface $registry) { $this->registry = $registry; } /** * {@inheritdoc} */ public function registerSubscribingHandler(SubscribingHandlerInterface $handler): void { $this->registry->registerSubscribingHandler($handler); } /** * {@inheritdoc} */ public function registerHandler(int $direction, string $typeName, string $format, $handler): void { $this->registry->registerHandler($direction, $typeName, $format, $handler); } /** * {@inheritdoc} */ public function getHandler(int $direction, string $typeName, string $format) { do { $handler = $this->registry->getHandler($direction, $typeName, $format); if (null !== $handler) { return $handler; } } while ($typeName = get_parent_class($typeName)); } } rest-bundle/Serializer/JMSSerializerAdapter.php 0000644 00000006613 15120163067 0015605 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Serializer; use FOS\RestBundle\Context\Context; use JMS\Serializer\Context as JMSContext; use JMS\Serializer\ContextFactory\DeserializationContextFactoryInterface; use JMS\Serializer\ContextFactory\SerializationContextFactoryInterface; use JMS\Serializer\DeserializationContext; use JMS\Serializer\SerializationContext; use JMS\Serializer\SerializerInterface; /** * Adapter to plug the JMS serializer into the FOSRestBundle Serializer API. * * @author Christian Flothmann <christian.flothmann@xabbuh.de> */ final class JMSSerializerAdapter implements Serializer { /** * @internal */ const SERIALIZATION = 0; /** * @internal */ const DESERIALIZATION = 1; private $serializer; private $serializationContextFactory; private $deserializationContextFactory; public function __construct( SerializerInterface $serializer, SerializationContextFactoryInterface $serializationContextFactory = null, DeserializationContextFactoryInterface $deserializationContextFactory = null ) { $this->serializer = $serializer; $this->serializationContextFactory = $serializationContextFactory; $this->deserializationContextFactory = $deserializationContextFactory; } /** * {@inheritdoc} */ public function serialize($data, string $format, Context $context): string { $context = $this->convertContext($context, self::SERIALIZATION); return $this->serializer->serialize($data, $format, $context); } /** * {@inheritdoc} */ public function deserialize(string $data, string $type, string $format, Context $context) { $context = $this->convertContext($context, self::DESERIALIZATION); return $this->serializer->deserialize($data, $type, $format, $context); } private function convertContext(Context $context, int $direction): JMSContext { if (self::SERIALIZATION === $direction) { $jmsContext = $this->serializationContextFactory ? $this->serializationContextFactory->createSerializationContext() : SerializationContext::create(); } else { $jmsContext = $this->deserializationContextFactory ? $this->deserializationContextFactory->createDeserializationContext() : DeserializationContext::create(); } foreach ($context->getAttributes() as $key => $value) { $jmsContext->setAttribute($key, $value); } if (null !== $context->getVersion()) { $jmsContext->setVersion($context->getVersion()); } if (null !== $context->getGroups()) { $jmsContext->setGroups($context->getGroups()); } if (true === $context->isMaxDepthEnabled()) { $jmsContext->enableMaxDepthChecks(); } if (null !== $context->getSerializeNull()) { $jmsContext->setSerializeNull($context->getSerializeNull()); } foreach ($context->getExclusionStrategies() as $strategy) { $jmsContext->addExclusionStrategy($strategy); } return $jmsContext; } } rest-bundle/Serializer/Serializer.php 0000644 00000001317 15120163067 0013726 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Serializer; use FOS\RestBundle\Context\Context; /** * @author Christian Flothmann <christian.flothmann@xabbuh.de> */ interface Serializer { public const FOS_BUNDLE_SERIALIZATION_CONTEXT = 'fos_bundle_serialization'; /** * @return string */ public function serialize($data, string $format, Context $context); public function deserialize(string $data, string $type, string $format, Context $context); } rest-bundle/Serializer/SymfonySerializerAdapter.php 0000644 00000003770 15120163067 0016621 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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 <christian.flothmann@xabbuh.de> */ final class SymfonySerializerAdapter implements Serializer { private $serializer; public function __construct(SerializerInterface $serializer) { $this->serializer = $serializer; } /** * {@inheritdoc} */ public function serialize($data, string $format, Context $context): string { $newContext = $this->convertContext($context); return $this->serializer->serialize($data, $format, $newContext); } /** * {@inheritdoc} */ public function deserialize(string $data, string $type, string $format, Context $context) { $newContext = $this->convertContext($context); return $this->serializer->deserialize($data, $type, $format, $newContext); } private function convertContext(Context $context): array { $newContext = []; foreach ($context->getAttributes() as $key => $value) { $newContext[$key] = $value; } if (null !== $context->getGroups()) { $newContext['groups'] = $context->getGroups(); } if (false === $context->hasAttribute(Serializer::FOS_BUNDLE_SERIALIZATION_CONTEXT)) { $newContext[Serializer::FOS_BUNDLE_SERIALIZATION_CONTEXT] = true; } $newContext['version'] = $context->getVersion(); $newContext['enable_max_depth'] = $context->isMaxDepthEnabled(); $newContext['skip_null_values'] = !$context->getSerializeNull(); return $newContext; } } rest-bundle/Util/ExceptionValueMap.php 0000644 00000002273 15120163067 0014014 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Util; /** * Stores map of values mapped to exception class * Resolves value by exception. * * @author Mikhail Shamin <munk13@gmail.com> * * @internal */ class ExceptionValueMap { /** * Map of values mapped to exception class * key => exception class * value => value associated with exception. */ private $map; /** * @param array<string,bool>|array<string,int> $map */ public function __construct(array $map) { $this->map = $map; } /** * @return bool|int|null null if not found */ public function resolveFromClassName(string $className) { foreach ($this->map as $mapClass => $value) { if (!$value) { continue; } if ($className === $mapClass || is_subclass_of($className, $mapClass)) { return $value; } } return null; } } rest-bundle/Util/ResolverTrait.php 0000644 00000002721 15120163067 0013226 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Util; use Symfony\Component\DependencyInjection\ContainerInterface; /** * @author Ener-Getick <egetick@gmail.com> * * @internal do not use this trait or its functions in your code */ trait ResolverTrait { 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) { // 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], gettype($resolved))); }, $value); return str_replace('%%', '%', $escapedValue); } } rest-bundle/Util/StopFormatListenerException.php 0000644 00000000670 15120163067 0016105 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Util; /** * Thrown to stop the format negotiator from continuing so that no format is set. */ class StopFormatListenerException extends \Exception { } rest-bundle/Validator/Constraints/Regex.php 0000644 00000002116 15120163067 0015010 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Validator\Constraints; use FOS\RestBundle\Util\ResolverTrait; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\Constraints\Regex as BaseRegex; use Symfony\Component\Validator\Constraints\RegexValidator; /** * @Annotation * * @author Ener-Getick <egetick@gmail.com> */ class Regex extends BaseRegex implements ResolvableConstraintInterface { use ResolverTrait; private $resolved; /** * {@inheritdoc} */ public function validatedBy(): string { return RegexValidator::class; } public function resolve(ContainerInterface $container): void { if ($this->resolved) { return; } $this->pattern = $this->resolveValue($container, $this->pattern); $this->resolved = true; } } rest-bundle/Validator/Constraints/ResolvableConstraintInterface.php 0000644 00000000735 15120163067 0021727 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Validator\Constraints; use Symfony\Component\DependencyInjection\ContainerInterface; interface ResolvableConstraintInterface { public function resolve(ContainerInterface $container); } rest-bundle/Version/Resolver/HeaderVersionResolver.php 0000644 00000001637 15120163067 0017217 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Version\Resolver; use FOS\RestBundle\Version\VersionResolverInterface; use Symfony\Component\HttpFoundation\Request; /** * @author Ener-Getick <egetick@gmail.com> */ final class HeaderVersionResolver implements VersionResolverInterface { private $headerName; public function __construct(string $headerName) { $this->headerName = $headerName; } /** * {@inheritdoc} */ public function resolve(Request $request): ?string { if (!$request->headers->has($this->headerName)) { return null; } return (string) $request->headers->get($this->headerName); } } rest-bundle/Version/Resolver/MediaTypeVersionResolver.php 0000644 00000001752 15120163067 0017706 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Version\Resolver; use FOS\RestBundle\Version\VersionResolverInterface; use Symfony\Component\HttpFoundation\Request; /** * @author Ener-Getick <egetick@gmail.com> */ final class MediaTypeVersionResolver implements VersionResolverInterface { private $regex; public function __construct(string $regex) { $this->regex = $regex; } /** * {@inheritdoc} */ public function resolve(Request $request): ?string { if (!$request->attributes->has('media_type') || false === preg_match($this->regex, $request->attributes->get('media_type'), $matches)) { return null; } return isset($matches['version']) ? $matches['version'] : null; } } rest-bundle/Version/Resolver/QueryParameterVersionResolver.php 0000644 00000001665 15120163067 0020776 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Version\Resolver; use FOS\RestBundle\Version\VersionResolverInterface; use Symfony\Component\HttpFoundation\Request; /** * @author Ener-Getick <egetick@gmail.com> */ final class QueryParameterVersionResolver implements VersionResolverInterface { private $parameterName; public function __construct(string $parameterName) { $this->parameterName = $parameterName; } /** * {@inheritdoc} */ public function resolve(Request $request): ?string { if (!$request->query->has($this->parameterName)) { return null; } return (string) $request->query->get($this->parameterName); } } rest-bundle/Version/ChainVersionResolver.php 0000644 00000002213 15120163067 0015237 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Version; use Symfony\Component\HttpFoundation\Request; /** * @author Ener-Getick <egetick@gmail.com> */ final class ChainVersionResolver implements VersionResolverInterface { private $resolvers = []; /** * @var VersionResolverInterface[] */ public function __construct(array $resolvers) { foreach ($resolvers as $resolver) { $this->addResolver($resolver); } } /** * {@inheritdoc} */ public function resolve(Request $request): ?string { foreach ($this->resolvers as $resolver) { $version = $resolver->resolve($request); if (null !== $version) { return $version; } } return null; } public function addResolver(VersionResolverInterface $resolver): void { $this->resolvers[] = $resolver; } } rest-bundle/Version/VersionResolverInterface.php 0000644 00000001064 15120163067 0016120 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\Version; use Symfony\Component\HttpFoundation\Request; /** * @author Ener-Getick <egetick@gmail.com> */ interface VersionResolverInterface { /** * @return string|null Current version or false if not resolved */ public function resolve(Request $request); } rest-bundle/View/ConfigurableViewHandlerInterface.php 0000644 00000001401 15120163067 0016762 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\View; /** * Specialized ViewInterface that allows dynamic configuration of JMS serializer context aspects. * * @author Lukas K. Smith <smith@pooteeweet.org> */ interface ConfigurableViewHandlerInterface extends ViewHandlerInterface { /** * @param string[]|string $groups */ public function setExclusionStrategyGroups($groups); public function setExclusionStrategyVersion(string $version); public function setSerializeNullStrategy(bool $isEnabled); } rest-bundle/View/JsonpHandler.php 0000644 00000003272 15120163067 0013007 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\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 <smith@pooteeweet.org> */ final class JsonpHandler { private $callbackParam; public function __construct(string $callbackParam) { $this->callbackParam = $callbackParam; } /** * Handles wrapping a JSON response into a JSONP response. */ public function createResponse(ViewHandler $handler, View $view, Request $request, string $format): Response { $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; } private function getCallback(Request $request): string { $callback = $request->query->get($this->callbackParam); $validator = new \JsonpCallbackValidator(); if (!is_string($callback) || !$validator->validate($callback)) { throw new BadRequestHttpException('Invalid JSONP callback value'); } return $callback; } } rest-bundle/View/View.php 0000644 00000010461 15120163067 0011330 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\View; use FOS\RestBundle\Context\Context; use Symfony\Component\HttpFoundation\Response; /** * Default View implementation. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> * @author Lukas K. Smith <smith@pooteeweet.org> */ final class View { private $data; private $statusCode; private $format; private $location; private $route; private $routeParameters; private $context; private $response; public static function create($data = null, ?int $statusCode = null, array $headers = []): self { return new static($data, $statusCode, $headers); } public static function createRedirect(string $url, int $statusCode = Response::HTTP_FOUND, array $headers = []): self { $view = static::create(null, $statusCode, $headers); $view->setLocation($url); return $view; } public static function createRouteRedirect( string $route, array $parameters = [], int $statusCode = Response::HTTP_FOUND, array $headers = [] ): self { $view = static::create(null, $statusCode, $headers); $view->setRoute($route); $view->setRouteParameters($parameters); return $view; } public function __construct($data = null, ?int $statusCode = null, array $headers = []) { $this->setData($data); $this->setStatusCode($statusCode); if (!empty($headers)) { $this->getResponse()->headers->replace($headers); } } public function setData($data): self { $this->data = $data; return $this; } public function setHeader(string $name, string $value): self { $this->getResponse()->headers->set($name, $value); return $this; } public function setHeaders(array $headers): self { $this->getResponse()->headers->replace($headers); return $this; } public function setStatusCode(?int $code): self { if (null !== $code) { $this->statusCode = $code; } return $this; } public function setContext(Context $context): self { $this->context = $context; return $this; } public function setFormat(string $format): self { $this->format = $format; return $this; } public function setLocation(string $location): self { $this->location = $location; $this->route = null; return $this; } /** * Sets the route (implicitly removes the location). */ public function setRoute(string $route): self { $this->route = $route; $this->location = null; return $this; } public function setRouteParameters(array $parameters): self { $this->routeParameters = $parameters; return $this; } public function setResponse(Response $response): self { $this->response = $response; return $this; } public function getData() { return $this->data; } public function getStatusCode(): ?int { return $this->statusCode; } public function getHeaders(): array { return $this->getResponse()->headers->all(); } public function getFormat(): ?string { return $this->format; } public function getLocation(): ?string { return $this->location; } public function getRoute(): ?string { return $this->route; } public function getRouteParameters(): ?array { return $this->routeParameters; } public function getResponse(): Response { if (null === $this->response) { $this->response = new Response(); if (null !== ($code = $this->getStatusCode())) { $this->response->setStatusCode($code); } } return $this->response; } public function getContext(): Context { if (null === $this->context) { $this->context = new Context(); } return $this->context; } } rest-bundle/View/ViewHandler.php 0000644 00000023165 15120163067 0012633 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\View; use FOS\RestBundle\Context\Context; use FOS\RestBundle\Serializer\Serializer; use Symfony\Component\Form\FormInterface; 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; /** * 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 via the Serializer * component. * * @author Jordi Boggiano <j.boggiano@seld.be> * @author Lukas K. Smith <smith@pooteeweet.org> */ final class ViewHandler implements ConfigurableViewHandlerInterface { /** * Key format, value a callable that returns a Response instance. * * @var array */ private $customHandlers = []; /** * The supported formats as keys. * * @var array */ private $formats; private $failedValidationCode; private $emptyContentCode; private $serializeNull; private $exclusionStrategyGroups = []; private $exclusionStrategyVersion; private $serializeNullStrategy; private $urlGenerator; private $serializer; private $requestStack; private $options; private function __construct( UrlGeneratorInterface $urlGenerator, Serializer $serializer, RequestStack $requestStack, array $formats = null, int $failedValidationCode = Response::HTTP_BAD_REQUEST, int $emptyContentCode = Response::HTTP_NO_CONTENT, bool $serializeNull = false, array $options = [] ) { $this->urlGenerator = $urlGenerator; $this->serializer = $serializer; $this->requestStack = $requestStack; $this->formats = (array) $formats; $this->failedValidationCode = $failedValidationCode; $this->emptyContentCode = $emptyContentCode; $this->serializeNull = $serializeNull; $this->options = $options + [ 'exclusionStrategyGroups' => [], 'exclusionStrategyVersion' => null, 'serializeNullStrategy' => null, ]; $this->reset(); } public static function create( UrlGeneratorInterface $urlGenerator, Serializer $serializer, RequestStack $requestStack, array $formats = null, int $failedValidationCode = Response::HTTP_BAD_REQUEST, int $emptyContentCode = Response::HTTP_NO_CONTENT, bool $serializeNull = false, array $options = [] ): self { return new self($urlGenerator, $serializer, $requestStack, $formats, $failedValidationCode, $emptyContentCode, $serializeNull, $options); } /** * @param string[]|string $groups */ public function setExclusionStrategyGroups($groups): void { $this->exclusionStrategyGroups = (array) $groups; } public function setExclusionStrategyVersion(string $version): void { $this->exclusionStrategyVersion = $version; } public function setSerializeNullStrategy(bool $isEnabled): void { $this->serializeNullStrategy = $isEnabled; } /** * {@inheritdoc} */ public function supports(string $format): bool { 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. */ public function registerHandler(string $format, callable $callable): void { $this->customHandlers[$format] = $callable; } /** * Handles a request with the proper handler. * * Decides on which handler to use based on the request format. * * @throws UnsupportedMediaTypeHttpException */ public function handle(View $view, Request $request = null): Response { 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); } public function createRedirectResponse(View $view, string $location, string $format): Response { $content = null; if ((Response::HTTP_CREATED === $view->getStatusCode() || Response::HTTP_ACCEPTED === $view->getStatusCode()) && null !== $view->getData()) { $response = $this->initResponse($view, $format); } else { $response = $view->getResponse(); } $code = $this->getStatusCode($view, $content); $response->setStatusCode($code); $response->headers->set('Location', $location); return $response; } public function createResponse(View $view, Request $request, string $format): Response { $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')) { $mimeType = $request->attributes->get('media_type'); if (null === $mimeType) { $mimeType = $request->getMimeType($format); } $response->headers->set('Content-Type', $mimeType); } return $response; } /** * 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 string|false|null */ private function getStatusCode(View $view, $content = null): int { $form = $this->getFormFromView($view); if (null !== $form && $form->isSubmitted() && !$form->isValid()) { return $this->failedValidationCode; } $statusCode = $view->getStatusCode(); if (null !== $statusCode) { return $statusCode; } return null !== $content ? Response::HTTP_OK : $this->emptyContentCode; } private function getSerializationContext(View $view): Context { $context = $view->getContext(); $groups = $context->getGroups(); if (empty($groups) && $this->exclusionStrategyGroups) { $context->setGroups($this->exclusionStrategyGroups); } if (null === $context->getVersion() && $this->exclusionStrategyVersion) { $context->setVersion($this->exclusionStrategyVersion); } if (null === $context->getSerializeNull() && null !== $this->serializeNullStrategy) { $context->setSerializeNull($this->serializeNullStrategy); } if (null !== $view->getStatusCode() && !$context->hasAttribute('status_code')) { $context->setAttribute('status_code', $view->getStatusCode()); } return $context; } private function initResponse(View $view, string $format): Response { $content = null; if ($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); $content = $this->serializer->serialize($data, $format, $context); } $response = $view->getResponse(); $response->setStatusCode($this->getStatusCode($view, $content)); if (null !== $content) { $response->setContent($content); } return $response; } private function getFormFromView(View $view): ?FormInterface { $data = $view->getData(); if ($data instanceof FormInterface) { return $data; } if (is_array($data) && isset($data['form']) && $data['form'] instanceof FormInterface) { return $data['form']; } return null; } private function getDataFromView(View $view) { $form = $this->getFormFromView($view); if (null === $form) { return $view->getData(); } return $form; } public function reset(): void { $this->exclusionStrategyGroups = $this->options['exclusionStrategyGroups']; $this->exclusionStrategyVersion = $this->options['exclusionStrategyVersion']; $this->serializeNullStrategy = $this->options['serializeNullStrategy']; } } rest-bundle/View/ViewHandlerInterface.php 0000644 00000002655 15120163067 0014455 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle\View; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** * @author Jordi Boggiano <j.boggiano@seld.be> * @author Lukas K. Smith <smith@pooteeweet.org> */ interface ViewHandlerInterface { /** * @return bool */ public function supports(string $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. */ public function registerHandler(string $format, callable $callable); /** * Handles a request with the proper handler. * * Decides on which handler to use based on the request format * * @return Response */ public function handle(View $view, Request $request = null); /** * @return Response */ public function createRedirectResponse(View $view, string $location, string $format); /** * @return Response */ public function createResponse(View $view, Request $request, string $format); } rest-bundle/.readthedocs.yaml 0000644 00000000202 15120163067 0012212 0 ustar 00 version: 2 sphinx: configuration: Resources/doc/conf.py python: install: - requirements: Resources/doc/requirements.txt rest-bundle/CHANGELOG.md 0000644 00000055135 15120163067 0010613 0 ustar 00 CHANGELOG ========= 3.0.3 ----- * fixed being able to configure exception codes and messages based on interfaces (e.g. `Throwable`) 3.0.2 ----- * fixed the `ViewHandler` to not override an already set `status_code` in the serialization context * fixed embedding status codes in the response body when a mapping of exception classes to status codes is configured 3.0.1 ----- * fixed handling requests without a content type inside the `RequestBodyParamConverter` * `FlattenExceptionNormalizer` does no longer implement the `CacheableSupportsMethodInterface` to ensure compatibility with older versions of the Symfony Serializer component 3.0.0 ----- ### Features * added support for Symfony 5 compatibility ### BC Breaks * the route generation feature was removed, setting it to another value than `false` leads to an exception * support for serializing exceptions was removed, setting the `fos_rest.exception.serialize_exceptions` option to anything else than `false` leads to an exception * support for returning anything other than `string` or `null` from `resolve()` when implementing the `VersionResolverInterface` was removed * removed support for passing version numbers as integers to `Context::setVersion()` * removed the `isFormatTemplating()`, `renderTemplate()`, and `prepareTemplateParameters()` methods from the `ViewHandler` class and the `ViewHandlerInterface` * the constructor of the `ViewHandler` class is `private` now, use the static `create()` factory method instead * removed the `setTemplateVar()`, `setPopulateDefaultVars()`, `getTemplateVar()`, and `isPopulateDefaultVars()` methods from the `Controller\Annotations\View` class * removed the `setEngine()`, `setTemplate()`, `setTemplateData()`, `setTemplateVar()`, `getEngine()`, `getTemplate()`, `getTemplateData()`, and `getTemplateVar()` methods from the `View\View` class * changed the default value of the `fos_rest.body_listener` option to `false` * removed the `setMaxDepth()`/`getMaxDepth()` methods from the `Context` class, use `enableMaxDepth()`/`disableMaxDepth()` instead * dropped support for Symfony components < 4.4 * removed the following options: * `fos_rest.access_denied_listener` * `fos_rest.exception.exception_controller` * `fos_rest.exception.exception_listener` * `fos_rest.exception.service` * `fos_rest.service.inflector` * `fos_rest.service.router` * `fos_rest.service.templating` * `fos_rest.view.default_engine` * `fos_rest.view.force_redirects` * `fos_rest.view.templating_formats` * removed the following classes and interfaces: * `FOS\RestBundle\Controller\Annotations\NamePrefix` * `FOS\RestBundle\Controller\Annotations\NoRoute` * `FOS\RestBundle\Controller\Annotations\Prefix` * `FOS\RestBundle\Controller\Annotations\RouteResource` * `FOS\RestBundle\Controller\Annotations\Version` * `FOS\RestBundle\Controller\ExceptionController` * `FOS\RestBundle\Controller\TemplatingExceptionController` * `FOS\RestBundle\Controller\TwigExceptionController` * `FOS\RestBundle\EventListener\AccessDeniedListener` * `FOS\RestBundle\EventListener\ExceptionListener` * `FOS\RestBundle\Inflector\DoctrineInflector` * `FOS\RestBundle\Inflector\InflectorInterface` * `FOS\RestBundle\Routing\Loader\DirectoryRouteLoader` * `FOS\RestBundle\Routing\Loader\Reader\RestActionReader` * `FOS\RestBundle\Routing\Loader\Reader\RestControllerReader` * `FOS\RestBundle\Routing\Loader\RestRouteLoader` * `FOS\RestBundle\Routing\Loader\RestRouteProcessor` * `FOS\RestBundle\Routing\Loader\RestXmlCollectionLoader` * `FOS\RestBundle\Routing\Loader\RestYamlCollectionLoader` * `FOS\RestBundle\Routing\ClassResourceInterface` * `FOS\RestBundle\Routing\RestRouteCollection` * `FOS\RestBundle\Serializer\Normalizer\ExceptionHandler` * `FOS\RestBundle\Serializer\Normalizer\ExceptionNormalizer` * removed the following services and aliases: * `fos_rest.access_denied_listener` * `fos_rest.exception_listener` * `fos_rest.exception.controller` * `fos_rest.exception.twig_controller` * `fos_rest.inflector` * `fos_rest.router` * `fos_rest.routing.loader.controller` * `fos_rest.routing.loader.directory` * `fos_rest.routing.loader.processor` * `fos_rest.routing.loader.reader.controller` * `fos_rest.routing.loader.reader.action` * `fos_rest.routing.loader.xml_collection` * `fos_rest.routing.loader.yaml_collection` * `fos_rest.serializer.exception_normalizer.jms` * `fos_rest.serializer.exception_normalizer.symfony` * `fos_rest.templating` * the following classes are marked as `internal` (backwards compatibility will no longer be guaranteed): * `FOS\RestBundle\DependencyInjection\Compiler\HandlerRegistryDecorationPass` * `FOS\RestBundle\DependencyInjection\FOSRestExtension` * `FOS\RestBundle\Form\Extension\DisableCSRFExtension` * `FOS\RestBundle\Form\Transformer\EntityToIdObjectTransformer` * `FOS\RestBundle\Normalizer\CamelKeysNormalizer` * `FOS\RestBundle\Normalizer\CamelKeysNormalizerWithLeadingUnderscore` * `FOS\RestBundle\Serializer\Normalizer\FormErrorHandler` * `FOS\RestBundle\Serializer\Normalizer\FormErrorNormalizer` * `FOS\RestBundle\Util\ExceptionValueMap` * the following classes are now `final`: * `FOS\RestBundle\Decoder\ContainerDecoderProvider` * `FOS\RestBundle\Decoder\JsonDecoder` * `FOS\RestBundle\Decoder\JsonToFormDecoder` * `FOS\RestBundle\Decoder\XmlDecoder` * `FOS\RestBundle\Form\Transformer\EntityToIdObjectTransformer` * `FOS\RestBundle\Negotiation\FormatNegotiator` * `FOS\RestBundle\Request\ParamFetcher` * `FOS\RestBundle\Request\ParamReader` * `FOS\RestBundle\Request\RequestBodyParamConverter` * `FOS\RestBundle\Response\AllowMethodsLoader\AllowedMethodsRouterLoader` * `FOS\RestBundle\Serializer\JMSSerializerAdapter` * `FOS\RestBundle\Serializer\SymfonySerializerAdapter` * `FOS\RestBundle\Version\ChainVersionResolver` * `FOS\RestBundle\Version\Resolver\HeaderVersionResolver` * `FOS\RestBundle\Version\Resolver\MediaTypeVersionResolver` * `FOS\RestBundle\Version\Resolver\QueryParameterVersionResolver` * `FOS\RestBundle\View\JsonpHandler` * `FOS\RestBundle\View\View` * `FOS\RestBundle\View\ViewHandler` 2.8.3 ----- * fixed being able to configure exception codes and messages based on interfaces (e.g. `Throwable`) 2.8.2 ----- * fixed the `ViewHandler` to not override an already set `status_code` in the serialization context * fixed embedding status codes in the response body when a mapping of exception classes to status codes is configured 2.8.1 ----- * fixed handling requests without a content type inside the `RequestBodyParamConverter` * `FlattenExceptionNormalizer` does no longer implement the `CacheableSupportsMethodInterface` to ensure compatibility with older versions of the Symfony Serializer component 2.8.0 ----- ### Features * added a `SerializerErrorHandler` that leverages the `FOS\RestBundle\Serializer\Serializer` interface to hook into the error rendering process provided by the ErrorHandler component since Symfony 4.4 * added a new normalizer (for the Symfony serializer) and a new handler (for the JMS serializer) to serialize `FlattenException` instances, for backwards compatibility the resulting format by default is the same as was used for exceptions/errors before, use the `flatten_exception_format` to opt-in to a format compatible with the API Problem spec (RFC 7807): ```yaml fos_rest: exception: flatten_exception_format: 'rfc7807' ``` * added a new `ResponseStatusCodeListener` that maps exception/error codes to response status codes, enable it by setting the new `map_exception_codes` option to `true` ### Deprecations * the route generation feature is deprecated, disable it explicitly: ```yaml fos_rest: routing_loader: false ``` You need to configure your routes explicitly, e.g. using the Symfony Core annotations or the FOSRestBundle shortcuts like `FOS\RestBundle\Controller\Annotations\Get`. You can use `bin/console debug:router --show-controllers` to help with the migration and compare routes before and after it. Change the routing loading: Before: ``` Acme\Controller\TestController: type: rest resource: Acme\Controller\TestController ``` After: ``` Acme\Controller\TestController: type: annotation resource: Acme\Controller\TestController ``` When using the Symfony Core route loading, route names might change as the FOSRestBundle used a different naming convention. Mind the `.{_format}` suffix if you used the `fos_rest.routing_loader.include_format` option. In case you have OpenAPI/Swagger annotations, you can also use [OpenAPI-Symfony-Routing](https://github.com/Tobion/OpenAPI-Symfony-Routing) which removes the need to have routing information duplicated. It also allows to add the `.{_format}` suffix automatically as before. If migration to explicit routes is not possible or feasible, consider using [RestRoutingBundle](https://github.com/handcraftedinthealps/RestRoutingBundle) which extracted the auto-generation of routes in a BC way. * deprecated support for serializing exceptions, disable it by setting the `serialize_exceptions` option to false: ```yaml fos_rest: exception: serialize_exceptions: false ``` * deprecated returning anything other than `string` or `null` from `resolve()` when implementing the `VersionResolverInterface`. * deprecated support for passing version numbers as integers to `Context::setVersion()` (strings will be enforced as of 3.0) * deprecated the `isFormatTemplating()`, `renderTemplate()`, and `prepareTemplateParameters()` methods of the `ViewHandler` class and the `ViewHandlerInterface` * deprecated the constructor of the `ViewHandler` class, use the static `create()` factory method instead * deprecated the `setTemplateVar()`, `setPopulateDefaultVars()`, `getTemplateVar()`, and `isPopulateDefaultVars()` methods of the `Controller\Annotations\View` class * deprecated the `setEngine()`, `setTemplate()`, `setTemplateData()`, `setTemplateVar()`, `getEngine()`, `getTemplate()`, `getTemplateData()`, and `getTemplateVar()` methods of the `View\View` class * deprecated not enabling the `fos_rest.body_listener` option explicitly, it will be disabled by default in 3.0 * deprecated the following options: * `fos_rest.access_denied_listener` * `fos_rest.exception.exception_controller` * `fos_rest.exception.exception_listener` * `fos_rest.exception.service` * `fos_rest.service.inflector` * `fos_rest.service.router` * `fos_rest.service.templating` * `fos_rest.view.default_engine` * `fos_rest.view.force_redirects` * `fos_rest.view.templating_formats` * the following classes and interfaces are marked as `deprecated`, they will be removed in 3.0: * `FOS\RestBundle\Controller\Annotations\NamePrefix` * `FOS\RestBundle\Controller\Annotations\NoRoute` * `FOS\RestBundle\Controller\Annotations\Prefix` * `FOS\RestBundle\Controller\Annotations\RouteResource` * `FOS\RestBundle\Controller\Annotations\Version` * `FOS\RestBundle\Controller\ExceptionController` * `FOS\RestBundle\Controller\TemplatingExceptionController` * `FOS\RestBundle\Controller\TwigExceptionController` * `FOS\RestBundle\EventListener\AccessDeniedListener` * `FOS\RestBundle\EventListener\ExceptionListener` * `FOS\RestBundle\Inflector\DoctrineInflector` * `FOS\RestBundle\Inflector\InflectorInterface` * `FOS\RestBundle\Routing\Loader\DirectoryRouteLoader` * `FOS\RestBundle\Routing\Loader\Reader\RestActionReader` * `FOS\RestBundle\Routing\Loader\Reader\RestControllerReader` * `FOS\RestBundle\Routing\Loader\RestRouteLoader` * `FOS\RestBundle\Routing\Loader\RestRouteProcessor` * `FOS\RestBundle\Routing\Loader\RestXmlCollectionLoader` * `FOS\RestBundle\Routing\Loader\RestYamlCollectionLoader` * `FOS\RestBundle\Routing\ClassResourceInterface` * `FOS\RestBundle\Routing\RestRouteCollection` * `FOS\RestBundle\Serializer\Normalizer\ExceptionHandler` * `FOS\RestBundle\Serializer\Normalizer\ExceptionNormalizer` * the following services and aliases are marked as `deprecated`, they will be removed in 3.0: * `fos_rest.access_denied_listener` * `fos_rest.exception_listener` * `fos_rest.exception.controller` * `fos_rest.exception.twig_controller` * `fos_rest.inflector` * `fos_rest.router` * `fos_rest.routing.loader.controller` * `fos_rest.routing.loader.directory` * `fos_rest.routing.loader.processor` * `fos_rest.routing.loader.reader.controller` * `fos_rest.routing.loader.reader.action` * `fos_rest.routing.loader.xml_collection` * `fos_rest.routing.loader.yaml_collection` * `fos_rest.serializer.exception_normalizer.jms` * `fos_rest.serializer.exception_normalizer.symfony` * `fos_rest.templating` * the following classes are marked as `internal` (backwards compatibility will no longer be guaranteed starting with FOSRestBundle 3.0): * `FOS\RestBundle\DependencyInjection\Compiler\HandlerRegistryDecorationPass` * `FOS\RestBundle\DependencyInjection\FOSRestExtension` * `FOS\RestBundle\Form\Extension\DisableCSRFExtension` * `FOS\RestBundle\Form\Transformer\EntityToIdObjectTransformer` * `FOS\RestBundle\Normalizer\CamelKeysNormalizer` * `FOS\RestBundle\Normalizer\CamelKeysNormalizerWithLeadingUnderscore` * `FOS\RestBundle\Serializer\Normalizer\FormErrorHandler` * `FOS\RestBundle\Serializer\Normalizer\FormErrorNormalizer` * `FOS\RestBundle\Util\ExceptionValueMap` * the following classes are marked as `final` (extending them will not be supported as of 3.0): * `FOS\RestBundle\Decoder\ContainerDecoderProvider` * `FOS\RestBundle\Decoder\JsonDecoder` * `FOS\RestBundle\Decoder\JsonToFormDecoder` * `FOS\RestBundle\Decoder\XmlDecoder` * `FOS\RestBundle\Form\Transformer\EntityToIdObjectTransformer` * `FOS\RestBundle\Negotiation\FormatNegotiator` * `FOS\RestBundle\Request\ParamFetcher` * `FOS\RestBundle\Request\ParamReader` * `FOS\RestBundle\Request\RequestBodyParamConverter` * `FOS\RestBundle\Response\AllowMethodsLoader\AllowedMethodsRouterLoader` * `FOS\RestBundle\Serializer\JMSSerializerAdapter` * `FOS\RestBundle\Serializer\SymfonySerializerAdapter` * `FOS\RestBundle\Version\ChainVersionResolver` * `FOS\RestBundle\Version\Resolver\HeaderVersionResolver` * `FOS\RestBundle\Version\Resolver\MediaTypeVersionResolver` * `FOS\RestBundle\Version\Resolver\QueryParameterVersionResolver` * `FOS\RestBundle\View\JsonpHandler` * `FOS\RestBundle\View\View` * `FOS\RestBundle\View\ViewHandler` 2.7.4 ----- * fixed compatibility with JMS Serializer with explicitly disabled max depth checks (#2060) * fixed config validation when mapping `Throwable` instances of classes that do not extend PHP's `Exception` class (#2131) 2.7.3 ----- * harden the `JsonToFormDecoder` to not error on non-array input (#2145) 2.7.2 ----- * fixed serializing Error instances when the Symfony Serializer is used (#2110) * fixed serializing Error instances when JMS Serializer is used (#2105) * fixed compatibility with `null` owner returned by SensioFrameworkExtraBundle (#2097) * completely fixed handling `Throwable` objects in `ExceptionController::showAction()`, continues #2093 (#2096) 2.7.1 ----- * fixed handling all `Throwable` objects in `ExceptionController::showAction()` (#2093) * fixed `ViewHandlerInterface` alias definition (#2085) 2.7.0 ----- * ignore `SessionInterface` and `UserInterface` controller action arguments * fixed `ExceptionListener` deprecation warning * fixed `ControllerNameParser` deprecation warning * fixed `DisableCSRFExtension::getExtendedTypes()` return type * improved `EngineInterface` error message in `ViewHandler` * improved Symfony 4.4 compatibility * automatically use Twig as templating engine when available 2.6.0 ----- * ensure compatibility with the `FlattenException` from the new ErrorRenderer component * fix handling the `serialize_null` option with the Symfony serializer * added support for using multiple constraints for the `requirements` option of the `@RequestParam` annotation * drop support for PHP 5.5, 5.6 and 7.0 * drop support for SF 4.0, 4.1 and 4.2 (3.4 LTS is still supported) * deprecated using the `ParamFetcher` class without passing a validator as the third argument, this argument will become mandatory in 3.0 * fix compatiblity without the deprecated templating in Symfony 4.3; see #2012 on how to configure the FOSRestBundle * removed symfony/templating from the dependencies; if you still use it you need to require it in your app 2.5.0 ----- * compatibility with Symfony 4.2 * deprecated the `FOSRestController` base class, use the new `AbstractFOSRestController` instead * dropped support for Symfony 2.7 to 3.3 * compatibility with JMS Serializer 2 and JMSSerializerBundle 3 * overwrite rules when they are defined in different config files instead of throwing exceptions * fixed using the `nullable` option of the param annotations when the `map` option is enabled * ensure a predictable order of routes by sorting controllers by name when loading classes from a directory * reset the internal state of the view handler to fix compatibility with PHP-PM * fix different bugs related to the handling of API versions (see #1491, #1529, #1691) 2.4.0 ----- * [BC BREAK] The `@Route` annotation and all its children no longer extend SensioFrameworkExtraBundle's annotation. The main effect is that `@Route::$service` is no longer available. Instead, define your controllers using the FQCN as service IDs or create an alias in the container using the FQCN. 2.3.1 ----- * improved Symfony 4 compatibility * manually decorate the core JMS handler registry * run checks after SensioFrameworkExtraBundle * made the view handler alias public * check for definitions before they might be removed * added Yaml routing resource support * refactored several unit tests 2.3.0 ----- * added support for file paths to the directory route loader * added support for context factories when using JMS Serializer * the `RequestBodyParamConverter` ignores unrelated controller arguments to not conflict with Symfony's built-in argument resolver * made the bundle compatible with SensioFrameworkExtraBundle 4.x * added some interface aliases to support by ID autowiring * added support for custom keys for groups when using JMSSerializerBundle * allow to load FOSRestBundle inside the kernel before JMSSerializerBundle * added the `fos_rest.routing_loader.prefix_methods` option to disable method name prefixes in generated route names * removed newline characters from exception messages 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 rest-bundle/FOSRestBundle.php 0000644 00000003272 15120163067 0012125 0 ustar 00 <?php /* * This file is part of the FOSRestBundle package. * * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FOS\RestBundle; use FOS\RestBundle\DependencyInjection\Compiler\ConfigurationCheckPass; use FOS\RestBundle\DependencyInjection\Compiler\HandlerRegistryDecorationPass; use FOS\RestBundle\DependencyInjection\Compiler\JMSFormErrorHandlerPass; use FOS\RestBundle\DependencyInjection\Compiler\JMSHandlersPass; use FOS\RestBundle\DependencyInjection\Compiler\FormatListenerRulesPass; use FOS\RestBundle\DependencyInjection\Compiler\SerializerConfigurationPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\HttpKernel\Bundle\Bundle; /** * @author Lukas Kahwe Smith <smith@pooteeweet.org> * @author Eriksen Costa <eriksencosta@gmail.com> */ class FOSRestBundle extends Bundle { const ZONE_ATTRIBUTE = '_fos_rest_zone'; /** * {@inheritdoc} */ public function build(ContainerBuilder $container) { $container->addCompilerPass(new SerializerConfigurationPass()); $container->addCompilerPass(new ConfigurationCheckPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -10); $container->addCompilerPass(new FormatListenerRulesPass()); $container->addCompilerPass(new JMSFormErrorHandlerPass()); $container->addCompilerPass(new JMSHandlersPass(), PassConfig::TYPE_BEFORE_REMOVING, -10); $container->addCompilerPass(new HandlerRegistryDecorationPass(), PassConfig::TYPE_AFTER_REMOVING); } } rest-bundle/LICENSE 0000644 00000002117 15120163067 0007777 0 ustar 00 Copyright (c) 2011-2021 FriendsOfSymfony <http://friendsofsymfony.github.com/> 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. rest-bundle/README.md 0000644 00000004155 15120163067 0010255 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 - Accept header format negotiation including handling for custom mime types - RESTful decoding of HTTP request body and Accept headers - Map exception codes to HTTP response status codes - A serializer error renderer that returns exceptions and errors in a format compatible with RFC 7807 using the Symfony Serializer component or the JMS Serializer [](https://github.com/FriendsOfSymfony/FOSRestBundle/actions?query=workflow:CI) [](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSRestBundle/?branch=master) [](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSRestBundle/?branch=master) [](https://packagist.org/packages/FriendsOfSymfony/rest-bundle) [](https://packagist.org/packages/FriendsOfSymfony/rest-bundle) [](https://insight.sensiolabs.com/projects/0be23389-2e85-49cf-b333-caaa36d11c62) Documentation ------------- [Read the Documentation](https://fosrestbundle.readthedocs.io/) Please see the upgrade files (`UPGRADING-X.X.md`) for any relevant instructions when upgrading to a newer version. Installation ------------ All the installation instructions are located in the [documentation](https://fosrestbundle.readthedocs.io/en/latest/1-setting_up_the_bundle.html?#step-1-setting-up-the-bundle). License ------- This bundle is under the MIT license. See the complete license [in the bundle](LICENSE). rest-bundle/UPGRADING-3.0.md 0000644 00000016104 15120163067 0011133 0 ustar 00 Upgrading From 2.x To 3.0 ========================= * Enabling the `fos_rest.routing_loader` option is not supported anymore. Setting it to another value than `false` leads to an exception: ```yaml fos_rest: routing_loader: false ``` You need to configure your routes explicitly, e.g. using the Symfony Core annotations or the FOSRestBundle shortcuts like `FOS\RestBundle\Controller\Annotations\Get`. You can use `bin/console debug:router --show-controllers` to help with the migration and compare routes before and after it. Change the routing loading: Before: ``` Acme\Controller\TestController: type: rest resource: Acme\Controller\TestController ``` After: ``` Acme\Controller\TestController: type: annotation resource: Acme\Controller\TestController ``` When using the Symfony Core route loading, route names might change as the FOSRestBundle used a different naming convention. Mind the `.{_format}` suffix if you used the `fos_rest.routing_loader.include_format` option. In case you have OpenAPI/Swagger annotations, you can also use [OpenAPI-Symfony-Routing](https://github.com/Tobion/OpenAPI-Symfony-Routing) which removes the need to have routing information duplicated. It also allows to add the `.{_format}` suffix automatically as before. If migration to explicit routes is not possible or feasible, consider using [RestRoutingBundle](https://github.com/handcraftedinthealps/RestRoutingBundle) which extracted the auto-generation of routes in a BC way. * Support for serializing exceptions has been removed. Setting the `fos_rest.exception.serialize_exceptions` option to anything else than `false` leads to an exception. * Support for returning anything other than `string` or `null` from `resolve()` when implementing the `VersionResolverInterface` has been removed. * `Context::setVersion()` does not accept integers anymore. * The `isFormatTemplating()`, `renderTemplate()`, and `prepareTemplateParameters()` methods of the `ViewHandler` class and the `ViewHandlerInterface` have been removed. * The constructor of the `ViewHandler` class has been changed to `private`. Use the static `create()` factory method instead. * The `setTemplateVar()`, `setPopulateDefaultVars()`, `getTemplateVar()`, and `isPopulateDefaultVars()` methods of the `Controller\Annotations\View` class have been removed. * The `setEngine()`, `setTemplate()`, `setTemplateData()`, `setTemplateVar()`, `getEngine()`, `getTemplate()`, `getTemplateData()`, and `getTemplateVar()` methods of the `View\View` class have been removed. * The default value of the `fos_rest.body_listener` option has been changed from enabled to disabled. * The `setMaxDepth()` method has been removed from the `Context` class. Use the `enableMaxDepth()` and `disableMaxDepth()` methods instead. * The `getMaxDepth()` method has been removed from the `Context` class. Use the `isMaxDepthEnabled()` method instead. * The following options have been removed: * `fos_rest.access_denied_listener` * `fos_rest.exception.exception_controller` * `fos_rest.exception.exception_listener` * `fos_rest.exception.service` * `fos_rest.service.inflector` * `fos_rest.service.router` * `fos_rest.service.templating` * `fos_rest.view.default_engine` * `fos_rest.view.force_redirects` * `fos_rest.view.templating_formats` * The following classes and interfaces have been removed: * `FOS\RestBundle\Controller\Annotations\NamePrefix` * `FOS\RestBundle\Controller\Annotations\NoRoute` * `FOS\RestBundle\Controller\Annotations\Prefix` * `FOS\RestBundle\Controller\Annotations\RouteResource` * `FOS\RestBundle\Controller\Annotations\Version` * `FOS\RestBundle\Controller\ExceptionController` * `FOS\RestBundle\Controller\FOSRestController` * `FOS\RestBundle\Controller\TemplatingExceptionController` * `FOS\RestBundle\Controller\TwigExceptionController` * `FOS\RestBundle\EventListener\AccessDeniedListener` * `FOS\RestBundle\EventListener\ExceptionListener` * `FOS\RestBundle\Inflector\DoctrineInflector` * `FOS\RestBundle\Inflector\InflectorInterface` * `FOS\RestBundle\Routing\Loader\DirectoryRouteLoader` * `FOS\RestBundle\Routing\Loader\Reader\RestActionReader` * `FOS\RestBundle\Routing\Loader\Reader\RestControllerReader` * `FOS\RestBundle\Routing\Loader\RestRouteLoader` * `FOS\RestBundle\Routing\Loader\RestRouteProcessor` * `FOS\RestBundle\Routing\Loader\RestXmlCollectionLoader` * `FOS\RestBundle\Routing\Loader\RestYamlCollectionLoader` * `FOS\RestBundle\Routing\ClassResourceInterface` * `FOS\RestBundle\Routing\RestRouteCollection` * `FOS\RestBundle\Serializer\Normalizer\ExceptionHandler` * `FOS\RestBundle\Serializer\Normalizer\ExceptionNormalizer` * The following services and aliases have been removed: * `fos_rest.access_denied_listener` * `fos_rest.exception_listener` * `fos_rest.exception.controller` * `fos_rest.exception.twig_controller` * `fos_rest.routing.loader.controller` * `fos_rest.routing.loader.directory` * `fos_rest.routing.loader.processor` * `fos_rest.routing.loader.reader.controller` * `fos_rest.routing.loader.reader.action` * `fos_rest.routing.loader.xml_collection` * `fos_rest.routing.loader.yaml_collection` * `fos_rest.serializer.exception_normalizer.jms` * `fos_rest.serializer.exception_normalizer.symfony` * `fos_rest.templating` * The following classes are marked as `internal` (backwards compatibility will no longer be guaranteed): * `FOS\RestBundle\DependencyInjection\Compiler\HandlerRegistryDecorationPass` * `FOS\RestBundle\DependencyInjection\FOSRestExtension` * `FOS\RestBundle\Form\Extension\DisableCSRFExtension` * `FOS\RestBundle\Form\Transformer\EntityToIdObjectTransformer` * `FOS\RestBundle\Normalizer\CamelKeysNormalizer` * `FOS\RestBundle\Normalizer\CamelKeysNormalizerWithLeadingUnderscore` * `FOS\RestBundle\Serializer\Normalizer\FormErrorHandler` * `FOS\RestBundle\Serializer\Normalizer\FormErrorNormalizer` * `FOS\RestBundle\Util\ExceptionValueMap` * The following classes are now `final`: * `FOS\RestBundle\Decoder\ContainerDecoderProvider` * `FOS\RestBundle\Decoder\JsonDecoder` * `FOS\RestBundle\Decoder\JsonToFormDecoder` * `FOS\RestBundle\Decoder\XmlDecoder` * `FOS\RestBundle\Form\Transformer\EntityToIdObjectTransformer` * `FOS\RestBundle\Negotiation\FormatNegotiator` * `FOS\RestBundle\Request\ParamFetcher` * `FOS\RestBundle\Request\ParamReader` * `FOS\RestBundle\Request\RequestBodyParamConverter` * `FOS\RestBundle\Response\AllowMethodsLoader\AllowedMethodsRouterLoader` * `FOS\RestBundle\Serializer\JMSSerializerAdapter` * `FOS\RestBundle\Serializer\SymfonySerializerAdapter` * `FOS\RestBundle\Version\ChainVersionResolver` * `FOS\RestBundle\Version\Resolver\HeaderVersionResolver` * `FOS\RestBundle\Version\Resolver\MediaTypeVersionResolver` * `FOS\RestBundle\Version\Resolver\QueryParameterVersionResolver` * `FOS\RestBundle\View\JsonpHandler` * `FOS\RestBundle\View\View` * `FOS\RestBundle\View\ViewHandler` rest-bundle/composer.json 0000644 00000006307 15120163067 0011521 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" } ], "autoload":{ "psr-4":{ "FOS\\RestBundle\\": "" }, "exclude-from-classmap": [ "Resources/", "Tests/" ] }, "require": { "php": "^7.2|^8.0", "symfony/config": "^4.4|^5.3|^6.0", "symfony/dependency-injection": "^4.4|^5.3|^6.0", "symfony/event-dispatcher": "^4.4|^5.3|^6.0", "symfony/framework-bundle": "^4.4.1|^5.0|^6.0", "symfony/http-foundation": "^4.4|^5.3|^6.0", "symfony/http-kernel": "^4.4|^5.3|^6.0", "symfony/routing": "^4.4|^5.3|^6.0", "symfony/security-core": "^4.4|^5.3|^6.0", "willdurand/jsonp-callback-validator": "^1.0|^2.0", "willdurand/negotiation": "^2.0|^3.0" }, "require-dev": { "doctrine/annotations": "^1.13.2", "friendsofphp/php-cs-fixer": "^3.0", "jms/serializer": "^1.13|^2.0|^3.0", "jms/serializer-bundle": "^2.4.3|^3.0.1|^4.0|^5.0", "psr/http-message": "^1.0", "psr/log": "^1.0|^2.0|^3.0", "sensio/framework-extra-bundle": "^6.1", "symfony/phpunit-bridge": "^5.3|^6.0", "symfony/asset": "^4.4|^5.3|^6.0", "symfony/browser-kit": "^4.4|^5.3|^6.0", "symfony/css-selector": "^4.4|^5.3|^6.0", "symfony/expression-language": "^4.4|^5.3|^6.0", "symfony/form": "^4.4|^5.3|^6.0", "symfony/mime": "^4.4|^5.3|^6.0", "symfony/security-bundle": "^4.4|^5.3|^6.0", "symfony/serializer": "^4.4|^5.3|^6.0", "symfony/twig-bundle": "^4.4|^5.3|^6.0", "symfony/validator": "^4.4|^5.3|^6.0", "symfony/web-profiler-bundle": "^4.4|^5.3|^6.0", "symfony/yaml": "^4.4|^5.3|^6.0" }, "suggest": { "sensio/framework-extra-bundle": "Add support for the request body converter and the view response listener, requires ^3.0", "jms/serializer-bundle": "Add support for advanced serialization capabilities, recommended, requires ^2.0|^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" }, "conflict": { "doctrine/annotations": "<1.12", "sensio/framework-extra-bundle": "<6.1", "symfony/error-handler": "<4.4.1", "jms/serializer-bundle": "<2.4.3|3.0.0", "jms/serializer": "<1.13.0" }, "minimum-stability": "dev", "extra": { "branch-alias": { "3.x-dev": "3.1-dev" } } }
Coded With 💗 by
0x6ick