Merge branch '3.4'

* 3.4:
  use the parseFile() method of the YAML parser
  [Yaml] support parsing files
  Adding Definition::addError() and a compiler pass to throw errors as exceptions
  [DI] Add AutowireRequiredMethodsPass to fix bindings for `@required` methods
  [Cache] Add ResettableInterface to allow resetting any pool's local state
  [DI][DX] Throw exception on some ContainerBuilder methods used from extensions
  added missing @author tag for new class
  allow forms without translations and validator
  [VarDumper] Make `dump()` a little bit more easier to use
  [Form] Add ambiguous & exception debug:form tests
  Reset the authentication token between requests.
  [Serializer] Getter for extra attributes in ExtraAttributesException
  [DI] Dont use JSON_BIGINT_AS_STRING

# Conflicts:
#	src/Symfony/Component/Routing/composer.json
#	src/Symfony/Component/Translation/composer.json
#	src/Symfony/Component/Yaml/Inline.php
#	src/Symfony/Component/Yaml/Tests/ParserTest.php
#	src/Symfony/Component/Yaml/Yaml.php
This commit is contained in:
Maxime Steinhausser 2017-09-26 08:12:47 +02:00
commit 3640585bfd
76 changed files with 1430 additions and 575 deletions

View File

@ -43,6 +43,7 @@ class CachePoolPass implements CompilerPassInterface
'provider',
'namespace',
'default_lifetime',
'reset',
);
foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) {
$adapter = $pool = $container->getDefinition($id);
@ -73,13 +74,19 @@ class CachePoolPass implements CompilerPassInterface
}
$i = 0;
foreach ($attributes as $attr) {
if (isset($tags[0][$attr]) && ('namespace' !== $attr || ArrayAdapter::class !== $adapter->getClass())) {
if (!isset($tags[0][$attr])) {
// no-op
} elseif ('reset' === $attr) {
if ($tags[0][$attr]) {
$pool->addTag('kernel.reset', array('method' => $tags[0][$attr]));
}
} elseif ('namespace' !== $attr || ArrayAdapter::class !== $adapter->getClass()) {
$pool->replaceArgument($i++, $tags[0][$attr]);
}
unset($tags[0][$attr]);
}
if (!empty($tags[0])) {
throw new InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "namespace" and "default_lifetime", found "%s".', $id, implode('", "', array_keys($tags[0]))));
throw new InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "namespace", "default_lifetime" and "reset", found "%s".', $id, implode('", "', array_keys($tags[0]))));
}
if (null !== $clearer) {

View File

@ -25,6 +25,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\DirectoryResource;
@ -72,6 +73,7 @@ use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\ObjectInitializerInterface;
use Symfony\Component\WebLink\HttpHeaderSerializer;
@ -146,15 +148,13 @@ class FrameworkExtension extends Extension
throw new LogicException('Translation support cannot be enabled as the Translation component is not installed.');
}
if (!class_exists('Symfony\Component\Translation\Translator') && $this->isConfigEnabled($container, $config['form'])) {
throw new LogicException('Form support cannot be enabled as the Translation component is not installed.');
}
if (!class_exists('Symfony\Component\Translation\Translator') && $this->isConfigEnabled($container, $config['validation'])) {
throw new LogicException('Validation support cannot be enabled as the Translation component is not installed.');
}
$loader->load('identity_translator.xml');
if (class_exists(Translator::class)) {
$loader->load('identity_translator.xml');
}
}
if (isset($config['secret'])) {
@ -200,7 +200,10 @@ class FrameworkExtension extends Extension
$config['validation']['enabled'] = true;
if (!class_exists('Symfony\Component\Validator\Validation')) {
throw new LogicException('The Validator component is required to use the Form component.');
$container->setParameter('validator.translation_domain', 'validators');
$container->removeDefinition('form.type_extension.form.validator');
$container->removeDefinition('form.type_guesser.validator');
}
} else {
$container->removeDefinition('Symfony\Component\Form\Command\DebugCommand');
@ -287,6 +290,8 @@ class FrameworkExtension extends Extension
->addTag('kernel.cache_warmer');
$container->registerForAutoconfiguration(EventSubscriberInterface::class)
->addTag('kernel.event_subscriber');
$container->registerForAutoconfiguration(ResettableInterface::class)
->addTag('kernel.reset', array('method' => 'reset'));
$container->registerForAutoconfiguration(PropertyListExtractorInterface::class)
->addTag('property_info.list_extractor');
$container->registerForAutoconfiguration(PropertyTypeExtractorInterface::class)
@ -342,6 +347,10 @@ class FrameworkExtension extends Extension
} else {
$container->setParameter('form.type_extension.csrf.enabled', false);
}
if (!class_exists(Translator::class)) {
$container->removeDefinition('form.type_extension.upload.validator');
}
}
/**

View File

@ -8,7 +8,7 @@
<defaults public="false" />
<service id="cache.app" parent="cache.adapter.filesystem" public="true">
<tag name="cache.pool" clearer="cache.app_clearer" />
<tag name="cache.pool" clearer="cache.app_clearer" reset="reset" />
</service>
<service id="cache.system" parent="cache.adapter.system" public="true">
@ -90,7 +90,7 @@
</service>
<service id="cache.adapter.memcached" class="Symfony\Component\Cache\Adapter\MemcachedAdapter" abstract="true">
<tag name="cache.pool" provider="cache.default_memcached_provider" clearer="cache.default_clearer" />
<tag name="cache.pool" provider="cache.default_memcached_provider" clearer="cache.default_clearer" reset="reset" />
<tag name="monolog.logger" channel="cache" />
<argument /> <!-- Memcached connection service -->
<argument /> <!-- namespace -->

View File

@ -12,7 +12,7 @@
<argument type="service" id="security.csrf.token_manager" />
<argument>%form.type_extension.csrf.enabled%</argument>
<argument>%form.type_extension.csrf.field_name%</argument>
<argument type="service" id="translator" />
<argument type="service" id="translator" on-invalid="null" />
<argument>%validator.translation_domain%</argument>
<argument type="service" id="form.server_params" />
</service>

View File

@ -21,7 +21,9 @@
</service>
<service id="Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface" alias="security.authorization_checker" />
<service id="security.token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage" public="true" />
<service id="security.token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage" public="true">
<tag name="kernel.reset" method="setToken" />
</service>
<service id="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface" alias="security.token_storage" />
<service id="security.user_value_resolver" class="Symfony\Bundle\SecurityBundle\SecurityUserValueResolver">

View File

@ -17,12 +17,13 @@ use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\AbstractTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface
{
use AbstractTrait;

View File

@ -14,12 +14,13 @@ namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Psr\Log\LoggerAwareInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ArrayTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ArrayAdapter implements AdapterInterface, LoggerAwareInterface
class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface
{
use ArrayTrait;

View File

@ -16,6 +16,7 @@ use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
/**
* Chains several adapters together.
@ -25,7 +26,7 @@ use Symfony\Component\Cache\PruneableInterface;
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ChainAdapter implements AdapterInterface, PruneableInterface
class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
private $adapters = array();
private $adapterCount;
@ -248,4 +249,16 @@ class ChainAdapter implements AdapterInterface, PruneableInterface
return $pruned;
}
/**
* {@inheritdoc}
*/
public function reset()
{
foreach ($this->adapters as $adapter) {
if ($adapter instanceof ResettableInterface) {
$adapter->reset();
}
}
}
}

View File

