Merge branch '3.4'
* 3.4: Add TokenProcessor [DI] Handle root namespace in service definitions Add support for command lazy-loading Use rawurlencode() to transform the Cookie into a string [TwigBundle] Added a RuntimeExtensionInterface to take advantage of autoconfigure [Process] Fix parsing args on Windows Add exculde verbosity test [HttpKernel][VarDumper] Truncate profiler data & optim perf [DI] Allow imports in string format for YAML [Validator] Allow to use a property path to get value to compare in comparison constraints [Security] Fix authentication.failure event not dispatched on AccountStatusException add option to define the access decision manager Add support for doctrin/dbal 2.6 types
This commit is contained in:
commit
bdaa7b118e
@ -11,6 +11,11 @@ CHANGELOG
|
||||
method throws an exception
|
||||
* removed the `DoctrineParserCache` class
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* added support for doctrine/dbal v2.6 types
|
||||
|
||||
3.1.0
|
||||
-----
|
||||
|
||||
|
@ -60,6 +60,8 @@ class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface
|
||||
case Type::DATETIMETZ:
|
||||
case 'vardatetime':
|
||||
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateTimeType', array(), Guess::HIGH_CONFIDENCE);
|
||||
case 'dateinterval':
|
||||
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateIntervalType', array(), Guess::HIGH_CONFIDENCE);
|
||||
case Type::DATE:
|
||||
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateType', array(), Guess::HIGH_CONFIDENCE);
|
||||
case Type::TIME:
|
||||
|
@ -117,6 +117,15 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE
|
||||
case DBALType::TIME:
|
||||
return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime'));
|
||||
|
||||
case 'date_immutable':
|
||||
case 'datetime_immutable':
|
||||
case 'datetimetz_immutable':
|
||||
case 'time_immutable':
|
||||
return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTimeImmutable'));
|
||||
|
||||
case 'dateinterval':
|
||||
return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateInterval'));
|
||||
|
||||
case DBALType::TARRAY:
|
||||
return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true));
|
||||
|
||||
|
@ -48,6 +48,8 @@ class DoctrineExtractorTest extends TestCase
|
||||
'id',
|
||||
'guid',
|
||||
'time',
|
||||
'timeImmutable',
|
||||
'dateInterval',
|
||||
'json',
|
||||
'simpleArray',
|
||||
'float',
|
||||
@ -78,6 +80,9 @@ class DoctrineExtractorTest extends TestCase
|
||||
array('id', array(new Type(Type::BUILTIN_TYPE_INT))),
|
||||
array('guid', array(new Type(Type::BUILTIN_TYPE_STRING))),
|
||||
array('bigint', array(new Type(Type::BUILTIN_TYPE_STRING))),
|
||||
array('time', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))),
|
||||
array('timeImmutable', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeImmutable'))),
|
||||
array('dateInterval', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateInterval'))),
|
||||
array('float', array(new Type(Type::BUILTIN_TYPE_FLOAT))),
|
||||
array('decimal', array(new Type(Type::BUILTIN_TYPE_STRING))),
|
||||
array('bool', array(new Type(Type::BUILTIN_TYPE_BOOL))),
|
||||
|
@ -55,6 +55,16 @@ class DoctrineDummy
|
||||
*/
|
||||
private $time;
|
||||
|
||||
/**
|
||||
* @Column(type="time_immutable")
|
||||
*/
|
||||
private $timeImmutable;
|
||||
|
||||
/**
|
||||
* @Column(type="dateinterval")
|
||||
*/
|
||||
private $dateInterval;
|
||||
|
||||
/**
|
||||
* @Column(type="json_array")
|
||||
*/
|
||||
|
43
src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php
Normal file
43
src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?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\Bridge\Monolog\Processor;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
|
||||
/**
|
||||
* Adds the current security token to the log entry.
|
||||
*
|
||||
* @author Dany Maillard <danymaillard93b@gmail.com>
|
||||
*/
|
||||
class TokenProcessor
|
||||
{
|
||||
private $tokenStorage;
|
||||
|
||||
public function __construct(TokenStorageInterface $tokenStorage)
|
||||
{
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
}
|
||||
|
||||
public function __invoke(array $records)
|
||||
{
|
||||
$records['extra']['token'] = null;
|
||||
if (null !== $token = $this->tokenStorage->getToken()) {
|
||||
$records['extra']['token'] = array(
|
||||
'username' => $token->getUsername(),
|
||||
'authenticated' => $token->isAuthenticated(),
|
||||
'roles' => array_map(function ($role) { return $role->getRole(); }, $token->getRoles()),
|
||||
);
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<?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\Bridge\Monolog\Tests\Processor;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bridge\Monolog\Processor\TokenProcessor;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
|
||||
/**
|
||||
* Tests the TokenProcessor.
|
||||
*
|
||||
* @author Dany Maillard <danymaillard93b@gmail.com>
|
||||
*/
|
||||
class TokenProcessorTest extends TestCase
|
||||
{
|
||||
public function testProcessor()
|
||||
{
|
||||
$token = new UsernamePasswordToken('user', 'password', 'provider', array('ROLE_USER'));
|
||||
$tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock();
|
||||
$tokenStorage->method('getToken')->willReturn($token);
|
||||
|
||||
$processor = new TokenProcessor($tokenStorage);
|
||||
$record = array('extra' => array());
|
||||
$record = $processor($record);
|
||||
|
||||
$this->assertArrayHasKey('token', $record['extra']);
|
||||
$this->assertEquals($token->getUsername(), $record['extra']['token']['username']);
|
||||
$this->assertEquals($token->isAuthenticated(), $record['extra']['token']['authenticated']);
|
||||
$roles = array_map(function ($role) { return $role->getRole(); }, $token->getRoles());
|
||||
$this->assertEquals($roles, $record['extra']['token']['roles']);
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
"require-dev": {
|
||||
"symfony/console": "~3.4|~4.0",
|
||||
"symfony/event-dispatcher": "~3.4|~4.0",
|
||||
"symfony/security-core": "~3.4|~4.0",
|
||||
"symfony/var-dumper": "~3.4|~4.0"
|
||||
},
|
||||
"conflict": {
|
||||
|
@ -37,6 +37,8 @@ CHANGELOG
|
||||
`Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass` instead
|
||||
* Deprecated `TranslatorPass`, use
|
||||
`Symfony\Component\Translation\DependencyInjection\TranslatorPass` instead
|
||||
* Added `command` attribute to the `console.command` tag which takes the command
|
||||
name as value, using it makes the command lazy
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
@ -68,15 +68,7 @@ class Application extends BaseApplication
|
||||
{
|
||||
$this->kernel->boot();
|
||||
|
||||
$container = $this->kernel->getContainer();
|
||||
|
||||
foreach ($this->all() as $command) {
|
||||
if ($command instanceof ContainerAwareInterface) {
|
||||
$command->setContainer($container);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setDispatcher($container->get('event_dispatcher'));
|
||||
$this->setDispatcher($this->kernel->getContainer()->get('event_dispatcher'));
|
||||
|
||||
return parent::doRun($input, $output);
|
||||
}
|
||||
@ -98,7 +90,13 @@ class Application extends BaseApplication
|
||||
{
|
||||
$this->registerCommands();
|
||||
|
||||
return parent::get($name);
|
||||
$command = parent::get($name);
|
||||
|
||||
if ($command instanceof ContainerAwareInterface) {
|
||||
$command->setContainer($this->kernel->getContainer());
|
||||
}
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -144,9 +142,15 @@ class Application extends BaseApplication
|
||||
}
|
||||
}
|
||||
|
||||
if ($container->has('console.command_loader')) {
|
||||
$this->setCommandLoader($container->get('console.command_loader'));
|
||||
}
|
||||
|
||||
if ($container->hasParameter('console.command.ids')) {
|
||||
foreach ($container->getParameter('console.command.ids') as $id) {
|
||||
$this->add($container->get($id));
|
||||
if (false !== $id) {
|
||||
$this->add($container->get($id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,10 +77,15 @@ class RouterDebugCommandTest extends TestCase
|
||||
|
||||
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
|
||||
$container
|
||||
->expects($this->once())
|
||||
->expects($this->atLeastOnce())
|
||||
->method('has')
|
||||
->with('router')
|
||||
->will($this->returnValue(true))
|
||||
->will($this->returnCallback(function ($id) {
|
||||
if ('console.command_loader' === $id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}))
|
||||
;
|
||||
$container
|
||||
->expects($this->any())
|
||||
|
@ -78,8 +78,14 @@ class RouterMatchCommandTest extends TestCase
|
||||
$container
|
||||
->expects($this->atLeastOnce())
|
||||
->method('has')
|
||||
->with('router')
|
||||
->will($this->returnValue(true));
|
||||
->will($this->returnCallback(function ($id) {
|
||||
if ('console.command_loader' === $id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}))
|
||||
;
|
||||
$container
|
||||
->expects($this->any())
|
||||
->method('get')
|
||||
|
@ -59,6 +59,26 @@ class MainConfiguration implements ConfigurationInterface
|
||||
$rootNode = $tb->root('security');
|
||||
|
||||
$rootNode
|
||||
->beforeNormalization()
|
||||
->ifTrue(function ($v) {
|
||||
if (!isset($v['access_decision_manager'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isset($v['access_decision_manager']['strategy']) && !isset($v['access_decision_manager']['service'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
->then(function ($v) {
|
||||
$v['access_decision_manager'] = array(
|
||||
'strategy' => AccessDecisionManager::STRATEGY_AFFIRMATIVE,
|
||||
);
|
||||
|
||||
return $v;
|
||||
})
|
||||
->end()
|
||||
->children()
|
||||
->scalarNode('access_denied_url')->defaultNull()->example('/foo/error403')->end()
|
||||
->enumNode('session_fixation_strategy')
|
||||
@ -73,11 +93,15 @@ class MainConfiguration implements ConfigurationInterface
|
||||
->children()
|
||||
->enumNode('strategy')
|
||||
->values(array(AccessDecisionManager::STRATEGY_AFFIRMATIVE, AccessDecisionManager::STRATEGY_CONSENSUS, AccessDecisionManager::STRATEGY_UNANIMOUS))
|
||||
->defaultValue(AccessDecisionManager::STRATEGY_AFFIRMATIVE)
|
||||
->end()
|
||||
->scalarNode('service')->end()
|
||||
->booleanNode('allow_if_all_abstain')->defaultFalse()->end()
|
||||
->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end()
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(function ($v) { return isset($v['strategy']) && isset($v['service']); })
|
||||
->thenInvalid('"strategy" and "service" cannot be used together.')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
@ -83,12 +83,17 @@ class SecurityExtension extends Extension
|
||||
$container->setParameter('security.access.denied_url', $config['access_denied_url']);
|
||||
$container->setParameter('security.authentication.manager.erase_credentials', $config['erase_credentials']);
|
||||
$container->setParameter('security.authentication.session_strategy.strategy', $config['session_fixation_strategy']);
|
||||
$container
|
||||
->getDefinition('security.access.decision_manager')
|
||||
->addArgument($config['access_decision_manager']['strategy'])
|
||||
->addArgument($config['access_decision_manager']['allow_if_all_abstain'])
|
||||
->addArgument($config['access_decision_manager']['allow_if_equal_granted_denied'])
|
||||
;
|
||||
|
||||
if (isset($config['access_decision_manager']['service'])) {
|
||||
$container->setAlias('security.access.decision_manager', $config['access_decision_manager']['service']);
|
||||
} else {
|
||||
$container
|
||||
->getDefinition('security.access.decision_manager')
|
||||
->addArgument($config['access_decision_manager']['strategy'])
|
||||
->addArgument($config['access_decision_manager']['allow_if_all_abstain'])
|
||||
->addArgument($config['access_decision_manager']['allow_if_equal_granted_denied']);
|
||||
}
|
||||
|
||||
$container->setParameter('security.access.always_authenticate_before_granting', $config['always_authenticate_before_granting']);
|
||||
$container->setParameter('security.authentication.hide_user_not_found', $config['hide_user_not_found']);
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
<service id="security.console.user_password_encoder_command" class="Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand">
|
||||
<argument type="service" id="security.encoder_factory"/>
|
||||
<argument type="collection" /> <!-- encoders' user classes -->
|
||||
<tag name="console.command" />
|
||||
<tag name="console.command" command="security:encode-password" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Bundle\SecurityBundle\SecurityBundle;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
|
||||
|
||||
abstract class CompleteConfigurationTest extends TestCase
|
||||
{
|
||||
@ -357,6 +358,29 @@ abstract class CompleteConfigurationTest extends TestCase
|
||||
$this->assertTrue($this->getContainer('remember_me_options')->has('security.console.user_password_encoder_command'));
|
||||
}
|
||||
|
||||
public function testDefaultAccessDecisionManagerStrategyIsAffirmative()
|
||||
{
|
||||
$container = $this->getContainer('access_decision_manager_default_strategy');
|
||||
|
||||
$this->assertSame(AccessDecisionManager::STRATEGY_AFFIRMATIVE, $container->getDefinition('security.access.decision_manager')->getArgument(1), 'Default vote strategy is affirmative');
|
||||
}
|
||||
|
||||
public function testCustomAccessDecisionManagerService()
|
||||
{
|
||||
$container = $this->getContainer('access_decision_manager_service');
|
||||
|
||||
$this->assertSame('app.access_decision_manager', (string) $container->getAlias('security.access.decision_manager'), 'The custom access decision manager service is aliased');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
|
||||
* @expectedExceptionMessage "strategy" and "service" cannot be used together.
|
||||
*/
|
||||
public function testAccessDecisionManagerServiceAndStrategyCannotBeUsedAtTheSameTime()
|
||||
{
|
||||
$container = $this->getContainer('access_decision_manager_service_and_strategy');
|
||||
}
|
||||
|
||||
protected function getContainer($file)
|
||||
{
|
||||
$file = $file.'.'.$this->getFileExtension();
|
||||
|
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
$container->loadFromExtension('security', array(
|
||||
'providers' => array(
|
||||
'default' => array(
|
||||
'memory' => array(
|
||||
'users' => array(
|
||||
'foo' => array('password' => 'foo', 'roles' => 'ROLE_USER'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'firewalls' => array(
|
||||
'simple' => array('pattern' => '/login', 'security' => false),
|
||||
),
|
||||
));
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
$container->loadFromExtension('security', array(
|
||||
'access_decision_manager' => array(
|
||||
'service' => 'app.access_decision_manager',
|
||||
),
|
||||
'providers' => array(
|
||||
'default' => array(
|
||||
'memory' => array(
|
||||
'users' => array(
|
||||
'foo' => array('password' => 'foo', 'roles' => 'ROLE_USER'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'firewalls' => array(
|
||||
'simple' => array('pattern' => '/login', 'security' => false),
|
||||
),
|
||||
));
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
$container->loadFromExtension('security', array(
|
||||
'access_decision_manager' => array(
|
||||
'service' => 'app.access_decision_manager',
|
||||
'strategy' => 'affirmative',
|
||||
),
|
||||
'providers' => array(
|
||||
'default' => array(
|
||||
'memory' => array(
|
||||
'users' => array(
|
||||
'foo' => array('password' => 'foo', 'roles' => 'ROLE_USER'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'firewalls' => array(
|
||||
'simple' => array('pattern' => '/login', 'security' => false),
|
||||
),
|
||||
));
|
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<srv:container xmlns="http://symfony.com/schema/dic/security"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:srv="http://symfony.com/schema/dic/services"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<config>
|
||||
<provider name="default">
|
||||
<memory>
|
||||
<user name="foo" password="foo" roles="ROLE_USER" />
|
||||
</memory>
|
||||
</provider>
|
||||
|
||||
<firewall name="simple" pattern="/login" security="false" />
|
||||
</config>
|
||||
</srv:container>
|
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<srv:container xmlns="http://symfony.com/schema/dic/security"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:srv="http://symfony.com/schema/dic/services"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<config>
|
||||
<access-decision-manager service="app.access_decision_manager" />
|
||||
|
||||
<provider name="default">
|
||||
<memory>
|
||||
<user name="foo" password="foo" roles="ROLE_USER" />
|
||||
</memory>
|
||||
</provider>
|
||||
|
||||
<firewall name="simple" pattern="/login" security="false" />
|
||||
</config>
|
||||
</srv:container>
|
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<srv:container xmlns="http://symfony.com/schema/dic/security"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:srv="http://symfony.com/schema/dic/services"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<config>
|
||||
<access-decision-manager service="app.access_decision_manager" strategy="affirmative" />
|
||||
|
||||
<provider name="default">
|
||||
<memory>
|
||||
<user name="foo" password="foo" roles="ROLE_USER" />
|
||||
</memory>
|
||||
</provider>
|
||||
|
||||
<firewall name="simple" pattern="/login" security="false" />
|
||||
</config>
|
||||
</srv:container>
|
@ -0,0 +1,8 @@
|
||||
security:
|
||||
providers:
|
||||
default:
|
||||
memory:
|
||||
users:
|
||||
foo: { password: foo, roles: ROLE_USER }
|
||||
firewalls:
|
||||
simple: { pattern: /login, security: false }
|
@ -0,0 +1,10 @@
|
||||
security:
|
||||
access_decision_manager:
|
||||
service: app.access_decision_manager
|
||||
providers:
|
||||
default:
|
||||
memory:
|
||||
users:
|
||||
foo: { password: foo, roles: ROLE_USER }
|
||||
firewalls:
|
||||
simple: { pattern: /login, security: false }
|
@ -0,0 +1,11 @@
|
||||
security:
|
||||
access_decision_manager:
|
||||
service: app.access_decision_manager
|
||||
strategy: affirmative
|
||||
providers:
|
||||
default:
|
||||
memory:
|
||||
users:
|
||||
foo: { password: foo, roles: ROLE_USER }
|
||||
firewalls:
|
||||
simple: { pattern: /login, security: false }
|
@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
use Symfony\Component\WebLink\HttpHeaderSerializer;
|
||||
use Twig\Extension\ExtensionInterface;
|
||||
use Twig\Extension\RuntimeExtensionInterface;
|
||||
use Twig\Loader\LoaderInterface;
|
||||
|
||||
/**
|
||||
@ -150,6 +151,7 @@ class TwigExtension extends Extension
|
||||
$container->registerForAutoconfiguration(\Twig_LoaderInterface::class)->addTag('twig.loader');
|
||||
$container->registerForAutoconfiguration(ExtensionInterface::class)->addTag('twig.extension');
|
||||
$container->registerForAutoconfiguration(LoaderInterface::class)->addTag('twig.loader');
|
||||
$container->registerForAutoconfiguration(RuntimeExtensionInterface::class)->addTag('twig.runtime');
|
||||
}
|
||||
|
||||
private function getBundleHierarchy(ContainerBuilder $container)
|
||||
|
@ -10,21 +10,21 @@
|
||||
<service id="web_server.command.server_run" class="Symfony\Bundle\WebServerBundle\Command\ServerRunCommand">
|
||||
<argument>%kernel.project_dir%/web</argument>
|
||||
<argument>%kernel.environment%</argument>
|
||||
<tag name="console.command" />
|
||||
<tag name="console.command" command="server:run" />
|
||||
</service>
|
||||
|
||||
<service id="web_server.command.server_start" class="Symfony\Bundle\WebServerBundle\Command\ServerStartCommand">
|
||||
<argument>%kernel.project_dir%/web</argument>
|
||||
<argument>%kernel.environment%</argument>
|
||||
<tag name="console.command" />
|
||||
<tag name="console.command" command="server:start" />
|
||||
</service>
|
||||
|
||||
<service id="web_server.command.server_stop" class="Symfony\Bundle\WebServerBundle\Command\ServerStopCommand">
|
||||
<tag name="console.command" />
|
||||
<tag name="console.command" command="server:stop" />
|
||||
</service>
|
||||
|
||||
<service id="web_server.command.server_status" class="Symfony\Bundle\WebServerBundle\Command\ServerStatusCommand">
|
||||
<tag name="console.command" />
|
||||
<tag name="console.command" command="server:status" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -62,7 +62,7 @@ class Cookie
|
||||
$this->rawValue = $value;
|
||||
} else {
|
||||
$this->value = $value;
|
||||
$this->rawValue = urlencode($value);
|
||||
$this->rawValue = rawurlencode($value);
|
||||
}
|
||||
$this->name = $name;
|
||||
$this->path = empty($path) ? '/' : $path;
|
||||
|
@ -16,6 +16,21 @@ use Symfony\Component\BrowserKit\Cookie;
|
||||
|
||||
class CookieTest extends TestCase
|
||||
{
|
||||
public function testToString()
|
||||
{
|
||||
$cookie = new Cookie('foo', 'bar', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
|
||||
$this->assertEquals('foo=bar; expires=Fri, 20 May 2011 15:25:52 GMT; domain=.myfoodomain.com; path=/; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie');
|
||||
|
||||
$cookie = new Cookie('foo', 'bar with white spaces', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
|
||||
$this->assertEquals('foo=bar%20with%20white%20spaces; expires=Fri, 20 May 2011 15:25:52 GMT; domain=.myfoodomain.com; path=/; secure; httponly', (string) $cookie, '->__toString() encodes the value of the cookie according to RFC 3986 (white space = %20)');
|
||||
|
||||
$cookie = new Cookie('foo', null, 1, '/admin/', '.myfoodomain.com');
|
||||
$this->assertEquals('foo=; expires=Thu, 01 Jan 1970 00:00:01 GMT; domain=.myfoodomain.com; path=/admin/; httponly', (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL');
|
||||
|
||||
$cookie = new Cookie('foo', 'bar', 0, '/', '');
|
||||
$this->assertEquals('foo=bar; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; httponly', (string) $cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTestsForToFromString
|
||||
*/
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Console;
|
||||
|
||||
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
|
||||
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Helper\DebugFormatterHelper;
|
||||
@ -63,6 +64,7 @@ class Application
|
||||
private $runningCommand;
|
||||
private $name;
|
||||
private $version;
|
||||
private $commandLoader;
|
||||
private $catchExceptions = true;
|
||||
private $autoExit = true;
|
||||
private $definition;
|
||||
@ -95,6 +97,11 @@ class Application
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
public function setCommandLoader(CommandLoaderInterface $commandLoader)
|
||||
{
|
||||
$this->commandLoader = $commandLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the current application.
|
||||
*
|
||||
@ -423,6 +430,10 @@ class Application
|
||||
throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
|
||||
}
|
||||
|
||||
if (!$command->getName()) {
|
||||
throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($command)));
|
||||
}
|
||||
|
||||
$this->commands[$command->getName()] = $command;
|
||||
|
||||
foreach ($command->getAliases() as $alias) {
|
||||
@ -443,12 +454,16 @@ class Application
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
if (!isset($this->commands[$name])) {
|
||||
if (isset($this->commands[$name])) {
|
||||
$command = $this->commands[$name];
|
||||
} elseif ($this->commandLoader && $this->commandLoader->has($name)) {
|
||||
$command = $this->commandLoader->get($name);
|
||||
$command->setName($name);
|
||||
$this->add($command);
|
||||
} else {
|
||||
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
|
||||
}
|
||||
|
||||
$command = $this->commands[$name];
|
||||
|
||||
if ($this->wantHelps) {
|
||||
$this->wantHelps = false;
|
||||
|
||||
@ -470,7 +485,7 @@ class Application
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
return isset($this->commands[$name]);
|
||||
return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -547,7 +562,7 @@ class Application
|
||||
*/
|
||||
public function find($name)
|
||||
{
|
||||
$allCommands = array_keys($this->commands);
|
||||
$allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
|
||||
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
|
||||
$commands = preg_grep('{^'.$expr.'}', $allCommands);
|
||||
|
||||
@ -573,12 +588,12 @@ class Application
|
||||
|
||||
// filter out aliases for commands which are already on the list
|
||||
if (count($commands) > 1) {
|
||||
$commandList = $this->commands;
|
||||
$commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
|
||||
$commandName = $commandList[$nameOrAlias]->getName();
|
||||
$commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
|
||||
$commands = array_unique(array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
|
||||
$commandName = $commandList[$nameOrAlias] instanceof Command ? $commandList[$nameOrAlias]->getName() : $nameOrAlias;
|
||||
|
||||
return $commandName === $nameOrAlias || !in_array($commandName, $commands);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
$exact = in_array($name, $commands, true);
|
||||
@ -590,6 +605,9 @@ class Application
|
||||
$maxLen = max(Helper::strlen($abbrev), $maxLen);
|
||||
}
|
||||
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) {
|
||||
if (!$commandList[$cmd] instanceof Command) {
|
||||
return $cmd;
|
||||
}
|
||||
$abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
|
||||
|
||||
return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
|
||||
@ -614,7 +632,18 @@ class Application
|
||||
public function all($namespace = null)
|
||||
{
|
||||
if (null === $namespace) {
|
||||
return $this->commands;
|
||||
if (!$this->commandLoader) {
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
$commands = $this->commands;
|
||||
foreach ($this->commandLoader->getNames() as $name) {
|
||||
if (!isset($commands[$name])) {
|
||||
$commands[$name] = $this->commandLoader->get($name);
|
||||
}
|
||||
}
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
$commands = array();
|
||||
@ -624,6 +653,14 @@ class Application
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->commandLoader) {
|
||||
foreach ($this->commandLoader->getNames() as $name) {
|
||||
if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
|
||||
$commands[$name] = $this->commandLoader->get($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,11 @@ CHANGELOG
|
||||
* removed `ConsoleExceptionEvent`
|
||||
* removed `ConsoleEvents::EXCEPTION`
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* added `CommandLoaderInterface` and PSR-11 `ContainerCommandLoader`
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
|
@ -61,10 +61,6 @@ class Command
|
||||
}
|
||||
|
||||
$this->configure();
|
||||
|
||||
if (!$this->name) {
|
||||
throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Console\CommandLoader;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
|
||||
/**
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
interface CommandLoaderInterface
|
||||
{
|
||||
/**
|
||||
* Loads a command.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return Command
|
||||
*
|
||||
* @throws CommandNotFoundException
|
||||
*/
|
||||
public function get($name);
|
||||
|
||||
/**
|
||||
* Checks if a command exists.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has($name);
|
||||
|
||||
/**
|
||||
* @return string[] All registered command names
|
||||
*/
|
||||
public function getNames();
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Console\CommandLoader;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
|
||||
/**
|
||||
* Loads commands from a PSR-11 container.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class ContainerCommandLoader implements CommandLoaderInterface
|
||||
{
|
||||
private $container;
|
||||
private $commandMap;
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container A container from which to load command services
|
||||
* @param array $commandMap An array with command names as keys and service ids as values
|
||||
*/
|
||||
public function __construct(ContainerInterface $container, array $commandMap)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->commandMap = $commandMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
if (!$this->has($name)) {
|
||||
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
|
||||
}
|
||||
|
||||
return $this->container->get($this->commandMap[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNames()
|
||||
{
|
||||
return array_keys($this->commandMap);
|
||||
}
|
||||
}
|
@ -12,9 +12,12 @@
|
||||
namespace Symfony\Component\Console\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\TypedReference;
|
||||
|
||||
/**
|
||||
* Registers console commands.
|
||||
@ -23,9 +26,20 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
*/
|
||||
class AddConsoleCommandPass implements CompilerPassInterface
|
||||
{
|
||||
private $commandLoaderServiceId;
|
||||
private $commandTag;
|
||||
|
||||
public function __construct($commandLoaderServiceId = 'console.command_loader', $commandTag = 'console.command')
|
||||
{
|
||||
$this->commandLoaderServiceId = $commandLoaderServiceId;
|
||||
$this->commandTag = $commandTag;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$commandServices = $container->findTaggedServiceIds('console.command', true);
|
||||
$commandServices = $container->findTaggedServiceIds($this->commandTag, true);
|
||||
$lazyCommandMap = array();
|
||||
$lazyCommandRefs = array();
|
||||
$serviceIds = array();
|
||||
|
||||
foreach ($commandServices as $id => $tags) {
|
||||
@ -36,21 +50,52 @@ class AddConsoleCommandPass implements CompilerPassInterface
|
||||
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
|
||||
}
|
||||
if (!$r->isSubclassOf(Command::class)) {
|
||||
throw new InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must be a subclass of "%s".', $id, Command::class));
|
||||
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
|
||||
}
|
||||
|
||||
$commandId = 'console.command.'.strtolower(str_replace('\\', '_', $class));
|
||||
if ($container->hasAlias($commandId) || isset($serviceIds[$commandId])) {
|
||||
$commandId = $commandId.'_'.$id;
|
||||
}
|
||||
if (!$definition->isPublic()) {
|
||||
$container->setAlias($commandId, $id);
|
||||
$id = $commandId;
|
||||
|
||||
if (!isset($tags[0]['command'])) {
|
||||
if (isset($serviceIds[$commandId]) || $container->hasAlias($commandId)) {
|
||||
$commandId = $commandId.'_'.$id;
|
||||
}
|
||||
if (!$definition->isPublic()) {
|
||||
$container->setAlias($commandId, $id);
|
||||
$id = $commandId;
|
||||
}
|
||||
$serviceIds[$commandId] = $id;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$serviceIds[$commandId] = $id;
|
||||
$serviceIds[$commandId] = false;
|
||||
$commandName = $tags[0]['command'];
|
||||
$lazyCommandMap[$commandName] = $id;
|
||||
$lazyCommandRefs[$id] = new TypedReference($id, $class);
|
||||
$aliases = array();
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if (!isset($tag['command'])) {
|
||||
throw new InvalidArgumentException(sprintf('Missing "command" attribute on tag "%s" for service "%s".', $this->commandTag, $id));
|
||||
}
|
||||
if ($commandName !== $tag['command']) {
|
||||
throw new InvalidArgumentException(sprintf('The "command" attribute must be the same on each "%s" tag for service "%s".', $this->commandTag, $id));
|
||||
}
|
||||
if (isset($tag['alias'])) {
|
||||
$aliases[] = $tag['alias'];
|
||||
$lazyCommandMap[$tag['alias']] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
if ($aliases) {
|
||||
$definition->addMethodCall('setAliases', array($aliases));
|
||||
}
|
||||
}
|
||||
|
||||
$container
|
||||
->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
|
||||
->setArguments(array(ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap));
|
||||
|
||||
$container->setParameter('console.command.ids', $serviceIds);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,9 @@ namespace Symfony\Component\Console\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
|
||||
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Symfony\Component\Console\Helper\FormatterHelper;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
@ -30,6 +33,8 @@ use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||
use Symfony\Component\Console\Event\ConsoleErrorEvent;
|
||||
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
class ApplicationTest extends TestCase
|
||||
@ -113,6 +118,26 @@ class ApplicationTest extends TestCase
|
||||
$this->assertCount(1, $commands, '->all() takes a namespace as its first argument');
|
||||
}
|
||||
|
||||
public function testAllWithCommandLoader()
|
||||
{
|
||||
$application = new Application();
|
||||
$commands = $application->all();
|
||||
$this->assertInstanceOf('Symfony\\Component\\Console\\Command\\HelpCommand', $commands['help'], '->all() returns the registered commands');
|
||||
|
||||
$application->add(new \FooCommand());
|
||||
$commands = $application->all('foo');
|
||||
$this->assertCount(1, $commands, '->all() takes a namespace as its first argument');
|
||||
|
||||
$application->setCommandLoader(new ContainerCommandLoader(
|
||||
new ServiceLocator(array('foo-bar' => function () { return new \Foo1Command(); })),
|
||||
array('foo:bar1' => 'foo-bar')
|
||||
));
|
||||
$commands = $application->all('foo');
|
||||
$this->assertCount(2, $commands, '->all() takes a namespace as its first argument');
|
||||
$this->assertInstanceOf(\FooCommand::class, $commands['foo:bar'], '->all() returns the registered commands');
|
||||
$this->assertInstanceOf(\Foo1Command::class, $commands['foo:bar1'], '->all() returns the registered commands');
|
||||
}
|
||||
|
||||
public function testRegister()
|
||||
{
|
||||
$application = new Application();
|
||||
@ -165,6 +190,30 @@ class ApplicationTest extends TestCase
|
||||
$this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $command, '->get() returns the help command if --help is provided as the input');
|
||||
}
|
||||
|
||||
public function testHasGetWithCommandLoader()
|
||||
{
|
||||
$application = new Application();
|
||||
$this->assertTrue($application->has('list'), '->has() returns true if a named command is registered');
|
||||
$this->assertFalse($application->has('afoobar'), '->has() returns false if a named command is not registered');
|
||||
|
||||
$application->add($foo = new \FooCommand());
|
||||
$this->assertTrue($application->has('afoobar'), '->has() returns true if an alias is registered');
|
||||
$this->assertEquals($foo, $application->get('foo:bar'), '->get() returns a command by name');
|
||||
$this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias');
|
||||
|
||||
$application->setCommandLoader(new ContainerCommandLoader(new ServiceLocator(array(
|
||||
'foo-bar' => function () { return new \Foo1Command(); },
|
||||
)), array('foo:bar1' => 'foo-bar', 'afoobar1' => 'foo-bar')));
|
||||
|
||||
$this->assertTrue($application->has('afoobar'), '->has() returns true if an instance is registered for an alias even with command loader');
|
||||
$this->assertEquals($foo, $application->get('foo:bar'), '->get() returns an instance by name even with command loader');
|
||||
$this->assertEquals($foo, $application->get('afoobar'), '->get() returns an instance by alias even with command loader');
|
||||
$this->assertTrue($application->has('foo:bar1'), '->has() returns true for commands registered in the loader');
|
||||
$this->assertInstanceOf(\Foo1Command::class, $foo1 = $application->get('foo:bar1'), '->get() returns a command by name from the command loader');
|
||||
$this->assertTrue($application->has('afoobar1'), '->has() returns true for commands registered in the loader');
|
||||
$this->assertEquals($foo1, $application->get('afoobar1'), '->get() returns a command by name from the command loader');
|
||||
}
|
||||
|
||||
public function testSilentHelp()
|
||||
{
|
||||
$application = new Application();
|
||||
@ -268,6 +317,20 @@ class ApplicationTest extends TestCase
|
||||
$this->assertInstanceOf('FooCommand', $application->find('a'), '->find() returns a command if the abbreviation exists for an alias');
|
||||
}
|
||||
|
||||
public function testFindWithCommandLoader()
|
||||
{
|
||||
$application = new Application();
|
||||
$application->setCommandLoader(new ContainerCommandLoader(new ServiceLocator(array(
|
||||
'foo-bar' => $f = function () { return new \FooCommand(); },
|
||||
)), array('foo:bar' => 'foo-bar')));
|
||||
|
||||
$this->assertInstanceOf('FooCommand', $application->find('foo:bar'), '->find() returns a command if its name exists');
|
||||
$this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $application->find('h'), '->find() returns a command if its name exists');
|
||||
$this->assertInstanceOf('FooCommand', $application->find('f:bar'), '->find() returns a command if the abbreviation for the namespace exists');
|
||||
$this->assertInstanceOf('FooCommand', $application->find('f:b'), '->find() returns a command if the abbreviation for the namespace and the command name exist');
|
||||
$this->assertInstanceOf('FooCommand', $application->find('a'), '->find() returns a command if the abbreviation exists for an alias');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideAmbiguousAbbreviations
|
||||
*/
|
||||
@ -1313,6 +1376,35 @@ class ApplicationTest extends TestCase
|
||||
$this->assertEquals($tester->getInput()->isInteractive(), @posix_isatty($inputStream));
|
||||
}
|
||||
|
||||
public function testRunLazyCommandService()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->addCompilerPass(new AddConsoleCommandPass());
|
||||
$container
|
||||
->register('lazy-command', LazyCommand::class)
|
||||
->addTag('console.command', array('command' => 'lazy:command', 'alias' => 'lazy:alias'))
|
||||
->addTag('console.command', array('command' => 'lazy:command', 'alias' => 'lazy:alias2'));
|
||||
$container->compile();
|
||||
|
||||
$application = new Application();
|
||||
$application->setCommandLoader($container->get('console.command_loader'));
|
||||
$application->setAutoExit(false);
|
||||
|
||||
$tester = new ApplicationTester($application);
|
||||
|
||||
$tester->run(array('command' => 'lazy:command'));
|
||||
$this->assertSame("lazy-command called\n", $tester->getDisplay(true));
|
||||
|
||||
$tester->run(array('command' => 'lazy:alias'));
|
||||
$this->assertSame("lazy-command called\n", $tester->getDisplay(true));
|
||||
|
||||
$tester->run(array('command' => 'lazy:alias2'));
|
||||
$this->assertSame("lazy-command called\n", $tester->getDisplay(true));
|
||||
|
||||
$command = $application->get('lazy:command');
|
||||
$this->assertSame(array('lazy:alias', 'lazy:alias2'), $command->getAliases());
|
||||
}
|
||||
|
||||
protected function getDispatcher($skipCommand = false)
|
||||
{
|
||||
$dispatcher = new EventDispatcher();
|
||||
@ -1397,3 +1489,11 @@ class CustomDefaultCommandApplication extends Application
|
||||
$this->setDefaultCommand($command->getName());
|
||||
}
|
||||
}
|
||||
|
||||
class LazyCommand extends Command
|
||||
{
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$output->writeln('lazy-command called');
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class CommandTest extends TestCase
|
||||
*/
|
||||
public function testCommandNameCannotBeEmpty()
|
||||
{
|
||||
new Command();
|
||||
(new Application())->add(new Command());
|
||||
}
|
||||
|
||||
public function testSetApplication()
|
||||
|
@ -0,0 +1,61 @@
|
||||
<?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\Console\Tests\CommandLoader;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
|
||||
class ContainerCommandLoaderTest extends TestCase
|
||||
{
|
||||
public function testHas()
|
||||
{
|
||||
$loader = new ContainerCommandLoader(new ServiceLocator(array(
|
||||
'foo-service' => function () { return new Command('foo'); },
|
||||
'bar-service' => function () { return new Command('bar'); },
|
||||
)), array('foo' => 'foo-service', 'bar' => 'bar-service'));
|
||||
|
||||
$this->assertTrue($loader->has('foo'));
|
||||
$this->assertTrue($loader->has('bar'));
|
||||
$this->assertFalse($loader->has('baz'));
|
||||
}
|
||||
|
||||
public function testGet()
|
||||
{
|
||||
$loader = new ContainerCommandLoader(new ServiceLocator(array(
|
||||
'foo-service' => function () { return new Command('foo'); },
|
||||
'bar-service' => function () { return new Command('bar'); },
|
||||
)), array('foo' => 'foo-service', 'bar' => 'bar-service'));
|
||||
|
||||
$this->assertInstanceOf(Command::class, $loader->get('foo'));
|
||||
$this->assertInstanceOf(Command::class, $loader->get('bar'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException
|
||||
*/
|
||||
public function testGetUnknownCommandThrows()
|
||||
{
|
||||
(new ContainerCommandLoader(new ServiceLocator(array()), array()))->get('unknown');
|
||||
}
|
||||
|
||||
public function testGetCommandNames()
|
||||
{
|
||||
$loader = new ContainerCommandLoader(new ServiceLocator(array(
|
||||
'foo-service' => function () { return new Command('foo'); },
|
||||
'bar-service' => function () { return new Command('bar'); },
|
||||
)), array('foo' => 'foo-service', 'bar' => 'bar-service'));
|
||||
|
||||
$this->assertSame(array('foo', 'bar'), $loader->getNames());
|
||||
}
|
||||
}
|
@ -12,10 +12,13 @@
|
||||
namespace Symfony\Component\Console\Tests\DependencyInjection;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
|
||||
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\TypedReference;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class AddConsoleCommandPassTest extends TestCase
|
||||
@ -53,6 +56,26 @@ class AddConsoleCommandPassTest extends TestCase
|
||||
$this->assertSame(array($alias => $id), $container->getParameter('console.command.ids'));
|
||||
}
|
||||
|
||||
public function testProcessRegisterLazyCommands()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container
|
||||
->register('my-command', MyCommand::class)
|
||||
->setPublic(false)
|
||||
->addTag('console.command', array('command' => 'my:command', 'alias' => 'my:alias'))
|
||||
;
|
||||
|
||||
(new AddConsoleCommandPass())->process($container);
|
||||
|
||||
$commandLoader = $container->getDefinition('console.command_loader');
|
||||
$commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0));
|
||||
|
||||
$this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass());
|
||||
$this->assertSame(array('my:command' => 'my-command', 'my:alias' => 'my-command'), $commandLoader->getArgument(1));
|
||||
$this->assertEquals(array(array('my-command' => new ServiceClosureArgument(new TypedReference('my-command', MyCommand::class)))), $commandLocator->getArguments());
|
||||
$this->assertSame(array('console.command.symfony_component_console_tests_dependencyinjection_mycommand' => false), $container->getParameter('console.command.ids'));
|
||||
}
|
||||
|
||||
public function visibilityProvider()
|
||||
{
|
||||
return array(
|
||||
@ -72,7 +95,7 @@ class AddConsoleCommandPassTest extends TestCase
|
||||
$container->addCompilerPass(new AddConsoleCommandPass());
|
||||
|
||||
$definition = new Definition('Symfony\Component\Console\Tests\DependencyInjection\MyCommand');
|
||||
$definition->addTag('console.command');
|
||||
$definition->addTag('console.command', array('command' => 'my:command'));
|
||||
$definition->setAbstract(true);
|
||||
$container->setDefinition('my-command', $definition);
|
||||
|
||||
@ -90,7 +113,7 @@ class AddConsoleCommandPassTest extends TestCase
|
||||
$container->addCompilerPass(new AddConsoleCommandPass());
|
||||
|
||||
$definition = new Definition('SplObjectStorage');
|
||||
$definition->addTag('console.command');
|
||||
$definition->addTag('console.command', array('command' => 'my:command'));
|
||||
$container->setDefinition('my-command', $definition);
|
||||
|
||||
$container->compile();
|
||||
|
@ -392,15 +392,9 @@ class PhpDumper extends Dumper
|
||||
*/
|
||||
private function addServiceInstance($id, Definition $definition, $isSimpleInstance)
|
||||
{
|
||||
$class = $definition->getClass();
|
||||
$class = $this->dumpValue($definition->getClass());
|
||||
|
||||
if ('\\' === substr($class, 0, 1)) {
|
||||
$class = substr($class, 1);
|
||||
}
|
||||
|
||||
$class = $this->dumpValue($class);
|
||||
|
||||
if (0 === strpos($class, "'") && false === strpos($class, '$') && !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
|
||||
if (0 === strpos($class, "'") && false === strpos($class, '$') && !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
|
||||
throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id));
|
||||
}
|
||||
|
||||
@ -1488,11 +1482,13 @@ EOF;
|
||||
if (false !== strpos($class, '$')) {
|
||||
return sprintf('${($_ = %s) && false ?: "_"}', $class);
|
||||
}
|
||||
if (0 !== strpos($class, "'") || !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
|
||||
if (0 !== strpos($class, "'") || !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
|
||||
throw new RuntimeException(sprintf('Cannot dump definition because of invalid class name (%s)', $class ?: 'n/a'));
|
||||
}
|
||||
|
||||
return '\\'.substr(str_replace('\\\\', '\\', $class), 1, -1);
|
||||
$class = substr(str_replace('\\\\', '\\', $class), 1, -1);
|
||||
|
||||
return 0 === strpos($class, '\\') ? $class : '\\'.$class;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,7 +176,10 @@ class YamlFileLoader extends FileLoader
|
||||
$defaultDirectory = dirname($file);
|
||||
foreach ($content['imports'] as $import) {
|
||||
if (!is_array($import)) {
|
||||
throw new InvalidArgumentException(sprintf('The values in the "imports" key should be arrays in %s. Check your YAML syntax.', $file));
|
||||
$import = array('resource' => $import);
|
||||
}
|
||||
if (!isset($import['resource'])) {
|
||||
throw new InvalidArgumentException(sprintf('An import should provide a resource in %s. Check your YAML syntax.', $file));
|
||||
}
|
||||
|
||||
$this->setCurrentDir($defaultDirectory);
|
||||
|
@ -597,4 +597,18 @@ class PhpDumperTest extends TestCase
|
||||
|
||||
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_array_params.php', str_replace('\\\\Dumper', '/Dumper', $dumper->dump(array('file' => self::$fixturesPath.'/php/services_array_params.php'))));
|
||||
}
|
||||
|
||||
public function testDumpHandlesLiteralClassWithRootNamespace()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('foo', '\\stdClass');
|
||||
$container->compile();
|
||||
|
||||
$dumper = new PhpDumper($container);
|
||||
eval('?>'.$dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Literal_Class_With_Root_Namespace')));
|
||||
|
||||
$container = new \Symfony_DI_PhpDumper_Test_Literal_Class_With_Root_Namespace();
|
||||
|
||||
$this->assertInstanceOf('stdClass', $container->get('foo'));
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
imports:
|
||||
- foo.yml
|
||||
- { resource: ~ }
|
||||
|
@ -1,5 +1,5 @@
|
||||
imports:
|
||||
- { resource: services2.yml }
|
||||
- services2.yml
|
||||
- { resource: services3.yml }
|
||||
- { resource: "../php/simple.php" }
|
||||
- { resource: "../ini/parameters.ini", class: Symfony\Component\DependencyInjection\Loader\IniFileLoader }
|
||||
|
@ -19,11 +19,7 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
use Symfony\Component\VarDumper\Caster\Caster;
|
||||
use Symfony\Component\VarDumper\Caster\ClassStub;
|
||||
use Symfony\Component\VarDumper\Caster\CutStub;
|
||||
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
|
||||
use Symfony\Component\VarDumper\Cloner\Data;
|
||||
use Symfony\Component\VarDumper\Cloner\Stub;
|
||||
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||
|
||||
/**
|
||||
* Data collector for {@link FormInterface} instances.
|
||||
@ -71,11 +67,6 @@ class FormDataCollector extends DataCollector implements FormDataCollectorInterf
|
||||
*/
|
||||
private $formsByView;
|
||||
|
||||
/**
|
||||
* @var ClonerInterface
|
||||
*/
|
||||
private $cloner;
|
||||
|
||||
public function __construct(FormDataExtractorInterface $dataExtractor)
|
||||
{
|
||||
if (!class_exists(ClassStub::class)) {
|
||||
@ -248,49 +239,33 @@ class FormDataCollector extends DataCollector implements FormDataCollectorInterf
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function cloneVar($var, $isClass = false)
|
||||
protected function getCasters()
|
||||
{
|
||||
if ($var instanceof Data) {
|
||||
return $var;
|
||||
}
|
||||
if (null === $this->cloner) {
|
||||
$this->cloner = new VarCloner();
|
||||
$this->cloner->setMaxItems(-1);
|
||||
$this->cloner->addCasters(array(
|
||||
'*' => function ($v, array $a, Stub $s, $isNested) {
|
||||
foreach ($a as &$v) {
|
||||
if (is_object($v) && !$v instanceof \DateTimeInterface) {
|
||||
$v = new CutStub($v);
|
||||
}
|
||||
}
|
||||
|
||||
return $a;
|
||||
},
|
||||
\Exception::class => function (\Exception $e, array $a, Stub $s) {
|
||||
if (isset($a[$k = "\0Exception\0previous"])) {
|
||||
return parent::getCasters() + array(
|
||||
\Exception::class => function (\Exception $e, array $a, Stub $s) {
|
||||
foreach (array("\0Exception\0previous", "\0Exception\0trace") as $k) {
|
||||
if (isset($a[$k])) {
|
||||
unset($a[$k]);
|
||||
++$s->cut;
|
||||
}
|
||||
}
|
||||
|
||||
return $a;
|
||||
},
|
||||
FormInterface::class => function (FormInterface $f, array $a) {
|
||||
return array(
|
||||
Caster::PREFIX_VIRTUAL.'name' => $f->getName(),
|
||||
Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub(get_class($f->getConfig()->getType()->getInnerType())),
|
||||
);
|
||||
},
|
||||
ConstraintViolationInterface::class => function (ConstraintViolationInterface $v, array $a) {
|
||||
return array(
|
||||
Caster::PREFIX_VIRTUAL.'root' => $v->getRoot(),
|
||||
Caster::PREFIX_VIRTUAL.'path' => $v->getPropertyPath(),
|
||||
Caster::PREFIX_VIRTUAL.'value' => $v->getInvalidValue(),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
return $this->cloner->cloneVar($var, Caster::EXCLUDE_VERBOSE);
|
||||
return $a;
|
||||
},
|
||||
FormInterface::class => function (FormInterface $f, array $a) {
|
||||
return array(
|
||||
Caster::PREFIX_VIRTUAL.'name' => $f->getName(),
|
||||
Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub(get_class($f->getConfig()->getType()->getInnerType())),
|
||||
);
|
||||
},
|
||||
ConstraintViolationInterface::class => function (ConstraintViolationInterface $v, array $a) {
|
||||
return array(
|
||||
Caster::PREFIX_VIRTUAL.'root' => $v->getRoot(),
|
||||
Caster::PREFIX_VIRTUAL.'path' => $v->getPropertyPath(),
|
||||
Caster::PREFIX_VIRTUAL.'value' => $v->getInvalidValue(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private function &recursiveBuildPreliminaryFormTree(FormInterface $form, array &$outputByHash)
|
||||
|
@ -39,8 +39,8 @@
|
||||
"symfony/dependency-injection": "<3.4",
|
||||
"symfony/doctrine-bridge": "<3.4",
|
||||
"symfony/framework-bundle": "<3.4",
|
||||
"symfony/twig-bridge": "<3.4",
|
||||
"symfony/var-dumper": "<3.4"
|
||||
"symfony/http-kernel": "<3.4",
|
||||
"symfony/twig-bridge": "<3.4"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/validator": "For form validation.",
|
||||
|
@ -145,7 +145,7 @@ class Cookie
|
||||
if ('' === (string) $this->getValue()) {
|
||||
$str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001';
|
||||
} else {
|
||||
$str .= $this->isRaw() ? $this->getValue() : urlencode($this->getValue());
|
||||
$str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue());
|
||||
|
||||
if (0 !== $this->getExpiresTime()) {
|
||||
$str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; max-age='.$this->getMaxAge();
|
||||
|
@ -161,6 +161,9 @@ class CookieTest extends TestCase
|
||||
$cookie = new Cookie('foo', 'bar', $expire = strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
|
||||
$this->assertEquals('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; max-age='.($expire - time()).'; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie');
|
||||
|
||||
$cookie = new Cookie('foo', 'bar with white spaces', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
|
||||
$this->assertEquals('foo=bar%20with%20white%20spaces; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() encodes the value of the cookie according to RFC 3986 (white space = %20)');
|
||||
|
||||
$cookie = new Cookie('foo', null, 1, '/admin/', '.myfoodomain.com');
|
||||
$this->assertEquals('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', $expire = time() - 31536001).'; max-age='.($expire - time()).'; path=/admin/; domain=.myfoodomain.com; httponly', (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL');
|
||||
|
||||
|
@ -11,9 +11,10 @@
|
||||
|
||||
namespace Symfony\Component\HttpKernel\DataCollector;
|
||||
|
||||
use Symfony\Component\VarDumper\Caster\ClassStub;
|
||||
use Symfony\Component\VarDumper\Caster\CutStub;
|
||||
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
|
||||
use Symfony\Component\VarDumper\Cloner\Data;
|
||||
use Symfony\Component\VarDumper\Cloner\Stub;
|
||||
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||
|
||||
/**
|
||||
@ -31,7 +32,7 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
|
||||
/**
|
||||
* @var ClonerInterface
|
||||
*/
|
||||
private static $cloner;
|
||||
private $cloner;
|
||||
|
||||
public function serialize()
|
||||
{
|
||||
@ -55,15 +56,38 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
|
||||
*/
|
||||
protected function cloneVar($var)
|
||||
{
|
||||
if (null === self::$cloner) {
|
||||
if (!class_exists(ClassStub::class)) {
|
||||
if ($var instanceof Data) {
|
||||
return $var;
|
||||
}
|
||||
if (null === $this->cloner) {
|
||||
if (!class_exists(CutStub::class)) {
|
||||
throw new \LogicException(sprintf('The VarDumper component is needed for the %s() method. Install symfony/var-dumper version 3.4 or above.', __METHOD__));
|
||||
}
|
||||
|
||||
self::$cloner = new VarCloner();
|
||||
self::$cloner->setMaxItems(-1);
|
||||
$this->cloner = new VarCloner();
|
||||
$this->cloner->setMaxItems(-1);
|
||||
$this->cloner->addCasters($this->getCasters());
|
||||
}
|
||||
|
||||
return self::$cloner->cloneVar($var);
|
||||
return $this->cloner->cloneVar($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable[] The casters to add to the cloner
|
||||
*/
|
||||
protected function getCasters()
|
||||
{
|
||||
return array(
|
||||
'*' => function ($v, array $a, Stub $s, $isNested) {
|
||||
if (!$v instanceof Stub) {
|
||||
foreach ($a as $k => $v) {
|
||||
if (is_object($v) && !$v instanceof \DateTimeInterface && !$v instanceof Stub) {
|
||||
$a[$k] = new CutStub($v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $a;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1472,14 +1472,17 @@ class Process implements \IteratorAggregate
|
||||
$varCount = 0;
|
||||
$varCache = array();
|
||||
$cmd = preg_replace_callback(
|
||||
'/"(
|
||||
'/"(?:(
|
||||
[^"%!^]*+
|
||||
(?:
|
||||
(?: !LF! | "(?:\^[%!^])?+" )
|
||||
[^"%!^]*+
|
||||
)++
|
||||
)"/x',
|
||||
) | [^"]*+ )"/x',
|
||||
function ($m) use (&$envBackup, &$env, &$varCache, &$varCount, $uid) {
|
||||
if (!isset($m[1])) {
|
||||
return $m[0];
|
||||
}
|
||||
if (isset($varCache[$m[0]])) {
|
||||
return $varCache[$m[0]];
|
||||
}
|
||||
|
@ -1423,6 +1423,24 @@ class ProcessTest extends TestCase
|
||||
$this->assertSame($arg, $p->getOutput());
|
||||
}
|
||||
|
||||
public function testRawCommandLine()
|
||||
{
|
||||
$p = new Process(sprintf('"%s" -r %s "a" "" "b"', self::$phpBin, escapeshellarg('print_r($argv);')));
|
||||
$p->run();
|
||||
|
||||
$expected = <<<EOTXT
|
||||
Array
|
||||
(
|
||||
[0] => -
|
||||
[1] => a
|
||||
[2] =>
|
||||
[3] => b
|
||||
)
|
||||
|
||||
EOTXT;
|
||||
$this->assertSame($expected, $p->getOutput());
|
||||
}
|
||||
|
||||
public function provideEscapeArgument()
|
||||
{
|
||||
yield array('a"b%c%');
|
||||
|
@ -81,9 +81,9 @@ class AuthenticationProviderManager implements AuthenticationManagerInterface
|
||||
break;
|
||||
}
|
||||
} catch (AccountStatusException $e) {
|
||||
$e->setToken($token);
|
||||
$lastException = $e;
|
||||
|
||||
throw $e;
|
||||
break;
|
||||
} catch (AuthenticationException $e) {
|
||||
$lastException = $e;
|
||||
}
|
||||
|
@ -13,6 +13,9 @@ namespace Symfony\Component\Security\Core\Tests\Authentication;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
|
||||
use Symfony\Component\Security\Core\AuthenticationEvents;
|
||||
use Symfony\Component\Security\Core\Event\AuthenticationEvent;
|
||||
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
|
||||
use Symfony\Component\Security\Core\Exception\ProviderNotFoundException;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\AccountStatusException;
|
||||
@ -125,6 +128,50 @@ class AuthenticationProviderManagerTest extends TestCase
|
||||
$this->assertEquals('bar', $token->getCredentials());
|
||||
}
|
||||
|
||||
public function testAuthenticateDispatchesAuthenticationFailureEvent()
|
||||
{
|
||||
$token = new UsernamePasswordToken('foo', 'bar', 'key');
|
||||
$provider = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface')->getMock();
|
||||
$provider->expects($this->once())->method('supports')->willReturn(true);
|
||||
$provider->expects($this->once())->method('authenticate')->willThrowException($exception = new AuthenticationException());
|
||||
|
||||
$dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
|
||||
$dispatcher
|
||||
->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with(AuthenticationEvents::AUTHENTICATION_FAILURE, $this->equalTo(new AuthenticationFailureEvent($token, $exception)));
|
||||
|
||||
$manager = new AuthenticationProviderManager(array($provider));
|
||||
$manager->setEventDispatcher($dispatcher);
|
||||
|
||||
try {
|
||||
$manager->authenticate($token);
|
||||
$this->fail('->authenticate() should rethrow exceptions');
|
||||
} catch (AuthenticationException $e) {
|
||||
$this->assertSame($token, $exception->getToken());
|
||||
}
|
||||
}
|
||||
|
||||
public function testAuthenticateDispatchesAuthenticationSuccessEvent()
|
||||
{
|
||||
$token = new UsernamePasswordToken('foo', 'bar', 'key');
|
||||
|
||||
$provider = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface')->getMock();
|
||||
$provider->expects($this->once())->method('supports')->willReturn(true);
|
||||
$provider->expects($this->once())->method('authenticate')->willReturn($token);
|
||||
|
||||
$dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
|
||||
$dispatcher
|
||||
->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with(AuthenticationEvents::AUTHENTICATION_SUCCESS, $this->equalTo(new AuthenticationEvent($token)));
|
||||
|
||||
$manager = new AuthenticationProviderManager(array($provider));
|
||||
$manager->setEventDispatcher($dispatcher);
|
||||
|
||||
$this->assertSame($token, $manager->authenticate($token));
|
||||
}
|
||||
|
||||
protected function getAuthenticationProvider($supports, $token = null, $exception = null)
|
||||
{
|
||||
$provider = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface')->getMock();
|
||||
|
@ -18,6 +18,7 @@ CHANGELOG
|
||||
* setting the `checkDNS` option of the `Url` constraint to `true` is deprecated in favor of
|
||||
the `Url::CHECK_DNS_TYPE_*` constants values and will throw an exception in Symfony 4.0
|
||||
* added min/max amount of pixels check to `Image` constraint via `minPixels` and `maxPixels`
|
||||
* added a new "propertyPath" option to comparison constraints in order to get the value to compare from an array or object
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||
|
||||
@ -24,6 +25,7 @@ abstract class AbstractComparison extends Constraint
|
||||
{
|
||||
public $message;
|
||||
public $value;
|
||||
public $propertyPath;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@ -34,11 +36,18 @@ abstract class AbstractComparison extends Constraint
|
||||
$options = array();
|
||||
}
|
||||
|
||||
if (is_array($options) && !isset($options['value'])) {
|
||||
throw new ConstraintDefinitionException(sprintf(
|
||||
'The %s constraint requires the "value" option to be set.',
|
||||
get_class($this)
|
||||
));
|
||||
if (is_array($options)) {
|
||||
if (!isset($options['value']) && !isset($options['propertyPath'])) {
|
||||
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires either the "value" or "propertyPath" option to be set.', get_class($this)));
|
||||
}
|
||||
|
||||
if (isset($options['value']) && isset($options['propertyPath'])) {
|
||||
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "value" or "propertyPath" options to be set, not both.', get_class($this)));
|
||||
}
|
||||
|
||||
if (isset($options['propertyPath']) && !class_exists(PropertyAccess::class)) {
|
||||
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option.', get_class($this)));
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($options);
|
||||
|
@ -11,8 +11,12 @@
|
||||
|
||||
namespace Symfony\Component\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
@ -23,6 +27,13 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
*/
|
||||
abstract class AbstractComparisonValidator extends ConstraintValidator
|
||||
{
|
||||
private $propertyAccessor;
|
||||
|
||||
public function __construct(PropertyAccessor $propertyAccessor = null)
|
||||
{
|
||||
$this->propertyAccessor = $propertyAccessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -36,7 +47,19 @@ abstract class AbstractComparisonValidator extends ConstraintValidator
|
||||
return;
|
||||
}
|
||||
|
||||
$comparedValue = $constraint->value;
|
||||
if ($path = $constraint->propertyPath) {
|
||||
if (null === $object = $this->context->getObject()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$comparedValue = $this->getPropertyAccessor()->getValue($object, $path);
|
||||
} catch (NoSuchPropertyException $e) {
|
||||
throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: %s', $path, get_class($constraint), $e->getMessage()), 0, $e);
|
||||
}
|
||||
} else {
|
||||
$comparedValue = $constraint->value;
|
||||
}
|
||||
|
||||
// Convert strings to DateTimes if comparing another DateTime
|
||||
// This allows to compare with any date/time value supported by
|
||||
@ -63,6 +86,15 @@ abstract class AbstractComparisonValidator extends ConstraintValidator
|
||||
}
|
||||
}
|
||||
|
||||
private function getPropertyAccessor()
|
||||
{
|
||||
if (null === $this->propertyAccessor) {
|
||||
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
|
||||
}
|
||||
|
||||
return $this->propertyAccessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the two given values to find if their relationship is valid.
|
||||
*
|
||||
|
@ -13,6 +13,7 @@ namespace Symfony\Component\Validator\Tests\Constraints;
|
||||
|
||||
use Symfony\Component\Intl\Util\IntlTestHelper;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
|
||||
|
||||
class ComparisonTest_Class
|
||||
@ -28,6 +29,11 @@ class ComparisonTest_Class
|
||||
{
|
||||
return (string) $this->value;
|
||||
}
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,12 +82,25 @@ abstract class AbstractComparisonValidatorTestCase extends ConstraintValidatorTe
|
||||
/**
|
||||
* @dataProvider provideInvalidConstraintOptions
|
||||
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
|
||||
* @expectedExceptionMessage requires either the "value" or "propertyPath" option to be set.
|
||||
*/
|
||||
public function testThrowsConstraintExceptionIfNoValueOrProperty($options)
|
||||
public function testThrowsConstraintExceptionIfNoValueOrPropertyPath($options)
|
||||
{
|
||||
$this->createConstraint($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
|
||||
* @expectedExceptionMessage requires only one of the "value" or "propertyPath" options to be set, not both.
|
||||
*/
|
||||
public function testThrowsConstraintExceptionIfBothValueAndPropertyPath()
|
||||
{
|
||||
$this->createConstraint((array(
|
||||
'value' => 'value',
|
||||
'propertyPath' => 'propertyPath',
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideAllValidComparisons
|
||||
*
|
||||
@ -113,11 +132,75 @@ abstract class AbstractComparisonValidatorTestCase extends ConstraintValidatorTe
|
||||
return $comparisons;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideValidComparisonsToPropertyPath
|
||||
*/
|
||||
public function testValidComparisonToPropertyPath($comparedValue)
|
||||
{
|
||||
$constraint = $this->createConstraint(array('propertyPath' => 'value'));
|
||||
|
||||
$object = new ComparisonTest_Class(5);
|
||||
|
||||
$this->setObject($object);
|
||||
|
||||
$this->validator->validate($comparedValue, $constraint);
|
||||
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideValidComparisonsToPropertyPath
|
||||
*/
|
||||
public function testValidComparisonToPropertyPathOnArray($comparedValue)
|
||||
{
|
||||
$constraint = $this->createConstraint(array('propertyPath' => '[root][value]'));
|
||||
|
||||
$this->setObject(array('root' => array('value' => 5)));
|
||||
|
||||
$this->validator->validate($comparedValue, $constraint);
|
||||
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public function testNoViolationOnNullObjectWithPropertyPath()
|
||||
{
|
||||
$constraint = $this->createConstraint(array('propertyPath' => 'propertyPath'));
|
||||
|
||||
$this->setObject(null);
|
||||
|
||||
$this->validator->validate('some data', $constraint);
|
||||
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public function testInvalidValuePath()
|
||||
{
|
||||
$constraint = $this->createConstraint(array('propertyPath' => 'foo'));
|
||||
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(ConstraintDefinitionException::class);
|
||||
$this->expectExceptionMessage(sprintf('Invalid property path "foo" provided to "%s" constraint', get_class($constraint)));
|
||||
} else {
|
||||
$this->setExpectedException(ConstraintDefinitionException::class, sprintf('Invalid property path "foo" provided to "%s" constraint', get_class($constraint)));
|
||||
}
|
||||
|
||||
$object = new ComparisonTest_Class(5);
|
||||
|
||||
$this->setObject($object);
|
||||
|
||||
$this->validator->validate(5, $constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
abstract public function provideValidComparisons();
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
abstract public function provideValidComparisonsToPropertyPath();
|
||||
|
||||
/**
|
||||
* @dataProvider provideAllInvalidComparisons
|
||||
*
|
||||
|
@ -51,6 +51,16 @@ class EqualToValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function provideValidComparisonsToPropertyPath()
|
||||
{
|
||||
return array(
|
||||
array(5),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -54,6 +54,17 @@ class GreaterThanOrEqualValidatorTest extends AbstractComparisonValidatorTestCas
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function provideValidComparisonsToPropertyPath()
|
||||
{
|
||||
return array(
|
||||
array(5),
|
||||
array(6),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -50,6 +50,16 @@ class GreaterThanValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function provideValidComparisonsToPropertyPath()
|
||||
{
|
||||
return array(
|
||||
array(6),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -69,6 +69,16 @@ class IdenticalToValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
return $comparisons;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function provideValidComparisonsToPropertyPath()
|
||||
{
|
||||
return array(
|
||||
array(5),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -56,6 +56,17 @@ class LessThanOrEqualValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function provideValidComparisonsToPropertyPath()
|
||||
{
|
||||
return array(
|
||||
array(4),
|
||||
array(5),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -50,6 +50,16 @@ class LessThanValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function provideValidComparisonsToPropertyPath()
|
||||
{
|
||||
return array(
|
||||
array(4),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -50,6 +50,16 @@ class NotEqualToValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function provideValidComparisonsToPropertyPath()
|
||||
{
|
||||
return array(
|
||||
array(0),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -53,6 +53,16 @@ class NotIdenticalToValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function provideValidComparisonsToPropertyPath()
|
||||
{
|
||||
return array(
|
||||
array(0),
|
||||
);
|
||||
}
|
||||
|
||||
public function provideAllInvalidComparisons()
|
||||
{
|
||||
$this->setDefaultTimezone('UTC');
|
||||
|
@ -30,6 +30,7 @@
|
||||
"symfony/dependency-injection": "~3.4|~4.0",
|
||||
"symfony/expression-language": "~3.4|~4.0",
|
||||
"symfony/cache": "~3.4|~4.0",
|
||||
"symfony/property-access": "~3.4|~4.0",
|
||||
"doctrine/annotations": "~1.0",
|
||||
"doctrine/cache": "~1.0",
|
||||
"egulias/email-validator": "^1.2.8|~2.0"
|
||||
@ -48,6 +49,7 @@
|
||||
"symfony/yaml": "",
|
||||
"symfony/config": "",
|
||||
"egulias/email-validator": "Strict (RFC compliant) email validation",
|
||||
"symfony/property-access": "For accessing properties within comparison constraints",
|
||||
"symfony/expression-language": "For using the Expression validator"
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -60,11 +60,20 @@ class Caster
|
||||
}
|
||||
|
||||
if ($a) {
|
||||
static $publicProperties = array();
|
||||
|
||||
$i = 0;
|
||||
$prefixedKeys = array();
|
||||
foreach ($a as $k => $v) {
|
||||
if (isset($k[0]) && "\0" !== $k[0] && !property_exists($class, $k)) {
|
||||
$prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k;
|
||||
if (isset($k[0]) && "\0" !== $k[0]) {
|
||||
if (!isset($publicProperties[$class])) {
|
||||
foreach (get_class_vars($class) as $prop => $v) {
|
||||
$publicProperties[$class][$prop] = true;
|
||||
}
|
||||
}
|
||||
if (!isset($publicProperties[$class][$k])) {
|
||||
$prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k;
|
||||
}
|
||||
} elseif (isset($k[16]) && "\0" === $k[16] && 0 === strpos($k, "\0class@anonymous\0")) {
|
||||
$prefixedKeys[$i] = "\0".get_parent_class($class).'@anonymous'.strrchr($k, "\0");
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ class ExceptionCaster
|
||||
}
|
||||
|
||||
unset($a[$sPrefix.'file'], $a[$sPrefix.'line'], $a[$sPrefix.'trace']);
|
||||
$a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace);
|
||||
$a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs);
|
||||
|
||||
return $a;
|
||||
}
|
||||
@ -256,7 +256,7 @@ class ExceptionCaster
|
||||
$trace = array();
|
||||
}
|
||||
|
||||
if (!($filter & Caster::EXCLUDE_VERBOSE)) {
|
||||
if (!($filter & Caster::EXCLUDE_VERBOSE) && $trace) {
|
||||
if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) {
|
||||
self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']);
|
||||
}
|
||||
|
@ -209,15 +209,17 @@ abstract class AbstractCloner implements ClonerInterface
|
||||
});
|
||||
$this->filter = $filter;
|
||||
|
||||
if ($gc = gc_enabled()) {
|
||||
gc_disable();
|
||||
}
|
||||
try {
|
||||
$data = $this->doClone($var);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
restore_error_handler();
|
||||
$this->prevErrorHandler = null;
|
||||
|
||||
if (isset($e)) {
|
||||
throw $e;
|
||||
} finally {
|
||||
if ($gc) {
|
||||
gc_enable();
|
||||
}
|
||||
restore_error_handler();
|
||||
$this->prevErrorHandler = null;
|
||||
}
|
||||
|
||||
return new Data($data);
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\VarDumper\Tests\Caster;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\VarDumper\Caster\Caster;
|
||||
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
|
||||
use Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo;
|
||||
use Symfony\Component\VarDumper\Tests\Fixtures\NotLoadableClass;
|
||||
@ -77,13 +78,27 @@ Closure {
|
||||
\$b: & 123
|
||||
}
|
||||
file: "%sReflectionCasterTest.php"
|
||||
line: "67 to 67"
|
||||
line: "68 to 68"
|
||||
}
|
||||
EOTXT
|
||||
, $var
|
||||
);
|
||||
}
|
||||
|
||||
public function testClosureCasterExcludingVerbosity()
|
||||
{
|
||||
$var = function () {};
|
||||
|
||||
$expectedDump = <<<EOTXT
|
||||
Closure {
|
||||
class: "Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest"
|
||||
this: Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest { …}
|
||||
}
|
||||
EOTXT;
|
||||
|
||||
$this->assertDumpEquals($expectedDump, $var, Caster::EXCLUDE_VERBOSE);
|
||||
}
|
||||
|
||||
public function testReflectionParameter()
|
||||
{
|
||||
$var = new \ReflectionParameter(__NAMESPACE__.'\reflectionParameterFixture', 0);
|
||||
|
Reference in New Issue
Block a user