@ -15,6 +15,8 @@ use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\PhpArrayTrait;
/**
@ -24,7 +26,7 @@ use Symfony\Component\Cache\Traits\PhpArrayTrait;
* @author Titouan Galopin <galopintitouan@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class PhpArrayAdapter implements AdapterInterface
class PhpArrayAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
use PhpArrayTrait;
@ -37,7 +39,7 @@ class PhpArrayAdapter implements AdapterInterface
public function __construct($file, AdapterInterface $fallbackPool)
{
$this->file = $file;
$this->fallbackPool = $fallbackPool;
$this->pool = $fallbackPool;
$this->zendDetectUnicode = ini_get('zend.detect_unicode');
$this->createCacheItem = \Closure::bind(
function ($key, $value, $isHit) {
@ -87,7 +89,7 @@ class PhpArrayAdapter implements AdapterInterface
$this->initialize();
}
if (!isset($this->values[$key])) {
return $this->fallbackPool->getItem($key);
return $this->pool->getItem($key);
}
$value = $this->values[$key];
@ -142,7 +144,7 @@ class PhpArrayAdapter implements AdapterInterface
$this->initialize();
}
return isset($this->values[$key]) || $this->fallbackPool->hasItem($key);
return isset($this->values[$key]) || $this->pool->hasItem($key);
}
/**
@ -157,7 +159,7 @@ class PhpArrayAdapter implements AdapterInterface
$this->initialize();
}
return !isset($this->values[$key]) && $this->fallbackPool->deleteItem($key);
return !isset($this->values[$key]) && $this->pool->deleteItem($key);
}
/**
@ -184,7 +186,7 @@ class PhpArrayAdapter implements AdapterInterface
}
if ($fallbackKeys) {
$deleted = $this->fallbackPool->deleteItems($fallbackKeys) && $deleted;
$deleted = $this->pool->deleteItems($fallbackKeys) && $deleted;
}
return $deleted;
@ -199,7 +201,7 @@ class PhpArrayAdapter implements AdapterInterface
$this->initialize();
}
return !isset($this->values[$item->getKey()]) && $this->fallbackPool->save($item);
return !isset($this->values[$item->getKey()]) && $this->pool->save($item);
}
/**
@ -211,7 +213,7 @@ class PhpArrayAdapter implements AdapterInterface
$this->initialize();
}
return !isset($this->values[$item->getKey()]) && $this->fallbackPool->saveDeferred($item);
return !isset($this->values[$item->getKey()]) && $this->pool->saveDeferred($item);
}
/**
@ -219,7 +221,7 @@ class PhpArrayAdapter implements AdapterInterface
*/
public function commit()
{
return $this->fallbackPool->commit();
return $this->pool->commit();
}
/**
@ -257,7 +259,7 @@ class PhpArrayAdapter implements AdapterInterface
}
if ($fallbackKeys) {
foreach ($this->fallbackPool->getItems($fallbackKeys) as $key => $item) {
foreach ($this->pool->getItems($fallbackKeys) as $key => $item) {
yield $key => $item;
}
}

View File

@ -14,13 +14,17 @@ namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ProxyAdapter implements AdapterInterface
class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
private $pool;
use ProxyTrait;
private $namespace;
private $namespaceLen;
private $createCacheItem;

View File

@ -12,13 +12,17 @@
namespace Symfony\Component\Cache\Adapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class SimpleCacheAdapter extends AbstractAdapter
class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
{
private $pool;
use ProxyTrait;
private $miss;
public function __construct(CacheInterface $pool, $namespace = '', $defaultLifetime = 0)

View File

@ -15,26 +15,29 @@ use Psr\Cache\CacheItemInterface;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface
class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, ResettableInterface
{
const TAGS_PREFIX = "\0tags\0";
private $itemsAdapter;
use ProxyTrait;
private $deferred = array();
private $createCacheItem;
private $setCacheItemTags;
private $getTagsByKey;
private $invalidateTags;
private $tagsAdapter;
private $tagsPool;
public function __construct(AdapterInterface $itemsAdapter, AdapterInterface $tagsAdapter = null)
public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null)
{
$this->itemsAdapter = $itemsAdapter;
$this->tagsAdapter = $tagsAdapter ?: $itemsAdapter;
$this->pool = $itemsPool;
$this->tags = $tagsPool ?: $itemsPool;
$this->createCacheItem = \Closure::bind(
function ($key, $value, CacheItem $protoItem) {
$item = new CacheItem();
@ -110,7 +113,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface
}
$f = $this->invalidateTags;
return $f($this->tagsAdapter, $tags);
return $f($this->tags, $tags);
}
/**
@ -121,10 +124,10 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface
if ($this->deferred) {
$this->commit();
}
if (!$this->itemsAdapter->hasItem($key)) {
if (!$this->pool->hasItem($key)) {
return false;
}
if (!$itemTags = $this->itemsAdapter->getItem(static::TAGS_PREFIX.$key)->get()) {
if (!$itemTags = $this->pool->getItem(static::TAGS_PREFIX.$key)->get()) {
return true;
}
@ -165,9 +168,9 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface
}
try {
$items = $this->itemsAdapter->getItems($tagKeys + $keys);
$items = $this->pool->getItems($tagKeys + $keys);
} catch (InvalidArgumentException $e) {
$this->itemsAdapter->getItems($keys); // Should throw an exception
$this->pool->getItems($keys); // Should throw an exception
throw $e;
}
@ -182,7 +185,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface
{
$this->deferred = array();
return $this->itemsAdapter->clear();
return $this->pool->clear();
}
/**
@ -204,7 +207,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface
}
}
return $this->itemsAdapter->deleteItems($keys);
return $this->pool->deleteItems($keys);
}
/**
@ -243,7 +246,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface
if ($this->deferred) {
$items = $this->deferred;
foreach ($items as $key => $item) {
if (!$this->itemsAdapter->saveDeferred($item)) {
if (!$this->pool->saveDeferred($item)) {
unset($this->deferred[$key]);
$ok = false;
}
@ -257,17 +260,17 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface
foreach ($tagsByKey as $key => $tags) {
if ($tags) {
$this->itemsAdapter->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
$this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
} else {
$deletedTags[] = static::TAGS_PREFIX.$key;
}
}
if ($deletedTags) {
$this->itemsAdapter->deleteItems($deletedTags);
$this->pool->deleteItems($deletedTags);
}
}
return $this->itemsAdapter->commit() && $ok;
return $this->pool->commit() && $ok;
}
public function __destruct()
@ -328,23 +331,11 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface
$tagVersions[$tag] = $tag.static::TAGS_PREFIX;
$tags[$tag.static::TAGS_PREFIX] = $tag;
}
foreach ($this->tagsAdapter->getItems($tagVersions) as $tag => $version) {
foreach ($this->tags->getItems($tagVersions) as $tag => $version) {
$tagVersions[$tags[$tag]] = $version->get() ?: 0;
}
}
return $tagVersions;
}
/**
* {@inheritdoc}
*/
public function prune()
{
if ($this->itemsAdapter instanceof PruneableInterface) {
return $this->itemsAdapter->prune();
}
return false;
}
}

View File

@ -12,6 +12,8 @@
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
/**
* An adapter that collects data about all cache calls.
@ -20,7 +22,7 @@ use Psr\Cache\CacheItemInterface;
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class TraceableAdapter implements AdapterInterface
class TraceableAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
protected $pool;
private $calls = array();
@ -168,6 +170,38 @@ class TraceableAdapter implements AdapterInterface
}
}
/**
* {@inheritdoc}
*/
public function prune()
{
if (!$this->pool instanceof PruneableInterface) {
return false;
}
$event = $this->start(__FUNCTION__);
try {
return $event->result = $this->pool->prune();
} finally {
$event->end = microtime(true);
}
}
/**
* {@inheritdoc}
*/
public function reset()
{
if (!$this->pool instanceof ResettableInterface) {
return;
}
$event = $this->start(__FUNCTION__);
try {
$this->pool->reset();
} finally {
$event->end = microtime(true);
}
}
public function getCalls()
{
try {

View File

@ -17,7 +17,7 @@ use Psr\Cache\CacheItemPoolInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class DoctrineProvider extends CacheProvider
class DoctrineProvider extends CacheProvider implements PruneableInterface, ResettableInterface
{
private $pool;
@ -26,6 +26,25 @@ class DoctrineProvider extends CacheProvider
$this->pool = $pool;
}
/**
* {@inheritdoc}
*/
public function prune()
{
return $this->pool instanceof PruneableInterface && $this->pool->prune();
}
/**
* {@inheritdoc}
*/
public function reset()
{
if ($this->pool instanceof ResettableInterface) {
$this->pool->reset();
}
$this->setNamespace($this->getNamespace());
}
/**
* {@inheritdoc}
*/

View File

@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache;
/**
* Resets a pool's local state.
*/
interface ResettableInterface
{
public function reset();
}

View File

@ -16,11 +16,12 @@ use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Traits\AbstractTrait;
use Symfony\Component\Cache\ResettableInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractCache implements CacheInterface, LoggerAwareInterface
abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, ResettableInterface
{
use AbstractTrait {
deleteItems as private;

View File

@ -15,12 +15,13 @@ use Psr\Log\LoggerAwareInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ArrayTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ArrayCache implements CacheInterface, LoggerAwareInterface
class ArrayCache implements CacheInterface, LoggerAwareInterface, ResettableInterface
{
use ArrayTrait {
ArrayTrait::deleteItem as delete;

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Cache\Simple;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
/**
* Chains several caches together.
@ -23,7 +24,7 @@ use Symfony\Component\Cache\PruneableInterface;
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ChainCache implements CacheInterface, PruneableInterface
class ChainCache implements CacheInterface, PruneableInterface, ResettableInterface
{
private $miss;
private $caches = array();
@ -236,4 +237,16 @@ class ChainCache implements CacheInterface, PruneableInterface
return $pruned;
}
/**
* {@inheritdoc}
*/
public function reset()
{
foreach ($this->caches as $cache) {
if ($cache instanceof ResettableInterface) {
$cache->reset();
}
}
}
}

View File

@ -14,6 +14,8 @@ namespace Symfony\Component\Cache\Simple;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Traits\PhpArrayTrait;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
/**
* Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
@ -22,7 +24,7 @@ use Symfony\Component\Cache\Traits\PhpArrayTrait;
* @author Titouan Galopin <galopintitouan@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class PhpArrayCache implements CacheInterface
class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInterface
{
use PhpArrayTrait;
@ -33,7 +35,7 @@ class PhpArrayCache implements CacheInterface
public function __construct($file, CacheInterface $fallbackPool)
{
$this->file = $file;
$this->fallbackPool = $fallbackPool;
$this->pool = $fallbackPool;
$this->zendDetectUnicode = ini_get('zend.detect_unicode');
}
@ -66,7 +68,7 @@ class PhpArrayCache implements CacheInterface
$this->initialize();
}
if (!isset($this->values[$key])) {
return $this->fallbackPool->get($key, $default);
return $this->pool->get($key, $default);
}
$value = $this->values[$key];
@ -122,7 +124,7 @@ class PhpArrayCache implements CacheInterface
$this->initialize();
}
return isset($this->values[$key]) || $this->fallbackPool->has($key);
return isset($this->values[$key]) || $this->pool->has($key);
}
/**
@ -137,7 +139,7 @@ class PhpArrayCache implements CacheInterface
$this->initialize();
}
return !isset($this->values[$key]) && $this->fallbackPool->delete($key);
return !isset($this->values[$key]) && $this->pool->delete($key);
}
/**
@ -168,7 +170,7 @@ class PhpArrayCache implements CacheInterface
}
if ($fallbackKeys) {
$deleted = $this->fallbackPool->deleteMultiple($fallbackKeys) && $deleted;
$deleted = $this->pool->deleteMultiple($fallbackKeys) && $deleted;
}
return $deleted;
@ -186,7 +188,7 @@ class PhpArrayCache implements CacheInterface
$this->initialize();
}
return !isset($this->values[$key]) && $this->fallbackPool->set($key, $value, $ttl);
return !isset($this->values[$key]) && $this->pool->set($key, $value, $ttl);
}
/**
@ -214,7 +216,7 @@ class PhpArrayCache implements CacheInterface
}
if ($fallbackValues) {
$saved = $this->fallbackPool->setMultiple($fallbackValues, $ttl) && $saved;
$saved = $this->pool->setMultiple($fallbackValues, $ttl) && $saved;
}
return $saved;
@ -247,7 +249,7 @@ class PhpArrayCache implements CacheInterface
}
if ($fallbackKeys) {
foreach ($this->fallbackPool->getMultiple($fallbackKeys, $default) as $key => $item) {
foreach ($this->pool->getMultiple($fallbackKeys, $default) as $key => $item) {
yield $key => $item;
}
}

View File

@ -18,13 +18,17 @@ use Psr\SimpleCache\CacheException as SimpleCacheException;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class Psr6Cache implements CacheInterface
class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterface
{
private $pool;
use ProxyTrait;
private $createCacheItem;
public function __construct(CacheItemPoolInterface $pool)

View File

@ -12,13 +12,15 @@
namespace Symfony\Component\Cache\Simple;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
/**
* An adapter that collects data about all cache calls.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class TraceableCache implements CacheInterface
class TraceableCache implements CacheInterface, PruneableInterface, ResettableInterface
{
private $pool;
private $miss;
@ -177,6 +179,38 @@ class TraceableCache implements CacheInterface
}
}
/**
* {@inheritdoc}
*/
public function prune()
{
if (!$this->pool instanceof PruneableInterface) {
return false;
}
$event = $this->start(__FUNCTION__);
try {
return $event->result = $this->pool->prune();
} finally {
$event->end = microtime(true);
}
}
/**
* {@inheritdoc}
*/
public function reset()
{
if (!$this->pool instanceof ResettableInterface) {
return;
}
$event = $this->start(__FUNCTION__);
try {
$this->pool->reset();
} finally {
$event->end = microtime(true);
}
}
public function getCalls()
{
try {

View File

@ -50,6 +50,7 @@ class PhpArrayAdapterTest extends AdapterTestCase
'testDeleteItemsInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
'testDefaultLifeTime' => 'PhpArrayAdapter does not allow configuring a default lifetime.',
'testPrune' => 'PhpArrayAdapter just proxies',
);
protected static $file;

View File

@ -25,6 +25,7 @@ class PhpArrayAdapterWithFallbackTest extends AdapterTestCase
'testHasItemInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
'testDeleteItemInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
'testDeleteItemsInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
'testPrune' => 'PhpArrayAdapter just proxies',
);
protected static $file;

View File

@ -24,6 +24,7 @@ class ProxyAdapterTest extends AdapterTestCase
protected $skippedTests = array(
'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.',
'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayAdapter is not.',
'testPrune' => 'ProxyAdapter just proxies',
);
public function createCachePool($defaultLifetime = 0)

View File

@ -19,6 +19,10 @@ use Symfony\Component\Cache\Adapter\SimpleCacheAdapter;
*/
class SimpleCacheAdapterTest extends AdapterTestCase
{
protected $skippedTests = array(
'testPrune' => 'SimpleCache just proxies',
);
public function createCachePool($defaultLifetime = 0)
{
return new SimpleCacheAdapter(new FilesystemCache(), '', $defaultLifetime);

View File

@ -19,6 +19,10 @@ use Symfony\Component\Cache\Adapter\TraceableAdapter;
*/
class TraceableAdapterTest extends AdapterTestCase
{
protected $skippedTests = array(
'testPrune' => 'TraceableAdapter just proxies',
);
public function createCachePool($defaultLifetime = 0)
{
return new TraceableAdapter(new FilesystemAdapter('', $defaultLifetime));

View File

@ -44,6 +44,7 @@ class PhpArrayCacheTest extends CacheTestCase
'testSetValidData' => 'PhpArrayCache does no validation',
'testDefaultLifeTime' => 'PhpArrayCache does not allow configuring a default lifetime.',
'testPrune' => 'PhpArrayCache just proxies',
);
protected static $file;

View File

@ -31,6 +31,7 @@ class PhpArrayCacheWithFallbackTest extends CacheTestCase
'testSetMultipleInvalidKeys' => 'PhpArrayCache does no validation',
'testSetMultipleInvalidTtl' => 'PhpArrayCache does no validation',
'testHasInvalidKeys' => 'PhpArrayCache does no validation',
'testPrune' => 'PhpArrayCache just proxies',
);
protected static $file;

View File

@ -19,6 +19,10 @@ use Symfony\Component\Cache\Simple\Psr6Cache;
*/
class Psr6CacheTest extends CacheTestCase
{
protected $skippedTests = array(
'testPrune' => 'Psr6Cache just proxies',
);
public function createSimpleCache($defaultLifetime = 0)
{
return new Psr6Cache(new FilesystemAdapter('', $defaultLifetime));

View File

@ -19,6 +19,10 @@ use Symfony\Component\Cache\Simple\TraceableCache;
*/
class TraceableCacheTest extends CacheTestCase
{
protected $skippedTests = array(
'testPrune' => 'TraceableCache just proxies',
);
public function createSimpleCache($defaultLifetime = 0)
{
return new TraceableCache(new FilesystemCache('', $defaultLifetime));

View File

@ -189,6 +189,17 @@ trait AbstractTrait
return $wasEnabled;
}
/**
* {@inheritdoc}
*/
public function reset()
{
if ($this->deferred) {
$this->commit();
}
$this->namespaceVersion = '';
}
/**
* Like the native unserialize() function but throws an exception if anything goes wrong.
*

View File

@ -69,6 +69,14 @@ trait ArrayTrait
return true;
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->clear();
}
private function generateItems(array $keys, $now, $f)
{
foreach ($keys as $i => $key) {

View File

@ -20,6 +20,15 @@ trait DoctrineTrait
{
private $provider;
/**
* {@inheritdoc}
*/
public function reset()
{
parent::reset();
$this->provider->setNamespace($this->provider->getNamespace());
}
/**
* {@inheritdoc}
*/

View File

@ -22,9 +22,10 @@ use Symfony\Component\Cache\Exception\InvalidArgumentException;
*/
trait PhpArrayTrait
{
use ProxyTrait;
private $file;
private $values;
private $fallbackPool;
private $zendDetectUnicode;
/**
@ -119,7 +120,7 @@ EOF;
$cleared = @unlink($this->file) || !file_exists($this->file);
return $this->fallbackPool->clear() && $cleared;
return $this->pool->clear() && $cleared;
}
/**

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Traits;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
trait ProxyTrait
{
private $pool;
/**
* {@inheritdoc}
*/
public function prune()
{
return $this->pool instanceof PruneableInterface && $this->pool->prune();
}
/**
* {@inheritdoc}
*/
public function reset()
{
if ($this->pool instanceof ResettableInterface) {
$this->pool->reset();
}
}
}

View File

@ -22,6 +22,9 @@ use Symfony\Component\DependencyInjection\Reference;
*/
abstract class AbstractRecursivePass implements CompilerPassInterface
{
/**
* @var ContainerBuilder
*/
protected $container;
protected $currentId;
@ -90,7 +93,7 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
{
if (is_string($factory = $definition->getFactory())) {
if (!function_exists($factory)) {
throw new RuntimeException(sprintf('Unable to resolve service "%s": function "%s" does not exist.', $this->currentId, $factory));
throw new RuntimeException(sprintf('Invalid service "%s": function "%s" does not exist.', $this->currentId, $factory));
}
$r = new \ReflectionFunction($factory);
if (false !== $r->getFileName() && file_exists($r->getFileName())) {
@ -108,7 +111,7 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
$class = $definition->getClass();
}
if ('__construct' === $method) {
throw new RuntimeException(sprintf('Unable to resolve service "%s": "__construct()" cannot be used as a factory method.', $this->currentId));
throw new RuntimeException(sprintf('Invalid service "%s": "__construct()" cannot be used as a factory method.', $this->currentId));
}
return $this->getReflectionMethod(new Definition($class), $method);
@ -117,14 +120,14 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
$class = $definition->getClass();
if (!$r = $this->container->getReflectionClass($class)) {
throw new RuntimeException(sprintf('Unable to resolve service "%s": class "%s" does not exist.', $this->currentId, $class));
throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class));
}
if (!$r = $r->getConstructor()) {
if ($required) {
throw new RuntimeException(sprintf('Unable to resolve service "%s": class%s has no constructor.', $this->currentId, sprintf($class !== $this->currentId ? ' "%s"' : '', $class)));
throw new RuntimeException(sprintf('Invalid service "%s": class%s has no constructor.', $this->currentId, sprintf($class !== $this->currentId ? ' "%s"' : '', $class)));
}
} elseif (!$r->isPublic()) {
throw new RuntimeException(sprintf('Unable to resolve service "%s": %s must be public.', $this->currentId, sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class)));
throw new RuntimeException(sprintf('Invalid service "%s": %s must be public.', $this->currentId, sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class)));
}
return $r;
@ -145,20 +148,20 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
}
if (!$class = $definition->getClass()) {
throw new RuntimeException(sprintf('Unable to resolve service "%s": the class is not set.', $this->currentId));
throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId));
}
if (!$r = $this->container->getReflectionClass($class)) {
throw new RuntimeException(sprintf('Unable to resolve service "%s": class "%s" does not exist.', $this->currentId, $class));
throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class));
}
if (!$r->hasMethod($method)) {
throw new RuntimeException(sprintf('Unable to resolve service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
}
$r = $r->getMethod($method);
if (!$r->isPublic()) {
throw new RuntimeException(sprintf('Unable to resolve service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
}
return $r;

View File

@ -11,11 +11,15 @@
namespace Symfony\Component\DependencyInjection\Compiler;
@trigger_error('The '.__NAMESPACE__.'\AutowireExceptionPass class is deprecated since version 3.4 and will be removed in 4.0. Use the DefinitionErrorExceptionPass class instead.', E_USER_DEPRECATED);
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Throws autowire exceptions from AutowirePass for definitions that still exist.
*
* @deprecated since version 3.4, will be removed in 4.0.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
class AutowireExceptionPass implements CompilerPassInterface

View File

@ -34,7 +34,7 @@ class AutowirePass extends AbstractRecursivePass
private $autowiringExceptions = array();
/**
* @param bool $throwOnAutowireException If false, retrieved errors via getAutowiringExceptions
* @param bool $throwOnAutowireException Errors can be retrieved via Definition::getErrors()
*/
public function __construct($throwOnAutowireException = true)
{
@ -42,10 +42,14 @@ class AutowirePass extends AbstractRecursivePass
}
/**
* @deprecated since version 3.4, to be removed in 4.0.
*
* @return AutowiringFailedException[]
*/
public function getAutowiringExceptions()
{
@trigger_error('Calling AutowirePass::getAutowiringExceptions() is deprecated since Symfony 3.4 and will be removed in 4.0. Use Definition::getErrors() instead.', E_USER_DEPRECATED);
return $this->autowiringExceptions;
}
@ -79,6 +83,7 @@ class AutowirePass extends AbstractRecursivePass
}
$this->autowiringExceptions[] = $e;
$this->container->getDefinition($this->currentId)->addError($e->getMessage());
return parent::processValue($value, $isRoot);
}
@ -103,7 +108,6 @@ class AutowirePass extends AbstractRecursivePass
return $value;
}
$autowiredMethods = $this->getMethodsToAutowire($reflectionClass);
$methodCalls = $value->getMethodCalls();
try {
@ -116,7 +120,7 @@ class AutowirePass extends AbstractRecursivePass
array_unshift($methodCalls, array($constructor, $value->getArguments()));
}
$methodCalls = $this->autowireCalls($reflectionClass, $methodCalls, $autowiredMethods);
$methodCalls = $this->autowireCalls($reflectionClass, $methodCalls);
if ($constructor) {
list(, $arguments) = array_shift($methodCalls);
@ -134,61 +138,18 @@ class AutowirePass extends AbstractRecursivePass
}
/**
* Gets the list of methods to autowire.
*
* @param \ReflectionClass $reflectionClass
*
* @return \ReflectionMethod[]
*/
private function getMethodsToAutowire(\ReflectionClass $reflectionClass)
{
$methodsToAutowire = array();
foreach ($reflectionClass->getMethods() as $reflectionMethod) {
$r = $reflectionMethod;
if ($r->isConstructor()) {
continue;
}
while (true) {
if (false !== $doc = $r->getDocComment()) {
if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
$methodsToAutowire[strtolower($reflectionMethod->name)] = $reflectionMethod;
break;
}
if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) {
break;
}
}
try {
$r = $r->getPrototype();
} catch (\ReflectionException $e) {
break; // method has no prototype
}
}
}
return $methodsToAutowire;
}
/**
* @param \ReflectionClass $reflectionClass
* @param array $methodCalls
* @param \ReflectionMethod[] $autowiredMethods
* @param array $methodCalls
*
* @return array
*/
private function autowireCalls(\ReflectionClass $reflectionClass, array $methodCalls, array $autowiredMethods)
private function autowireCalls(\ReflectionClass $reflectionClass, array $methodCalls)
{
foreach ($methodCalls as $i => $call) {
list($method, $arguments) = $call;
if ($method instanceof \ReflectionFunctionAbstract) {
$reflectionMethod = $method;
} elseif (isset($autowiredMethods[$lcMethod = strtolower($method)]) && $autowiredMethods[$lcMethod]->isPublic()) {
$reflectionMethod = $autowiredMethods[$lcMethod];
unset($autowiredMethods[$lcMethod]);
} else {
$reflectionMethod = $this->getReflectionMethod(new Definition($reflectionClass->name), $method);
}
@ -200,16 +161,6 @@ class AutowirePass extends AbstractRecursivePass
}
}
foreach ($autowiredMethods as $lcMethod => $reflectionMethod) {
$method = $reflectionMethod->name;
if (!$reflectionMethod->isPublic()) {
$class = $reflectionClass->name;
throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
}
$methodCalls[] = array($method, $this->autowireMethod($reflectionMethod, array()));
}
return $methodCalls;
}

View File

@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
/**
* Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class AutowireRequiredMethodsPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, $isRoot = false)
{
$value = parent::processValue($value, $isRoot);
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
return $value;
}
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
return $value;
}
$alreadyCalledMethods = array();
foreach ($value->getMethodCalls() as list($method)) {
$alreadyCalledMethods[strtolower($method)] = true;
}
foreach ($reflectionClass->getMethods() as $reflectionMethod) {
$r = $reflectionMethod;
if ($r->isConstructor() || isset($alreadyCalledMethods[strtolower($r->name)])) {
continue;
}
while (true) {
if (false !== $doc = $r->getDocComment()) {
if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
$value->addMethodCall($reflectionMethod->name);
break;
}
if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) {
break;
}
}
try {
$r = $r->getPrototype();
} catch (\ReflectionException $e) {
break; // method has no prototype
}
}
}
return $value;
}
}

View File

@ -22,6 +22,13 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException;
*/
class CheckArgumentsValidityPass extends AbstractRecursivePass
{
private $throwExceptions;
public function __construct($throwExceptions = true)
{
$this->throwExceptions = $throwExceptions;
}
/**
* {@inheritdoc}
*/
@ -35,10 +42,20 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass
foreach ($value->getArguments() as $k => $v) {
if ($k !== $i++) {
if (!is_int($k)) {
throw new RuntimeException(sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k));
$msg = sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
break;
}
throw new RuntimeException(sprintf('Invalid constructor argument %d for service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $this->currentId, $i));
$msg = sprintf('Invalid constructor argument %d for service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $this->currentId, $i);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
}
}
@ -47,10 +64,20 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass
foreach ($methodCall[1] as $k => $v) {
if ($k !== $i++) {
if (!is_int($k)) {
throw new RuntimeException(sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k));
$msg = sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
break;
}
throw new RuntimeException(sprintf('Invalid argument %d for method call "%s" of service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $methodCall[0], $this->currentId, $i));
$msg = sprintf('Invalid argument %d for method call "%s" of service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $methodCall[0], $this->currentId, $i);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
}
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* Throws an exception for any Definitions that have errors and still exist.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
class DefinitionErrorExceptionPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, $isRoot = false)
{
if (!$value instanceof Definition || empty($value->getErrors())) {
return parent::processValue($value, $isRoot);
}
// only show the first error so they user can focus on it
$errors = $value->getErrors();
$message = reset($errors);
throw new RuntimeException($message);
}
}

View File

@ -38,10 +38,14 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements Repe
*
* The key is the inlined service id and its value is the list of services it was inlined into.
*
* @deprecated since version 3.4, to be removed in 4.0.
*
* @return array
*/
public function getInlinedServiceIds()
{
@trigger_error('Calling InlineServiceDefinitionsPass::getInlinedServiceIds() is deprecated since Symfony 3.4 and will be removed in 4.0.', E_USER_DEPRECATED);
return $this->inlinedServiceIds;
}

View File

@ -12,10 +12,13 @@
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/**
* Merges extension configs into the container builder.
@ -52,7 +55,7 @@ class MergeExtensionConfigurationPass implements CompilerPassInterface
}
$config = $resolvingBag->resolveValue($config);
$tmpContainer = new ContainerBuilder($resolvingBag);
$tmpContainer = new MergeExtensionConfigurationContainerBuilder($extension, $resolvingBag);
$tmpContainer->setResourceTracking($container->isTrackingResources());
$tmpContainer->addObjectResource($extension);
if ($extension instanceof ConfigurationExtensionInterface && null !== $configuration = $extension->getConfiguration($config, $tmpContainer)) {
@ -121,3 +124,44 @@ class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag
return null !== $this->processedEnvPlaceholders ? $this->processedEnvPlaceholders : parent::getEnvPlaceholders();
}
}
/**
* A container builder preventing using methods that wouldn't have any effect from extensions.
*
* @internal
*/
class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder
{
private $extensionClass;
public function __construct(ExtensionInterface $extension, ParameterBagInterface $parameterBag = null)
{
parent::__construct($parameterBag);
$this->extensionClass = get_class($extension);
}
/**
* {@inheritdoc}
*/
public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION/*, int $priority = 0*/)
{
throw new LogicException(sprintf('You cannot add compiler pass "%s" from extension "%s". Compiler passes must be registered before the container is compiled.', get_class($pass), $this->extensionClass));
}
/**
* {@inheritdoc}
*/
public function registerExtension(ExtensionInterface $extension)
{
throw new LogicException(sprintf('You cannot register extension "%s" from "%s". Extensions must be registered before the container is compiled.', get_class($extension), $this->extensionClass));
}
/**
* {@inheritdoc}
*/
public function compile($resolveEnvPlaceholders = false)
{
throw new LogicException(sprintf('Cannot compile the container in extension "%s".', $this->extensionClass));
}
}

View File

@ -57,15 +57,16 @@ class PassConfig
new CheckDefinitionValidityPass(),
new RegisterServiceSubscribersPass(),
new ResolveNamedArgumentsPass(),
new AutowireRequiredMethodsPass(),
new ResolveBindingsPass(),
$autowirePass = new AutowirePass(false),
new AutowirePass(false),
new ResolveServiceSubscribersPass(),
new ResolveReferencesToAliasesPass(),
new ResolveInvalidReferencesPass(),
new AnalyzeServiceReferencesPass(true),
new CheckCircularReferencesPass(),
new CheckReferenceValidityPass(),
new CheckArgumentsValidityPass(),
new CheckArgumentsValidityPass(false),
));
$this->removingPasses = array(array(
@ -74,11 +75,11 @@ class PassConfig
new RemoveAbstractDefinitionsPass(),
new RepeatedPass(array(
new AnalyzeServiceReferencesPass(),
$inlinedServicePass = new InlineServiceDefinitionsPass(),
new InlineServiceDefinitionsPass(),
new AnalyzeServiceReferencesPass(),
new RemoveUnusedDefinitionsPass(),
)),
new AutowireExceptionPass($autowirePass, $inlinedServicePass),
new DefinitionErrorExceptionPass(),
new CheckExceptionOnInvalidReferenceBehaviorPass(),
));
}

View File

@ -61,11 +61,11 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass
}
}
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": method "%s()" has no argument named "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key));
throw new InvalidArgumentException(sprintf('Invalid service "%s": method "%s()" has no argument named "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key));
}
if (null !== $argument && !$argument instanceof Reference && !$argument instanceof Definition) {
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": the value of argument "%s" of method "%s()" must be null, an instance of %s or an instance of %s, %s given.', $this->currentId, $key, $class !== $this->currentId ? $class.'::'.$method : $method, Reference::class, Definition::class, gettype($argument)));
throw new InvalidArgumentException(sprintf('Invalid service "%s": the value of argument "%s" of method "%s()" must be null, an instance of %s or an instance of %s, %s given.', $this->currentId, $key, $class !== $this->currentId ? $class.'::'.$method : $method, Reference::class, Definition::class, gettype($argument)));
}
foreach ($parameters as $j => $p) {
@ -76,7 +76,7 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass
}
}
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": method "%s()" has no argument type-hinted as "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key));
throw new InvalidArgumentException(sprintf('Invalid service "%s": method "%s()" has no argument type-hinted as "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key));
}
if ($resolvedArguments !== $call[1]) {

View File

@ -43,6 +43,7 @@ class Definition
private $autowired = false;
private $changes = array();
private $bindings = array();
private $errors = array();
protected $arguments = array();
@ -868,4 +869,24 @@ class Definition
return $this;
}
/**
* Add an error that occurred when building this Definition.
*
* @param string $error
*/
public function addError($error)
{
$this->errors[] = $error;
}
/**
* Returns any errors that occurred while building this Definition.
*
* @return array
*/
public function getErrors()
{
return $this->errors;
}
}

View File

@ -15,6 +15,9 @@ use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class EnvVarProcessor implements EnvVarProcessorInterface
{
private $container;
@ -119,7 +122,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface
}
if ('json' === $prefix) {
$env = json_decode($env, true, JSON_BIGINT_AS_STRING);
$env = json_decode($env, true);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new RuntimeException(sprintf('Invalid JSON in env var "%s": '.json_last_error_msg(), $name));

View File

@ -636,7 +636,7 @@ class YamlFileLoader extends FileLoader
}
try {
$configuration = $this->yamlParser->parse(file_get_contents($file), Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS);
$configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS);
} catch (ParseException $e) {
throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $file), 0, $e);
}

View File

@ -18,6 +18,9 @@ use Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
/**
* @group legacy
*/
class AutowireExceptionPassTest extends TestCase
{
public function testThrowsException()

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass;
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -20,6 +21,8 @@ use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic;
use Symfony\Component\DependencyInjection\TypedReference;
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
@ -128,6 +131,9 @@ class AutowirePassTest extends TestCase
$this->assertEquals(DInterface::class, (string) $container->getDefinition('h')->getArgument(1));
}
/**
* @group legacy
*/
public function testExceptionsAreStored()
{
$container = new ContainerBuilder();
@ -145,7 +151,7 @@ class AutowirePassTest extends TestCase
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Unable to resolve service "private_service": constructor of class "Symfony\Component\DependencyInjection\Tests\Compiler\PrivateConstructor" must be public.
* @expectedExceptionMessage Invalid service "private_service": constructor of class "Symfony\Component\DependencyInjection\Tests\Compiler\PrivateConstructor" must be public.
*/
public function testPrivateConstructorThrowsAutowireException()
{
@ -523,6 +529,7 @@ class AutowirePassTest extends TestCase
;
(new ResolveClassPass())->process($container);
(new AutowireRequiredMethodsPass())->process($container);
(new AutowirePass())->process($container);
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
@ -559,6 +566,7 @@ class AutowirePassTest extends TestCase
;
(new ResolveClassPass())->process($container);
(new AutowireRequiredMethodsPass())->process($container);
(new AutowirePass())->process($container);
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
@ -630,6 +638,8 @@ class AutowirePassTest extends TestCase
$aDefinition = $container->register('setter_injection_collision', SetterInjectionCollision::class);
$aDefinition->setAutowired(true);
(new AutowireRequiredMethodsPass())->process($container);
$pass = new AutowirePass();
$pass->process($container);
}
@ -711,6 +721,7 @@ class AutowirePassTest extends TestCase
}
(new ResolveClassPass())->process($container);
(new AutowireRequiredMethodsPass())->process($container);
(new AutowirePass())->process($container);
}
@ -719,7 +730,7 @@ class AutowirePassTest extends TestCase
return array(
array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class cannot be loaded.'),
array('setDifferentNamespace', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setDifferentNamespace()" references class "stdClass" but no such service exists. It cannot be auto-registered because it is from a different root namespace.'),
array(null, 'Cannot autowire service "foo": method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setProtectedMethod()" must be public.'),
array(null, 'Invalid service "foo": method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setProtectedMethod()" must be public.'),
);
}
@ -779,333 +790,3 @@ class AutowirePassTest extends TestCase
$pass->process($container);
}
}
class Foo
{
}
class Bar
{
public function __construct(Foo $foo)
{
}
}
interface AInterface
{
}
class A implements AInterface
{
public static function create(Foo $foo)
{
}
}
class B extends A
{
}
class C
{
public function __construct(A $a)
{
}
}
interface DInterface
{
}
interface EInterface extends DInterface
{
}
interface IInterface
{
}
class I implements IInterface
{
}
class F extends I implements EInterface
{
}
class G
{
public function __construct(DInterface $d, EInterface $e, IInterface $i)
{
}
}
class H
{
public function __construct(B $b, DInterface $d)
{
}
}
class D
{
public function __construct(A $a, DInterface $d)
{
}
}
class E
{
public function __construct(D $d = null)
{
}
}
class J
{
public function __construct(I $i)
{
}
}
interface CollisionInterface
{
}
class CollisionA implements CollisionInterface
{
}
class CollisionB implements CollisionInterface
{
}
class CannotBeAutowired
{
public function __construct(CollisionInterface $collision)
{
}
}
class CannotBeAutowiredForwardOrder
{
public function __construct(CollisionA $a, CollisionInterface $b, CollisionB $c)
{
}
}
class CannotBeAutowiredReverseOrder
{
public function __construct(CollisionA $a, CollisionB $c, CollisionInterface $b)
{
}
}
class Lille
{
}
class Dunglas
{
public function __construct(Lille $l)
{
}
}
class LesTilleuls
{
public function __construct(Dunglas $j, Dunglas $k)
{
}
}
class OptionalParameter
{
public function __construct(CollisionInterface $c = null, A $a, Foo $f = null)
{
}
}
class BadTypeHintedArgument
{
public function __construct(Dunglas $k, NotARealClass $r)
{
}
}
class BadParentTypeHintedArgument
{
public function __construct(Dunglas $k, OptionalServiceClass $r)
{
}
}
class NotGuessableArgument
{
public function __construct(Foo $k)
{
}
}
class NotGuessableArgumentForSubclass
{
public function __construct(A $k)
{
}
}
class MultipleArguments
{
public function __construct(A $k, $foo, Dunglas $dunglas)
{
}
}
class MultipleArgumentsOptionalScalar
{
public function __construct(A $a, $foo = 'default_val', Lille $lille = null)
{
}
}
class MultipleArgumentsOptionalScalarLast
{
public function __construct(A $a, Lille $lille, $foo = 'some_val')
{
}
}
class MultipleArgumentsOptionalScalarNotReallyOptional
{
public function __construct(A $a, $foo = 'default_val', Lille $lille)
{
}
}
/*
* Classes used for testing createResourceForClass
*/
class ClassForResource
{
public function __construct($foo, Bar $bar = null)
{
}
public function setBar(Bar $bar)
{
}
}
class IdenticalClassResource extends ClassForResource
{
}
class ClassChangedConstructorArgs extends ClassForResource
{
public function __construct($foo, Bar $bar, $baz)
{
}
}
class SetterInjection extends SetterInjectionParent
{
/**
* @required
*/
public function setFoo(Foo $foo)
{
// should be called
}
/** @inheritdoc*/
public function setDependencies(Foo $foo, A $a)
{
// should be called
}
/** {@inheritdoc} */
public function setWithCallsConfigured(A $a)
{
// this method has a calls configured on it
}
public function notASetter(A $a)
{
// should be called only when explicitly specified
}
/**
* @required*/
public function setChildMethodWithoutDocBlock(A $a)
{
}
}
class SetterInjectionParent
{
/** @required*/
public function setDependencies(Foo $foo, A $a)
{
// should be called
}
public function notASetter(A $a)
{
// @required should be ignored when the child does not add @inheritdoc
}
/** @required <tab> prefix is on purpose */
public function setWithCallsConfigured(A $a)
{
}
/** @required */
public function setChildMethodWithoutDocBlock(A $a)
{
}
}
class SetterInjectionCollision
{
/**
* @required
*/
public function setMultipleInstancesForOneArg(CollisionInterface $collision)
{
// The CollisionInterface cannot be autowired - there are multiple
// should throw an exception
}
}
class NotWireable
{
public function setNotAutowireable(NotARealClass $n)
{
}
public function setBar()
{
}
public function setOptionalNotAutowireable(NotARealClass $n = null)
{
}
public function setDifferentNamespace(\stdClass $n)
{
}
public function setOptionalNoTypeHint($foo = null)
{
}
public function setOptionalArgNoAutowireable($other = 'default_val')
{
}
/** @required */
protected function setProtectedMethod(A $a)
{
}
}
class PrivateConstructor
{
private function __construct()
{
}
}

View File

@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
class AutowireRequiredMethodsPassTest extends TestCase
{
public function testSetterInjection()
{
$container = new ContainerBuilder();
$container->register(Foo::class);
$container->register(A::class);
$container->register(CollisionA::class);
$container->register(CollisionB::class);
// manually configure *one* call, to override autowiring
$container
->register('setter_injection', SetterInjection::class)
->setAutowired(true)
->addMethodCall('setWithCallsConfigured', array('manual_arg1', 'manual_arg2'));
(new ResolveClassPass())->process($container);
(new AutowireRequiredMethodsPass())->process($container);
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
$this->assertEquals(
array('setWithCallsConfigured', 'setFoo', 'setDependencies', 'setChildMethodWithoutDocBlock'),
array_column($methodCalls, 0)
);
// test setWithCallsConfigured args
$this->assertEquals(
array('manual_arg1', 'manual_arg2'),
$methodCalls[0][1]
);
// test setFoo args
$this->assertEquals(array(), $methodCalls[1][1]);
}
public function testExplicitMethodInjection()
{
$container = new ContainerBuilder();
$container->register(Foo::class);
$container->register(A::class);
$container->register(CollisionA::class);
$container->register(CollisionB::class);
$container
->register('setter_injection', SetterInjection::class)
->setAutowired(true)
->addMethodCall('notASetter', array());
(new ResolveClassPass())->process($container);
(new AutowireRequiredMethodsPass())->process($container);
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
$this->assertEquals(
array('notASetter', 'setFoo', 'setDependencies', 'setWithCallsConfigured', 'setChildMethodWithoutDocBlock'),
array_column($methodCalls, 0)
);
$this->assertEquals(array(), $methodCalls[0][1]);
}
}

View File

@ -64,4 +64,15 @@ class CheckArgumentsValidityPassTest extends TestCase
array(array(), array(array('baz', array(1 => 1)))),
);
}
public function testNoException()
{
$container = new ContainerBuilder();
$definition = $container->register('foo');
$definition->setArguments(array(null, 'a' => 'a'));
$pass = new CheckArgumentsValidityPass(false);
$pass->process($container);
$this->assertCount(1, $definition->getErrors());
}
}

View File

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Compiler\DefinitionErrorExceptionPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
class DefinitionErrorExceptionPassTest extends TestCase
{
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Things went wrong!
*/
public function testThrowsException()
{
$container = new ContainerBuilder();
$def = new Definition();
$def->addError('Things went wrong!');
$def->addError('Now something else!');
$container->register('foo_service_id')
->setArguments(array(
$def,
));
$pass = new DefinitionErrorExceptionPass();
$pass->process($container);
}
public function testNoExceptionThrown()
{
$container = new ContainerBuilder();
$def = new Definition();
$container->register('foo_service_id')
->setArguments(array(
$def,
));
$pass = new DefinitionErrorExceptionPass();
$pass->process($container);
$this->assertSame($def, $container->getDefinition('foo_service_id')->getArgument(0));
}
}

View File

@ -252,6 +252,9 @@ class InlineServiceDefinitionsPassTest extends TestCase
$this->assertSame('inline', (string) $values[0]);
}
/**
* @group legacy
*/
public function testGetInlinedServiceIdData()
{
$container = new ContainerBuilder();

View File

@ -16,6 +16,7 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass;
use Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@ -54,6 +55,22 @@ class MergeExtensionConfigurationPassTest extends TestCase
$this->assertEquals(array($provider), $tmpProviders);
}
public function testExtensionLoadGetAMergeExtensionConfigurationContainerBuilderInstance()
{
$extension = $this->getMockBuilder(FooExtension::class)->setMethods(array('load'))->getMock();
$extension->expects($this->once())
->method('load')
->with($this->isType('array'), $this->isInstanceOf(MergeExtensionConfigurationContainerBuilder::class))
;
$container = new ContainerBuilder(new ParameterBag());
$container->registerExtension($extension);
$container->prependExtensionConfig('foo', array());
$pass = new MergeExtensionConfigurationPass();
$pass->process($container);
}
public function testExtensionConfigurationIsTrackedByDefault()
{
$extension = $this->getMockBuilder(FooExtension::class)->setMethods(array('getConfiguration'))->getMock();

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
@ -20,6 +21,8 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
use Symfony\Component\DependencyInjection\TypedReference;
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
class ResolveBindingsPassTest extends TestCase
{
public function testProcess()
@ -79,4 +82,17 @@ class ResolveBindingsPassTest extends TestCase
$this->assertEquals(array($typedRef), $container->getDefinition('def1')->getArguments());
$this->assertEquals(array(new Reference('foo')), $container->getDefinition('def2')->getArguments());
}
public function testScalarSetter()
{
$container = new ContainerBuilder();
$definition = $container->autowire('foo', ScalarSetter::class);
$definition->setBindings(array('$defaultLocale' => 'fr'));
(new AutowireRequiredMethodsPass())->process($container);
(new ResolveBindingsPass())->process($container);
$this->assertEquals(array(array('setDefaultLocale', array('fr'))), $definition->getMethodCalls());
}
}

View File

@ -371,4 +371,13 @@ class DefinitionTest extends TestCase
$def->setAutoconfigured(true);
$this->assertTrue($def->isAutoconfigured());
}
public function testAddError()
{
$def = new Definition('stdClass');
$this->assertEmpty($def->getErrors());
$def->addError('First error');
$def->addError('Second error');
$this->assertSame(array('First error', 'Second error'), $def->getErrors());
}
}

View File

@ -0,0 +1,343 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
class Foo
{
}
class Bar
{
public function __construct(Foo $foo)
{
}
}
interface AInterface
{
}
class A implements AInterface
{
public static function create(Foo $foo)
{
}
}
class B extends A
{
}
class C
{
public function __construct(A $a)
{
}
}
interface DInterface
{
}
interface EInterface extends DInterface
{
}
interface IInterface
{
}
class I implements IInterface
{
}
class F extends I implements EInterface
{
}
class G
{
public function __construct(DInterface $d, EInterface $e, IInterface $i)
{
}
}
class H
{
public function __construct(B $b, DInterface $d)
{
}
}
class D
{
public function __construct(A $a, DInterface $d)
{
}
}
class E
{
public function __construct(D $d = null)
{
}
}
class J
{
public function __construct(I $i)
{
}
}
interface CollisionInterface
{
}
class CollisionA implements CollisionInterface
{
}
class CollisionB implements CollisionInterface
{
}
class CannotBeAutowired
{
public function __construct(CollisionInterface $collision)
{
}
}
class CannotBeAutowiredForwardOrder
{
public function __construct(CollisionA $a, CollisionInterface $b, CollisionB $c)
{
}
}
class CannotBeAutowiredReverseOrder
{
public function __construct(CollisionA $a, CollisionB $c, CollisionInterface $b)
{
}
}
class Lille
{
}
class Dunglas
{
public function __construct(Lille $l)
{
}
}
class LesTilleuls
{
public function __construct(Dunglas $j, Dunglas $k)
{
}
}
class OptionalParameter
{
public function __construct(CollisionInterface $c = null, A $a, Foo $f = null)
{
}
}
class BadTypeHintedArgument
{
public function __construct(Dunglas $k, NotARealClass $r)
{
}
}
class BadParentTypeHintedArgument
{
public function __construct(Dunglas $k, OptionalServiceClass $r)
{
}
}
class NotGuessableArgument
{
public function __construct(Foo $k)
{
}
}
class NotGuessableArgumentForSubclass
{
public function __construct(A $k)
{
}
}
class MultipleArguments
{
public function __construct(A $k, $foo, Dunglas $dunglas)
{
}
}
class MultipleArgumentsOptionalScalar
{
public function __construct(A $a, $foo = 'default_val', Lille $lille = null)
{
}
}
class MultipleArgumentsOptionalScalarLast
{
public function __construct(A $a, Lille $lille, $foo = 'some_val')
{
}
}
class MultipleArgumentsOptionalScalarNotReallyOptional
{
public function __construct(A $a, $foo = 'default_val', Lille $lille)
{
}
}
/*
* Classes used for testing createResourceForClass
*/
class ClassForResource
{
public function __construct($foo, Bar $bar = null)
{
}
public function setBar(Bar $bar)
{
}
}
class IdenticalClassResource extends ClassForResource
{
}
class ClassChangedConstructorArgs extends ClassForResource
{
public function __construct($foo, Bar $bar, $baz)
{
}
}
class SetterInjectionCollision
{
/**
* @required
*/
public function setMultipleInstancesForOneArg(CollisionInterface $collision)
{
// The CollisionInterface cannot be autowired - there are multiple
// should throw an exception
}
}
class SetterInjection extends SetterInjectionParent
{
/**
* @required
*/
public function setFoo(Foo $foo)
{
// should be called
}
/** @inheritdoc*/ // <- brackets are missing on purpose
public function setDependencies(Foo $foo, A $a)
{
// should be called
}
/** {@inheritdoc} */
public function setWithCallsConfigured(A $a)
{
// this method has a calls configured on it
}
public function notASetter(A $a)
{
// should be called only when explicitly specified
}
/**
* @required*/
public function setChildMethodWithoutDocBlock(A $a)
{
}
}
class SetterInjectionParent
{
/** @required*/
public function setDependencies(Foo $foo, A $a)
{
// should be called
}
public function notASetter(A $a)
{
// @required should be ignored when the child does not add @inheritdoc
}
/** @required <tab> prefix is on purpose */
public function setWithCallsConfigured(A $a)
{
}
/** @required */
public function setChildMethodWithoutDocBlock(A $a)
{
}
}
class NotWireable
{
public function setNotAutowireable(NotARealClass $n)
{
}
public function setBar()
{
}
public function setOptionalNotAutowireable(NotARealClass $n = null)
{
}
public function setDifferentNamespace(\stdClass $n)
{
}
public function setOptionalNoTypeHint($foo = null)
{
}
public function setOptionalArgNoAutowireable($other = 'default_val')
{
}
/** @required */
protected function setProtectedMethod(A $a)
{
}
}
class PrivateConstructor
{
private function __construct()
{
}
}
class ScalarSetter
{
/**
* @required
*/
public function setDefaultLocale($defaultLocale)
{
}
}

View File

@ -118,7 +118,7 @@ EOF
return $classes[0];
}
if (!$input->isInteractive()) {
throw new InvalidArgumentException(sprintf("The type \"%s\" is ambiguous.\nDid you mean one of these?\n %s", $shortClassName, implode("\n ", $classes)));
throw new InvalidArgumentException(sprintf("The type \"%s\" is ambiguous.\n\nDid you mean one of these?\n %s", $shortClassName, implode("\n ", $classes)));
}
return $io->choice(sprintf("The type \"%s\" is ambiguous.\n\n Select one of the following form types to display its information:", $shortClassName), $classes, $classes[0]);

View File

@ -13,11 +13,11 @@ namespace Symfony\Component\Form\Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Form\Command\DebugCommand;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormRegistryInterface;
use Symfony\Component\Form\ResolvedFormTypeInterface;
use Symfony\Component\Form\FormRegistry;
use Symfony\Component\Form\ResolvedFormTypeFactory;
class DebugCommandTest extends TestCase
{
@ -39,6 +39,67 @@ class DebugCommandTest extends TestCase
$this->assertContains('Symfony\Component\Form\Extension\Core\Type\FormType (Block prefix: "form")', $tester->getDisplay());
}
/**
* @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException
* @expectedExceptionMessage Could not find type "NonExistentType"
*/
public function testDebugSingleFormTypeNotFound()
{
$tester = $this->createCommandTester();
$tester->execute(array('class' => 'NonExistentType'), array('decorated' => false, 'interactive' => false));
}
public function testDebugAmbiguousFormType()
{
$expectedMessage = <<<TXT
The type "AmbiguousType" is ambiguous.
Did you mean one of these?
Symfony\Component\Form\Tests\Fixtures\Debug\A\AmbiguousType
Symfony\Component\Form\Tests\Fixtures\Debug\B\AmbiguousType
TXT;
if (method_exists($this, 'expectException')) {
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage($expectedMessage);
} else {
$this->setExpectedException(InvalidArgumentException::class, $expectedMessage);
}
$tester = $this->createCommandTester(array(
'Symfony\Component\Form\Tests\Fixtures\Debug\A',
'Symfony\Component\Form\Tests\Fixtures\Debug\B',
));
$tester->execute(array('class' => 'AmbiguousType'), array('decorated' => false, 'interactive' => false));
}
public function testDebugAmbiguousFormTypeInteractive()
{
$tester = $this->createCommandTester(array(
'Symfony\Component\Form\Tests\Fixtures\Debug\A',
'Symfony\Component\Form\Tests\Fixtures\Debug\B',
));
$tester->setInputs(array(0));
$tester->execute(array('class' => 'AmbiguousType'), array('decorated' => false, 'interactive' => true));
$this->assertEquals(0, $tester->getStatusCode(), 'Returns 0 in case of success');
$output = $tester->getDisplay(true);
$this->assertStringMatchesFormat(<<<TXT
The type "AmbiguousType" is ambiguous.
Select one of the following form types to display its information: [%A\A\AmbiguousType]:
[0] %A\A\AmbiguousType
[1] %A\B\AmbiguousType
%A
%A\A\AmbiguousType (Block prefix: "ambiguous")
%A
TXT
, $output);
}
/**
* @expectedException \InvalidArgumentException
*/
@ -47,36 +108,10 @@ class DebugCommandTest extends TestCase
$this->createCommandTester()->execute(array('class' => 'test'));
}
/**
* @return CommandTester
*/
private function createCommandTester()
private function createCommandTester(array $namespaces = null)
{
$resolvedFormType = $this->getMockBuilder(ResolvedFormTypeInterface::class)->getMock();
$resolvedFormType
->expects($this->any())
->method('getParent')
->willReturn(null)
;
$resolvedFormType
->expects($this->any())
->method('getInnerType')
->willReturn(new FormType())
;
$resolvedFormType
->expects($this->any())
->method('getTypeExtensions')
->willReturn(array())
;
$formRegistry = $this->getMockBuilder(FormRegistryInterface::class)->getMock();
$formRegistry
->expects($this->any())
->method('getType')
->will($this->returnValue($resolvedFormType))
;
$command = new DebugCommand($formRegistry);
$formRegistry = new FormRegistry(array(), new ResolvedFormTypeFactory());
$command = null === $namespaces ? new DebugCommand($formRegistry) : new DebugCommand($formRegistry, $namespaces);
$application = new Application();
$application->add($command);

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Fixtures\Debug\A;
use Symfony\Component\Form\AbstractType;
class AmbiguousType extends AbstractType
{
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Fixtures\Debug\B;
use Symfony\Component\Form\AbstractType;
class AmbiguousType extends AbstractType
{
}

View File

@ -58,7 +58,7 @@ class YamlFileLoader extends FileLoader
}
try {
$parsedConfig = $this->yamlParser->parse(file_get_contents($path));
$parsedConfig = $this->yamlParser->parseFile($path);
} catch (ParseException $e) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e);
}

View File

@ -18,6 +18,7 @@ CHANGELOG
* added `AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT` context option
to disable throwing an `UnexpectedValueException` on a type mismatch
* added support for serializing `DateInterval` objects
* added getter for extra attributes in `ExtraAttributesException`
3.3.0
-----

View File

@ -18,10 +18,24 @@ namespace Symfony\Component\Serializer\Exception;
*/
class ExtraAttributesException extends RuntimeException
{
private $extraAttributes;
public function __construct(array $extraAttributes, \Exception $previous = null)
{
$msg = sprintf('Extra attributes are not allowed ("%s" are unknown).', implode('", "', $extraAttributes));
$this->extraAttributes = $extraAttributes;
parent::__construct($msg, 0, $previous);
}
/**
* Get the extra attributes that are not allowed.
*
* @return array
*/
public function getExtraAttributes()
{
return $this->extraAttributes;
}
}

View File

@ -113,7 +113,7 @@ class YamlFileLoader extends FileLoader
$this->yamlParser = new Parser();
}
$classes = $this->yamlParser->parse(file_get_contents($this->file));
$classes = $this->yamlParser->parseFile($this->file);
if (empty($classes)) {
return array();

View File

@ -39,7 +39,7 @@ class YamlFileLoader extends FileLoader
}
try {
$messages = $this->yamlParser->parse(file_get_contents($resource));
$messages = $this->yamlParser->parseFile($resource);
} catch (ParseException $e) {
throw new InvalidResourceException(sprintf('Error parsing YAML, invalid file "%s"', $resource), 0, $e);
}

View File

@ -116,7 +116,7 @@ class YamlFileLoader extends FileLoader
private function parseFile($path)
{
try {
$classes = $this->yamlParser->parse(file_get_contents($path), Yaml::PARSE_CONSTANT);
$classes = $this->yamlParser->parseFile($path, Yaml::PARSE_CONSTANT);
} catch (ParseException $e) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e);
}

View File

@ -22,5 +22,11 @@ if (!function_exists('dump')) {
foreach ($moreVars as $var) {
VarDumper::dump($var);
}
if (1 < func_num_args()) {
return func_get_args();
}
return $var;
}
}

View File

@ -34,6 +34,8 @@ CHANGELOG
3.4.0
-----
* added support for parsing YAML files using the `Yaml::parseFile()` or `Parser::parseFile()` method
* the `Dumper`, `Parser`, and `Yaml` classes are marked as final
* Deprecated the `!php/object:` tag which will be replaced by the

View File

@ -26,7 +26,8 @@ class Inline
{
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
public static $parsedLineNumber;
public static $parsedLineNumber = -1;
public static $parsedFilename;
private static $exceptionOnInvalidType = false;
private static $objectSupport = false;
@ -34,19 +35,18 @@ class Inline
private static $constantSupport = false;
/**
* @param int $flags
* @param int|null $parsedLineNumber
* @param int $flags
* @param int|null $parsedLineNumber
* @param string|null $parsedFilename
*/
public static function initialize($flags, $parsedLineNumber = null)
public static function initialize($flags, $parsedLineNumber = null, $parsedFilename = null)
{
self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags);
self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags);
self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags);
self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags);
if (null !== $parsedLineNumber) {
self::$parsedLineNumber = $parsedLineNumber;
}
self::$parsedFilename = $parsedFilename;
self::$parsedLineNumber = null !== $parsedLineNumber ? $parsedLineNumber : -1;
}
/**
@ -96,7 +96,7 @@ class Inline
// some comments are allowed at the end
if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) {
throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)));
throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber, $value, self::$parsedFilename);
}
if (isset($mbEncoding)) {
@ -274,7 +274,7 @@ class Inline
if (null !== $delimiters) {
$tmp = ltrim(substr($scalar, $i), ' ');
if (!in_array($tmp[0], $delimiters)) {
throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber, $scalar, self::$parsedFilename);
}
}
} else {
@ -291,12 +291,12 @@ class Inline
$output = $match[1];
$i += strlen($output);
} else {
throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar));
throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar), self::$parsedLineNumber, null, self::$parsedFilename);
}
// a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) {
throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]));
throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber, $output, self::$parsedFilename);
}
if ($evaluate) {
@ -320,7 +320,7 @@ class Inline
private static function parseQuotedScalar(string $scalar, int &$i): string
{
if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)));
throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)), self::$parsedLineNumber, $scalar, self::$parsedFilename);
}
$output = substr($match[0], 1, strlen($match[0]) - 2);
@ -403,7 +403,7 @@ class Inline
++$i;
}
throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence));
throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence), self::$parsedLineNumber, null, self::$parsedFilename);
}
/**
@ -516,7 +516,7 @@ class Inline
}
}
throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping));
throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping), self::$parsedLineNumber, null, self::$parsedFilename);
}
/**
@ -544,11 +544,11 @@ class Inline
// an unquoted *
if (false === $value || '' === $value) {
throw new ParseException('A reference must contain at least one character.');
throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber, $value, self::$parsedFilename);
}
if (!array_key_exists($value, $references)) {
throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber, $value, self::$parsedFilename);
}
return $references[$value];
@ -575,7 +575,7 @@ class Inline
}
if (self::$exceptionOnInvalidType) {
throw new ParseException('Object support when parsing a YAML file has been disabled.');
throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber, $scalar, self::$parsedFilename);
}
return;
@ -585,10 +585,10 @@ class Inline
return constant($const);
}
throw new ParseException(sprintf('The constant "%s" is not defined.', $const));
throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber, $scalar, self::$parsedFilename);
}
if (self::$exceptionOnInvalidType) {
throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar));
throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename);
}
return;
@ -597,7 +597,7 @@ class Inline
case 0 === strpos($scalar, '!!binary '):
return self::evaluateBinaryScalar(substr($scalar, 9));
default:
throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar));
throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename);
}
// Optimize for returning strings.
@ -677,14 +677,14 @@ class Inline
// Built-in tags
if ($tag && '!' === $tag[0]) {
throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag));
throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber, $value, self::$parsedFilename);
}
if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) {
return $tag;
}
throw new ParseException(sprintf('Tags support is not enabled. Enable the `Yaml::PARSE_CUSTOM_TAGS` flag to use "!%s".', $tag));
throw new ParseException(sprintf('Tags support is not enabled. Enable the `Yaml::PARSE_CUSTOM_TAGS` flag to use "!%s".', $tag), self::$parsedLineNumber, $value, self::$parsedFilename);
}
/**
@ -697,11 +697,11 @@ class Inline
$parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar));
if (0 !== (strlen($parsedBinaryData) % 4)) {
throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', strlen($parsedBinaryData)));
throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', strlen($parsedBinaryData)), self::$parsedLineNumber, $scalar, self::$parsedFilename);
}
if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) {
throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData));
throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber, $scalar, self::$parsedFilename);
}
return base64_decode($parsedBinaryData, true);

View File

@ -26,6 +26,7 @@ class Parser
const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)';
const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
private $filename;
private $offset = 0;
private $totalNumberOfLines;
private $lines = array();
@ -35,6 +36,35 @@ class Parser
private $skippedLineNumbers = array();
private $locallySkippedLineNumbers = array();
/**
* Parses a YAML file into a PHP value.
*
* @param string $filename The path to the YAML file to be parsed
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the file could not be read or the YAML is not valid
*/
public function parseFile($filename, $flags = 0)
{
if (!is_file($filename)) {
throw new ParseException(sprintf('File "%s" does not exist.', $filename));
}
if (!is_readable($filename)) {
throw new ParseException(sprintf('File "%s" cannot be read.', $filename));
}
$this->filename = $filename;
try {
return $this->parse(file_get_contents($filename), $flags);
} finally {
$this->filename = null;
}
}
/**
* Parses a YAML string to a PHP value.
*
@ -48,7 +78,7 @@ class Parser
public function parse(string $value, int $flags = 0)
{
if (false === preg_match('//u', $value)) {
throw new ParseException('The YAML value does not appear to be valid UTF-8.');
throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename);
}
$this->refs = array();
@ -125,13 +155,13 @@ class Parser
// tab?
if ("\t" === $this->currentLine[0]) {
throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
$isRef = $mergeNode = false;
if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
if ($context && 'mapping' == $context) {
throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine);
throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
$context = 'sequence';
@ -175,11 +205,11 @@ class Parser
&& (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'")))
) {
if ($context && 'sequence' == $context) {
throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine);
throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
$context = 'mapping';
Inline::initialize($flags, $this->getRealCurrentLineNb());
Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename);
try {
$key = Inline::parseScalar($values['key']);
} catch (ParseException $e) {
@ -204,13 +234,13 @@ class Parser
if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
$refName = substr(rtrim($values['value']), 1);
if (!array_key_exists($refName, $this->refs)) {
throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
$refValue = $this->refs[$refName];
if (!is_array($refValue)) {
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
$data += $refValue; // array union
@ -223,7 +253,7 @@ class Parser
$parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags);
if (!is_array($parsed)) {
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
if (isset($parsed[0])) {
@ -232,7 +262,7 @@ class Parser
// in the sequence override keys specified in later mapping nodes.
foreach ($parsed as $parsedItem) {
if (!is_array($parsedItem)) {
throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename);
}
$data += $parsedItem; // array union
@ -298,7 +328,7 @@ class Parser
} else {
// multiple documents are not supported
if ('---' === $this->currentLine) {
throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine);
throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
if (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1]) {
@ -360,7 +390,7 @@ class Parser
}
}
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
} while ($this->moveToNextLine());
@ -463,7 +493,7 @@ class Parser
$unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
} else {
$newIndent = $indentation;
@ -542,7 +572,7 @@ class Parser
break;
} else {
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
}
@ -602,7 +632,7 @@ class Parser
}
if (!array_key_exists($value, $this->refs)) {
throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine);
throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
return $this->refs[$value];
@ -652,7 +682,7 @@ class Parser
$parsedValue = Inline::parse($value, $flags, $this->refs);
if ('mapping' === $context && is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
throw new ParseException('A colon cannot be used in an unquoted mapping value.');
throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
return $parsedValue;
@ -992,13 +1022,13 @@ class Parser
// Built-in tags
if ($tag && '!' === $tag[0]) {
throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag));
throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
if (Yaml::PARSE_CUSTOM_TAGS & $flags) {
return $tag;
}
throw new ParseException(sprintf('Tags support is not enabled. You must use the flag `Yaml::PARSE_CUSTOM_TAGS` to use "%s".', $matches['tag']));
throw new ParseException(sprintf('Tags support is not enabled. You must use the flag `Yaml::PARSE_CUSTOM_TAGS` to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
}

View File

@ -0,0 +1,18 @@
- escapedCharacters
- sfComments
- sfCompact
- sfTests
- sfObjects
- sfMergeKey
- sfQuotes
- YtsAnchorAlias
- YtsBasicTests
- YtsBlockMapping
- YtsDocumentSeparator
- YtsErrorTests
- YtsFlowCollections
- YtsFoldedScalars
- YtsNullsAndEmpties
- YtsSpecificationExamples
- YtsTypeTransfers
- unindentedCollections

View File

@ -19,6 +19,11 @@ use Symfony\Component\Yaml\Yaml;
class InlineTest extends TestCase
{
protected function setUp()
{
Inline::initialize(0);
}
/**
* @dataProvider getTestsForParse
*/
@ -241,11 +246,16 @@ class InlineTest extends TestCase
/**
* @dataProvider getReservedIndicators
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
* @expectedExceptionMessage cannot start a plain scalar; you need to quote the scalar.
*/
public function testParseUnquotedScalarStartingWithReservedIndicator($indicator)
{
if (method_exists($this, 'expectExceptionMessage')) {
$this->expectException(ParseException::class);
$this->expectExceptionMessage(sprintf('cannot start a plain scalar; you need to quote the scalar (near "%sfoo ").', $indicator));
} else {
$this->setExpectedException(ParseException::class, sprintf('cannot start a plain scalar; you need to quote the scalar (near "%sfoo ").', $indicator));
}
Inline::parse(sprintf('{ foo: %sfoo }', $indicator));
}
@ -256,11 +266,16 @@ class InlineTest extends TestCase
/**
* @dataProvider getScalarIndicators
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
* @expectedExceptionMessage cannot start a plain scalar; you need to quote the scalar.
*/
public function testParseUnquotedScalarStartingWithScalarIndicator($indicator)
{
if (method_exists($this, 'expectExceptionMessage')) {
$this->expectException(ParseException::class);
$this->expectExceptionMessage(sprintf('cannot start a plain scalar; you need to quote the scalar (near "%sfoo ").', $indicator));
} else {
$this->setExpectedException(ParseException::class, sprintf('cannot start a plain scalar; you need to quote the scalar (near "%sfoo ").', $indicator));
}
Inline::parse(sprintf('{ foo: %sfoo }', $indicator));
}

View File

@ -30,6 +30,8 @@ class ParserTest extends TestCase
protected function tearDown()
{
$this->parser = null;
chmod(__DIR__.'/Fixtures/not_readable.yml', 0644);
}
/**
@ -1594,7 +1596,7 @@ YAML
/**
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
* @expectedExceptionMessage The built-in tag "!!foo" is not implemented.
* @expectedExceptionMessage The built-in tag "!!foo" is not implemented at line 1 (near "!!foo").
*/
public function testExceptionWhenUsingUnsuportedBuiltInTags()
{
@ -1745,6 +1747,43 @@ YAML;
$this->assertSame($expected, $this->parser->parse($yaml, Yaml::PARSE_CONSTANT));
}
public function testFilenamesAreParsedAsStringsWithoutFlag()
{
$file = __DIR__.'/Fixtures/index.yml';
$this->assertSame($file, $this->parser->parse($file));
}
public function testParseFile()
{
$this->assertInternalType('array', $this->parser->parseFile(__DIR__.'/Fixtures/index.yml'));
}
/**
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
* @expectedExceptionMessageRegExp #^File ".+/Fixtures/nonexistent.yml" does not exist\.$#
*/
public function testParsingNonExistentFilesThrowsException()
{
$this->parser->parseFile(__DIR__.'/Fixtures/nonexistent.yml');
}
/**
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
* @expectedExceptionMessageRegExp #^File ".+/Fixtures/not_readable.yml" cannot be read\.$#
*/
public function testParsingNotReadableFilesThrowsException()
{
if ('\\' === DIRECTORY_SEPARATOR) {
$this->markTestSkipped('chmod is not supported on Windows');
}
$file = __DIR__.'/Fixtures/not_readable.yml';
chmod($file, 0200);
$this->parser->parseFile($file);
}
}
class B

View File

@ -34,6 +34,29 @@ class Yaml
const PARSE_CUSTOM_TAGS = 512;
const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024;
/**
* Parses a YAML file into a PHP value.
*
* Usage:
* <code>
* $array = Yaml::parseFile('config.yml');
* print_r($array);
* </code>
*
* @param string $filename The path to the YAML file to be parsed
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the file could not be read or the YAML is not valid
*/
public static function parseFile($filename, $flags = 0)
{
$yaml = new Parser();
return $yaml->parseFile($filename, $flags);
}
/**
* Parses YAML into a PHP value.
*