Upgraded to Symfony 2.1-beta2

This commit is contained in:
Polonkai Gergely
2012-07-15 14:56:31 +02:00
parent c1232c9792
commit bb7aee6fee
455 changed files with 21001 additions and 18480 deletions

View File

@@ -1,19 +0,0 @@
<?php
if (!@include __DIR__ . '/../vendor/.composer/autoload.php') {
die("You must set up the project dependencies, run the following commands:
wget http://getcomposer.org/composer.phar
php composer.phar install
");
}
spl_autoload_register(function($class) {
if (0 === strpos($class, 'Symfony\\Bundle\\MonologBundle')) {
$path = __DIR__.'/../'.implode('/', array_slice(explode('\\', $class), 3)).'.php';
if (!stream_resolve_include_path($path)) {
return false;
}
require_once $path;
return true;
}
});

View File

@@ -23,7 +23,8 @@
"monolog/monolog": "1.*"
},
"require-dev": {
"symfony/yaml": "2.1.*"
"symfony/yaml": "2.1.*",
"symfony/config": "2.1.*"
},
"autoload": {
"psr-0": { "Symfony\\Bundle\\MonologBundle": "" }

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="Tests/bootstrap.php">
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="MonologBundle for the Symfony Framework">
<directory>./Tests</directory>

View File

@@ -53,6 +53,8 @@ class Configuration implements ConfigurationInterface
->scalarNode('password')->defaultNull()->end()
->scalarNode('host')->defaultValue('localhost')->end()
->scalarNode('port')->defaultFalse()->end()
->scalarNode('timeout')->defaultValue(30)->end()
->scalarNode('source_ip')->defaultNull()->end()
->scalarNode('encryption')
->defaultNull()
->validate()

View File

@@ -76,7 +76,7 @@ class SwiftmailerExtension extends Extension
$config['port'] = 'ssl' === $config['encryption'] ? 465 : 25;
}
foreach (array('encryption', 'port', 'host', 'username', 'password', 'auth_mode') as $key) {
foreach (array('encryption', 'port', 'host', 'username', 'password', 'auth_mode', 'timeout', 'source_ip') as $key) {
$container->setParameter('swiftmailer.transport.smtp.'.$key, $config[$key]);
}

View File

@@ -19,6 +19,8 @@
<xsd:attribute name="port" type="xsd:string" />
<xsd:attribute name="encryption" type="encryption" />
<xsd:attribute name="auth-mode" type="auth_mode" />
<xsd:attribute name="timeout" type="xsd:string"/>
<xsd:attribute name="source-ip" type="xsd:string"/>
<xsd:attribute name="transport" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="delivery-address" type="xsd:string" />

View File

@@ -22,6 +22,8 @@
<call method="setUsername"><argument>%swiftmailer.transport.smtp.username%</argument></call>
<call method="setPassword"><argument>%swiftmailer.transport.smtp.password%</argument></call>
<call method="setAuthMode"><argument>%swiftmailer.transport.smtp.auth_mode%</argument></call>
<call method="setTimeout"><argument>%swiftmailer.transport.smtp.timeout%</argument></call>
<call method="setSourceIp"><argument>%swiftmailer.transport.smtp.source_ip%</argument></call>
</service>
</services>
</container>

View File

@@ -18,8 +18,13 @@
],
"require": {
"php": ">=5.3.2",
"swiftmailer/swiftmailer": ">=4.1.8,<4.2-dev",
"symfony/swiftmailer-bridge": "self.version"
"swiftmailer/swiftmailer": ">=4.2.0,<4.3-dev",
"symfony/swiftmailer-bridge": "2.1.*"
},
"require-dev": {
"symfony/dependency-injection": "2.1.*",
"symfony/http-kernel": "2.1.*",
"symfony/config": "2.1.*"
},
"autoload": {
"psr-0": { "Symfony\\Bundle\\SwiftmailerBundle": "" }
@@ -27,7 +32,7 @@
"target-dir": "Symfony/Bundle/SwiftmailerBundle",
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
"dev-master": "2.1-dev"
}
}
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="SwiftmailerBundle for the Symfony Framework">
<directory>./Tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>.</directory>
<exclude>
<directory>./Resources</directory>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@@ -65,6 +65,52 @@
After: `$request->getLocale()`
### HttpFoundation
* [BC BREAK] The current locale for the user is not stored anymore in the session
You can simulate the old behavior by registering a listener that looks like the following, if the paramater which handle locale value in the request is `_locale`:
```
namespace XXX;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LocaleListener implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale = 'en')
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
if ($locale = $request->attributes->get('_locale')) {
$request->getSession()->set('_locale', $locale);
} else {
$request->setDefaultLocale($request->getSession()->get('_locale', $this->defaultLocale));
}
}
static public function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
```
### Security
* `Symfony\Component\Security\Core\User\UserInterface::equals()` has moved to
@@ -193,6 +239,40 @@
public function finishView(FormViewInterface $view, FormInterface $form, array $options)
```
* If you previously inherited from `FieldType`, you should now inherit from
`FormType`. You should also set the option `compound` to `false` if your field
is not supposed to contain child fields.
`FieldType` was deprecated and will be removed in Symfony 2.3.
Before:
```
public function getParent(array $options)
{
return 'field';
}
```
After:
```
public function getParent()
{
return 'form';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'compound' => false,
));
}
```
The changed signature of `getParent()` is explained in the next step.
The new method `setDefaultOptions` is described in the section "Deprecations".
* No options are passed to `getParent()` of `FormTypeInterface` anymore. If
you previously dynamically inherited from `FormType` or `FieldType`, you can now
dynamically set the "compound" option instead.
@@ -460,6 +540,22 @@
{% endfor %}
```
* Creation of default labels has been moved to the view layer. You will need
to incorporate this logic into any custom `form_label` templates to
accommodate those cases when the `label` option has not been explicitly
set.
```
{% block form_label %}
{% if label is empty %}
{% set label = name|humanize %}
{% endif %}
{# ... #}
{% endblock %}
````
#### Other BC Breaks
* The order of the first two arguments of the methods `createNamed` and
@@ -1008,7 +1104,7 @@
### Serializer
* The key names created by the `GetSetMethodNormalizer` have changed from
from all lowercased to camelCased (e.g. `mypropertyvalue` to `myPropertyValue`).
all lowercased to camelCased (e.g. `mypropertyvalue` to `myPropertyValue`).
* The `item` element is now converted to an array when deserializing XML.

View File

@@ -15,8 +15,3 @@ if (!function_exists('intl_get_error_code')) {
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
AnnotationRegistry::registerFile(__DIR__.'/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');
if (is_file(__DIR__.'/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php')) {
require_once __DIR__.'/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php';
Swift::registerAutoload(__DIR__.'/vendor/swiftmailer/swiftmailer/lib/swift_init.php');
}

View File

@@ -67,6 +67,7 @@
"SessionHandlerInterface": "src/Symfony/Component/HttpFoundation/Resources/stubs"
}
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"

View File

@@ -17,7 +17,7 @@
],
"require": {
"php": ">=5.3.3",
"swiftmailer/swiftmailer": ">=4.1.2,<4.2-dev"
"swiftmailer/swiftmailer": ">=4.2.0,<4.3-dev"
},
"suggest": {
"symfony/http-kernel": "self.version"

View File

@@ -91,6 +91,13 @@ class FormExtension extends \Twig_Extension
);
}
public function getFilters()
{
return array(
'humanize' => new \Twig_Filter_Function(__NAMESPACE__.'\humanize'),
);
}
public function isChoiceGroup($label)
{
return FormUtil::isChoiceGroup($label);
@@ -364,3 +371,8 @@ class FormExtension extends \Twig_Extension
return $blocks;
}
}
function humanize($text)
{
return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text))));
}

View File

@@ -225,6 +225,9 @@
{% if required %}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
{% endif %}
{% if label is empty %}
{% set label = name|humanize %}
{% endif %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans({}, translation_domain) }}</label>
{% endspaceless %}
{% endblock form_label %}

View File

@@ -32,3 +32,9 @@ CHANGELOG
`gc_probability`/`gc_divisor` chance of being run. The `gc_maxlifetime` defines
how long a session can idle for. It is different from cookie lifetime which
declares how long a cookie can be stored on the remote client.
* Removed 'auto_start' configuration parameter from session config. The session will
start on demand.
* [BC BREAK] TemplateNameParser::parseFromFilename() has been moved to a dedicated
parser: TemplateFilenameParser::parse().
* [BC BREAK] Kernel parameters are replaced by their value whereever they appear
in Route patterns, requirements and defaults. Use '%%' as the escaped value for '%'.

View File

@@ -13,7 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateNameParser;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
/**
@@ -31,11 +31,11 @@ class TemplateFinder implements TemplateFinderInterface
/**
* Constructor.
*
* @param KernelInterface $kernel A KernelInterface instance
* @param TemplateNameParser $parser A TemplateNameParser instance
* @param string $rootDir The directory where global templates can be stored
* @param KernelInterface $kernel A KernelInterface instance
* @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance
* @param string $rootDir The directory where global templates can be stored
*/
public function __construct(KernelInterface $kernel, TemplateNameParser $parser, $rootDir)
public function __construct(KernelInterface $kernel, TemplateNameParserInterface $parser, $rootDir)
{
$this->kernel = $kernel;
$this->parser = $parser;
@@ -78,7 +78,7 @@ class TemplateFinder implements TemplateFinderInterface
if (is_dir($dir)) {
$finder = new Finder();
foreach ($finder->files()->followLinks()->in($dir) as $file) {
$template = $this->parser->parseFromFilename($file->getRelativePathname());
$template = $this->parser->parse($file->getRelativePathname());
if (false !== $template) {
$templates[] = $template;
}

View File

@@ -84,6 +84,11 @@ class Client extends BaseClient
/**
* Returns the script to execute when the request must be insulated.
*
* It assumes that the autoloader is named 'autoload.php' and that it is
* stored in the same directory as the kernel (this is the case for the
* Symfony Standard Edition). If this is not your case, create your own
* client and override this method.
*
* @param Request $request A Request instance
*
* @return string The script content
@@ -94,11 +99,22 @@ class Client extends BaseClient
$request = str_replace("'", "\\'", serialize($request));
$r = new \ReflectionObject($this->kernel);
$autoloader = dirname($r->getFileName()).'/autoload.php';
if (is_file($autoloader)) {
$autoloader = str_replace("'", "\\'", $autoloader);
} else {
$autoloader = '';
}
$path = str_replace("'", "\\'", $r->getFileName());
return <<<EOF
<?php
if ('$autoloader') {
require_once '$autoloader';
}
require_once '$path';
\$kernel = unserialize('$kernel');

View File

@@ -37,6 +37,11 @@ class CacheWarmupCommand extends ContainerAwareCommand
The <info>%command.name%</info> command warms up the cache.
Before running this command, the cache must be empty.
This command does not generate the classes cache (as when executing this
command, too many classes that should be part of the cache are already loaded
in memory). Use <comment>curl</comment> or any other similar tool to warm up
the classes cache if you want.
EOF
)
;

View File

@@ -17,6 +17,7 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* A console command for dumping available configuration reference
@@ -102,6 +103,12 @@ EOF
'" does not have it\'s getConfiguration() method setup');
}
if (!$configuration instanceof ConfigurationInterface) {
throw new \LogicException(
'Configuration class "'.get_class($configuration).
'" should implement ConfigurationInterface in order to be dumpable');
}
$rootNode = $configuration->getConfigTreeBuilder()->buildTree();
$output->writeln($message);

View File

@@ -32,7 +32,7 @@ class ContainerDebugCommand extends ContainerAwareCommand
/**
* @var ContainerBuilder
*/
private $containerBuilder;
protected $containerBuilder;
/**
* @see Command
@@ -209,7 +209,7 @@ EOF
*
* @return \Symfony\Component\DependencyInjection\Definition|\Symfony\Component\DependencyInjection\Alias
*/
private function resolveServiceDefinition($serviceId)
protected function resolveServiceDefinition($serviceId)
{
if ($this->containerBuilder->hasDefinition($serviceId)) {
return $this->containerBuilder->getDefinition($serviceId);

View File

@@ -74,11 +74,13 @@ EOF
}
}
protected function outputRoutes(OutputInterface $output)
protected function outputRoutes(OutputInterface $output, $routes = null)
{
$routes = array();
foreach ($this->getContainer()->get('router')->getRouteCollection()->all() as $name => $route) {
$routes[$name] = $route->compile();
if (null === $routes) {
$routes = array();
foreach ($this->getContainer()->get('router')->getRouteCollection()->all() as $name => $route) {
$routes[$name] = $route->compile();
}
}
$output->writeln($this->getHelper('formatter')->formatSection('router', 'Current routes'));

View File

@@ -46,7 +46,21 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->scalarNode('charset')->info('general configuration')->end()
->scalarNode('charset')
->defaultNull()
->beforeNormalization()
->ifTrue(function($v) { return null !== $v; })
->then(function($v) {
$message = 'The charset setting is deprecated. Just remove it from your configuration file.';
if ('UTF-8' !== $v) {
$message .= sprintf(' You need to define a getCharset() method in your Application Kernel class that returns "%s".', $v);
}
throw new \RuntimeException($message);
})
->end()
->end()
->scalarNode('trust_proxy_headers')->defaultFalse()->end()
->scalarNode('secret')->isRequired()->end()
->scalarNode('ide')->defaultNull()->end()
@@ -176,7 +190,16 @@ class Configuration implements ConfigurationInterface
->info('session configuration')
->canBeUnset()
->children()
->booleanNode('auto_start')->defaultFalse()->end()
->booleanNode('auto_start')
->info('DEPRECATED! Session starts on demand')
->defaultNull()
->beforeNormalization()
->ifTrue(function($v) { return null !== $v; })
->then(function($v) {
throw new \RuntimeException('The auto_start setting is deprecated. Just remove it from your configuration file.');
})
->end()
->end()
->scalarNode('storage_id')->defaultValue('session.storage.native')->end()
->scalarNode('handler_id')->defaultValue('session.handler.native_file')->end()
->scalarNode('name')->end()

View File

@@ -60,9 +60,6 @@ class FrameworkExtension extends Extension
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
if (isset($config['charset'])) {
$container->setParameter('kernel.charset', $config['charset']);
}
$container->setParameter('kernel.secret', $config['secret']);
$container->setParameter('kernel.trust_proxy_headers', $config['trust_proxy_headers']);
@@ -119,17 +116,12 @@ class FrameworkExtension extends Extension
'Symfony\\Component\\Config\\FileLocator',
'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface',
'Symfony\\Component\\EventDispatcher\\EventDispatcher',
'Symfony\\Component\\EventDispatcher\\Event',
'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface',
'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher',
'Symfony\\Component\\HttpKernel\\HttpKernel',
'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener',
'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener',
'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver',
'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface',
'Symfony\\Component\\HttpKernel\\Event\\KernelEvent',
'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent',
'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent',
@@ -143,7 +135,6 @@ class FrameworkExtension extends Extension
'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver',
// Cannot be included because annotations will parse the big compiled class file
// 'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller',
'Symfony\\Bundle\\FrameworkBundle\\HttpKernel',
));
}
@@ -266,13 +257,8 @@ class FrameworkExtension extends Extension
$container->setParameter('request_listener.https_port', $config['https_port']);
$this->addClassesToCompile(array(
'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface',
'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface',
'Symfony\\Component\\Routing\\RouterInterface',
'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
'Symfony\\Component\\Routing\\Generator\\UrlGenerator',
'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface',
'Symfony\\Component\\Routing\\RequestContextAwareInterface',
'Symfony\\Component\\Routing\\RequestContext',
'Symfony\\Component\\Routing\\Router',
'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableUrlMatcher',
@@ -291,13 +277,10 @@ class FrameworkExtension extends Extension
{
$loader->load('session.xml');
// session
$container->getDefinition('session_listener')->addArgument($config['auto_start']);
// session storage
$container->setAlias('session.storage', $config['storage_id']);
$options = array();
foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'auto_start', 'gc_maxlifetime', 'gc_probability', 'gc_divisor') as $key) {
foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'gc_maxlifetime', 'gc_probability', 'gc_divisor') as $key) {
if (isset($config[$key])) {
$options[$key] = $config[$key];
}
@@ -319,7 +302,6 @@ class FrameworkExtension extends Extension
$this->addClassesToCompile(array(
'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SessionListener',
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface',
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage',
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\FileSessionHandler',
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy',
@@ -411,14 +393,6 @@ class FrameworkExtension extends Extension
$this->addClassesToCompile(array(
'Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables',
'Symfony\\Bundle\\FrameworkBundle\\Templating\\EngineInterface',
'Symfony\\Component\\Templating\\StreamingEngineInterface',
'Symfony\\Component\\Templating\\TemplateNameParserInterface',
'Symfony\\Component\\Templating\\TemplateNameParser',
'Symfony\\Component\\Templating\\EngineInterface',
'Symfony\\Component\\Config\\FileLocatorInterface',
'Symfony\\Component\\Templating\\TemplateReferenceInterface',
'Symfony\\Component\\Templating\\TemplateReference',
'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateReference',
'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateNameParser',
$container->findDefinition('templating.locator')->getClass(),
@@ -426,9 +400,6 @@ class FrameworkExtension extends Extension
if (in_array('php', $config['engines'], true)) {
$this->addClassesToCompile(array(
'Symfony\\Component\\Templating\\PhpEngine',
'Symfony\\Component\\Templating\\Loader\\LoaderInterface',
'Symfony\\Component\\Templating\\Storage\\Storage',
'Symfony\\Component\\Templating\\Storage\\FileStorage',
'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine',
'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader',

View File

@@ -33,15 +33,9 @@ class SessionListener implements EventSubscriberInterface
*/
private $container;
/**
* @var boolean
*/
private $autoStart;
public function __construct(ContainerInterface $container, $autoStart = false)
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->autoStart = $autoStart;
}
public function onKernelRequest(GetResponseEvent $event)
@@ -50,20 +44,12 @@ class SessionListener implements EventSubscriberInterface
return;
}
if (!$this->container->has('session')) {
return;
}
$request = $event->getRequest();
if ($request->hasSession()) {
if (!$this->container->has('session') || $request->hasSession()) {
return;
}
$request->setSession($session = $this->container->get('session'));
if ($this->autoStart || $request->hasPreviousSession()) {
$session->start();
}
$request->setSession($this->container->get('session'));
}
static public function getSubscribedEvents()

View File

@@ -32,7 +32,7 @@ abstract class HttpCache extends BaseHttpCache
* Constructor.
*
* @param HttpKernelInterface $kernel An HttpKernelInterface instance
* @param String $cacheDir The cache directory (default used if null)
* @param string $cacheDir The cache directory (default used if null)
*/
public function __construct(HttpKernelInterface $kernel, $cacheDir = null)
{

View File

@@ -21,7 +21,7 @@
* @author: Albert Jessurum <ajessu@gmail.com>
*/
if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['REQUEST_URI'])) {
if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_NAME'])) {
return false;
}

View File

@@ -89,6 +89,7 @@
<tag name="kernel.event_subscriber" />
<tag name="monolog.logger" channel="request" />
<argument type="service" id="router" />
<argument type="service" id="router.request_context" on-invalid="ignore" />
<argument type="service" id="logger" on-invalid="ignore" />
</service>
</services>

View File

@@ -21,6 +21,7 @@
<xsd:element name="annotations" type="annotations" minOccurs="0" maxOccurs="1" />
</xsd:all>
<!-- charset is deprecated and will be removed in 2.2 -->
<xsd:attribute name="charset" type="xsd:string" />
<xsd:attribute name="trust-proxy-headers" type="xsd:string" />
<xsd:attribute name="ide" type="xsd:string" />
@@ -84,9 +85,9 @@
<xsd:attribute name="domain" type="xsd:string" />
<xsd:attribute name="secure" type="xsd:boolean" />
<xsd:attribute name="httponly" type="xsd:boolean" />
<xsd:attribute name="auto-start" type="xsd:boolean" />
<!-- end of deprecated attributes -->
<xsd:attribute name="cache-limiter" type="xsd:string" />
<xsd:attribute name="auto-start" type="xsd:boolean" />
<xsd:attribute name="gc-maxlifetime" type="xsd:string" />
<xsd:attribute name="gc-divisor" type="xsd:string" />
<xsd:attribute name="gc-probability" type="xsd:string" />

View File

@@ -7,6 +7,7 @@
<parameters>
<parameter key="templating.engine.delegating.class">Symfony\Bundle\FrameworkBundle\Templating\DelegatingEngine</parameter>
<parameter key="templating.name_parser.class">Symfony\Bundle\FrameworkBundle\Templating\TemplateNameParser</parameter>
<parameter key="templating.filename_parser.class">Symfony\Bundle\FrameworkBundle\Templating\TemplateFilenameParser</parameter>
<parameter key="templating.cache_warmer.template_paths.class">Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplatePathsCacheWarmer</parameter>
<parameter key="templating.locator.class">Symfony\Bundle\FrameworkBundle\Templating\Loader\TemplateLocator</parameter>
<parameter key="templating.loader.filesystem.class">Symfony\Bundle\FrameworkBundle\Templating\Loader\FilesystemLoader</parameter>
@@ -25,6 +26,8 @@
<argument type="service" id="kernel" />
</service>
<service id="templating.filename_parser" class="%templating.filename_parser.class%" />
<service id="templating.locator" class="%templating.locator.class%" public="false">
<argument type="service" id="file_locator" />
<argument>%kernel.cache_dir%</argument>
@@ -32,7 +35,7 @@
<service id="templating.finder" class="%templating.finder.class%" public="false">
<argument type="service" id="kernel" />
<argument type="service" id="templating.name_parser" />
<argument type="service" id="templating.filename_parser" />
<argument>%kernel.root_dir%/Resources</argument>
</service>

View File

@@ -1,3 +1,4 @@
<?php if ($required) { $label_attr['class'] = trim((isset($label_attr['class']) ? $label_attr['class'] : '').' required'); } ?>
<?php if (!$compound) { $label_attr['for'] = $id; } ?>
<?php if (!$label) { $label = $view['form']->humanize($name); } ?>
<label <?php foreach ($label_attr as $k => $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo $view->escape($view['translator']->trans($label, array(), $translation_domain)) ?></label>

View File

@@ -16,6 +16,8 @@ use Symfony\Component\Routing\RequestContext;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* This Router only creates the Loader only when the cache is empty.
@@ -72,9 +74,12 @@ class Router extends BaseRouter implements WarmableInterface
}
/**
* Replaces placeholders with service container parameter values in route defaults and requirements.
* Replaces placeholders with service container parameter values in:
* - the route defaults,
* - the route requirements,
* - the route pattern.
*
* @param $collection
* @param RouteCollection $collection
*/
private function resolveParameters(RouteCollection $collection)
{
@@ -83,27 +88,60 @@ class Router extends BaseRouter implements WarmableInterface
$this->resolveParameters($route);
} else {
foreach ($route->getDefaults() as $name => $value) {
if (!$value || '%' !== $value[0] || '%' !== substr($value, -1)) {
continue;
}
$key = substr($value, 1, -1);
if ($this->container->hasParameter($key)) {
$route->setDefault($name, $this->container->getParameter($key));
}
$route->setDefault($name, $this->resolveString($value));
}
foreach ($route->getRequirements() as $name => $value) {
if (!$value || '%' !== $value[0] || '%' !== substr($value, -1)) {
continue;
}
$key = substr($value, 1, -1);
if ($this->container->hasParameter($key)) {
$route->setRequirement($name, $this->container->getParameter($key));
}
$route->setRequirement($name, $this->resolveString($value));
}
$route->setPattern($this->resolveString($route->getPattern()));
}
}
}
/**
* Replaces placeholders with the service container parameters in the given string.
*
* @param string $value The source string which might contain %placeholders%
*
* @return string A string where the placeholders have been replaced.
*
* @throws ParameterNotFoundException When a placeholder does not exist as a container parameter
* @throws RuntimeException When a container value is not a string or a numeric value
*/
private function resolveString($value)
{
$container = $this->container;
$escapedValue = preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($container, $value) {
// skip %%
if (!isset($match[1])) {
return '%%';
}
$key = strtolower($match[1]);
if (!$container->hasParameter($key)) {
throw new ParameterNotFoundException($key);
}
$resolved = $container->getParameter($key);
if (is_string($resolved) || is_numeric($resolved)) {
return (string) $resolved;
}
throw new RuntimeException(sprintf(
'A string value must be composed of strings and/or numbers,' .
'but found parameter "%s" of type %s inside string value "%s".',
$key,
gettype($resolved),
$value)
);
}, $value);
return str_replace('%%', '%', $escapedValue);
}
}

View File

@@ -314,6 +314,11 @@ class FormHelper extends Helper
return trim($this->engine->render($template, $variables));
}
public function humanize($text)
{
return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text))));
}
public function getName()
{
return 'form';

View File

@@ -0,0 +1,40 @@
<?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\Bundle\FrameworkBundle\Templating;
use Symfony\Component\Templating\TemplateNameParserInterface;
/**
* TemplateFilenameParser converts template filenames to
* TemplateReferenceInterface instances.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TemplateFilenameParser implements TemplateNameParserInterface
{
/**
* {@inheritdoc}
*/
public function parse($file)
{
$parts = explode('/', strtr($file, '\\', '/'));
$elements = explode('.', array_pop($parts));
if (3 > count($elements)) {
return false;
}
$engine = array_pop($elements);
$format = array_pop($elements);
return new TemplateReference('', implode('/', $parts), implode('.', $elements), $format, $engine);
}
}

View File

@@ -11,7 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Templating;
use Symfony\Component\Templating\TemplateNameParser as BaseTemplateNameParser;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Symfony\Component\Templating\TemplateReferenceInterface;
use Symfony\Component\HttpKernel\KernelInterface;
@@ -22,7 +22,7 @@ use Symfony\Component\HttpKernel\KernelInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TemplateNameParser extends BaseTemplateNameParser
class TemplateNameParser implements TemplateNameParserInterface
{
protected $kernel;
protected $cache;
@@ -80,26 +80,4 @@ class TemplateNameParser extends BaseTemplateNameParser
return $this->cache[$name] = $template;
}
/**
* Convert a filename to a template.
*
* @param string $file The filename
*
* @return TemplateReferenceInterface A template
*/
public function parseFromFilename($file)
{
$parts = explode('/', strtr($file, '\\', '/'));
$elements = explode('.', array_pop($parts));
if (3 > count($elements)) {
return false;
}
$engine = array_pop($elements);
$format = array_pop($elements);
return new TemplateReference('', implode('/', $parts), implode('.', $elements), $format, $engine);
}
}

View File

@@ -12,7 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\CacheWarmer;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateNameParser;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateFilenameParser;
use Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplateFinder;
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\BaseBundle\BaseBundle;
@@ -38,7 +38,7 @@ class TemplateFinderTest extends TestCase
->will($this->returnValue(array('BaseBundle' => new BaseBundle())))
;
$parser = new TemplateNameParser($kernel);
$parser = new TemplateFilenameParser($kernel);
$finder = new TemplateFinder($kernel, $parser, __DIR__.'/../Fixtures/Resources');

View File

@@ -3,7 +3,6 @@
$container->loadFromExtension('framework', array(
'secret' => 's3cr3t',
'session' => array(
'auto_start' => true,
'storage_id' => 'session.storage.native',
'handler_id' => 'session.handler.native_file',
'name' => '_SYMFONY',

View File

@@ -3,7 +3,6 @@
$container->loadFromExtension('framework', array(
'secret' => 's3cr3t',
'session' => array(
'auto_start' => true,
'storage_id' => 'session.storage.native',
'handler_id' => 'session.handler.native_file',
'name' => '_SYMFONY',

View File

@@ -19,7 +19,6 @@ $container->loadFromExtension('framework', array(
'type' => 'xml',
),
'session' => array(
'auto_start' => true,
'storage_id' => 'session.storage.native',
'handler_id' => 'session.handler.native_file',
'name' => '_SYMFONY',

View File

@@ -7,6 +7,6 @@
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config secret="s3cr3t">
<framework:session auto-start="true" storage-id="session.storage.native" handler-id="session.handler.native_file" name="_SYMFONY" lifetime="2012" path="/sf2" domain="sf2.example.com" secure="false" httponly="false" cookie-lifetime="86400" cookie-path="/" cookie-domain="example.com" cookie-secure="true" cookie-httponly="true" />
<framework:session storage-id="session.storage.native" handler-id="session.handler.native_file" name="_SYMFONY" lifetime="2012" path="/sf2" domain="sf2.example.com" secure="false" httponly="false" cookie-lifetime="86400" cookie-path="/" cookie-domain="example.com" cookie-secure="true" cookie-httponly="true" />
</framework:config>
</container>

View File

@@ -7,6 +7,6 @@
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config secret="s3cr3t">
<framework:session auto-start="true" storage-id="session.storage.native" handler-id="session.handler.native_file" name="_SYMFONY" lifetime="2012" path="/sf2" domain="sf2.example.com" secure="false" httponly="false" cookie-lifetime="86400" cookie-path="/" cookie-httponly="true" />
<framework:session storage-id="session.storage.native" handler-id="session.handler.native_file" name="_SYMFONY" lifetime="2012" path="/sf2" domain="sf2.example.com" secure="false" httponly="false" cookie-lifetime="86400" cookie-path="/" cookie-httponly="true" />
</framework:config>
</container>

View File

@@ -12,7 +12,7 @@
<framework:esi enabled="true" />
<framework:profiler only-exceptions="true" />
<framework:router resource="%kernel.root_dir%/config/routing.xml" type="xml" />
<framework:session auto-start="true" gc-maxlifetime="90000" gc-probability="1" gc-divisor="108" storage-id="session.storage.native" handler-id="session.handler.native_file" name="_SYMFONY" cookie-lifetime="86400" cookie-path="/" cookie-domain="example.com" cookie-secure="true" cookie-httponly="true" save-path="/path/to/sessions" />
<framework:session gc-maxlifetime="90000" gc-probability="1" gc-divisor="108" storage-id="session.storage.native" handler-id="session.handler.native_file" name="_SYMFONY" cookie-lifetime="86400" cookie-path="/" cookie-domain="example.com" cookie-secure="true" cookie-httponly="true" save-path="/path/to/sessions" />
<framework:templating assets-version="SomeVersionScheme" cache="/path/to/cache" >
<framework:loader>loader.foo</framework:loader>
<framework:loader>loader.bar</framework:loader>

View File

@@ -1,7 +1,6 @@
framework:
secret: s3cr3t
session:
auto_start: true
storage_id: session.storage.native
handler_id: session.handler.native_file
name: _SYMFONY

View File

@@ -1,7 +1,6 @@
framework:
secret: s3cr3t
session:
auto_start: true
storage_id: session.storage.native
handler_id: session.handler.native_file
name: _SYMFONY

View File

@@ -13,7 +13,6 @@ framework:
resource: %kernel.root_dir%/config/routing.xml
type: xml
session:
auto_start: true
storage_id: session.storage.native
handler_id: session.handler.native_file
name: _SYMFONY

View File

@@ -77,7 +77,6 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertTrue($container->hasDefinition('session'), '->registerSessionConfiguration() loads session.xml');
$this->assertEquals('fr', $container->getParameter('kernel.default_locale'));
$this->assertTrue($container->getDefinition('session_listener')->getArgument(1));
$this->assertEquals('session.storage.native', (string) $container->getAlias('session.storage'));
$this->assertEquals('session.handler.native_file', (string) $container->getAlias('session.handler'));

View File

@@ -1,5 +1,4 @@
framework:
charset: UTF-8
secret: test
csrf_protection:
enabled: true
@@ -9,7 +8,6 @@ framework:
test: ~
default_locale: en
session:
auto_start: true
storage_id: session.storage.mock_file
services:

View File

@@ -17,72 +17,151 @@ use Symfony\Component\Routing\RouteCollection;
class RoutingTest extends \PHPUnit_Framework_TestCase
{
public function testPlaceholders()
public function testDefaultsPlaceholders()
{
$routes = new RouteCollection();
$routes->add('foo', new Route('/foo', array(
'foo' => '%foo%',
'bar' => '%bar%',
'foobar' => 'foobar',
'foo1' => '%foo',
'foo2' => 'foo%',
'foo3' => 'f%o%o',
), array(
'foo' => '%foo%',
'bar' => '%bar%',
'foobar' => 'foobar',
'foo1' => '%foo',
'foo2' => 'foo%',
'foo3' => 'f%o%o',
)));
$routes->add('foo', new Route(
'/foo',
array(
'foo' => 'before_%parameter.foo%',
'bar' => '%parameter.bar%_after',
'baz' => '%%unescaped%%',
),
array(
)
));
$sc = $this->getServiceContainer($routes);
$sc->expects($this->at(1))->method('hasParameter')->will($this->returnValue(false));
$sc->expects($this->at(2))->method('hasParameter')->will($this->returnValue(true));
$sc->expects($this->at(3))->method('getParameter')->will($this->returnValue('bar'));
$sc->expects($this->at(4))->method('hasParameter')->will($this->returnValue(false));
$sc->expects($this->at(5))->method('hasParameter')->will($this->returnValue(true));
$sc->expects($this->at(6))->method('getParameter')->will($this->returnValue('bar'));
$sc->expects($this->at(1))->method('hasParameter')->will($this->returnValue(true));
$sc->expects($this->at(2))->method('getParameter')->will($this->returnValue('foo'));
$sc->expects($this->at(3))->method('hasParameter')->will($this->returnValue(true));
$sc->expects($this->at(4))->method('getParameter')->will($this->returnValue('bar'));
$router = new Router($sc, 'foo');
$route = $router->getRouteCollection()->get('foo');
$this->assertEquals('%foo%', $route->getDefault('foo'));
$this->assertEquals('bar', $route->getDefault('bar'));
$this->assertEquals('foobar', $route->getDefault('foobar'));
$this->assertEquals('%foo', $route->getDefault('foo1'));
$this->assertEquals('foo%', $route->getDefault('foo2'));
$this->assertEquals('f%o%o', $route->getDefault('foo3'));
$this->assertEquals(
array(
'foo' => 'before_foo',
'bar' => 'bar_after',
'baz' => '%unescaped%',
),
$route->getDefaults()
);
}
$this->assertEquals('%foo%', $route->getRequirement('foo'));
$this->assertEquals('bar', $route->getRequirement('bar'));
$this->assertEquals('foobar', $route->getRequirement('foobar'));
$this->assertEquals('%foo', $route->getRequirement('foo1'));
$this->assertEquals('foo%', $route->getRequirement('foo2'));
$this->assertEquals('f%o%o', $route->getRequirement('foo3'));
public function testRequirementsPlaceholders()
{
$routes = new RouteCollection();
$routes->add('foo', new Route(
'/foo',
array(
),
array(
'foo' => 'before_%parameter.foo%',
'bar' => '%parameter.bar%_after',
'baz' => '%%unescaped%%',
)
));
$sc = $this->getServiceContainer($routes);
$sc->expects($this->at(1))->method('hasParameter')->with('parameter.foo')->will($this->returnValue(true));
$sc->expects($this->at(2))->method('getParameter')->with('parameter.foo')->will($this->returnValue('foo'));
$sc->expects($this->at(3))->method('hasParameter')->with('parameter.bar')->will($this->returnValue(true));
$sc->expects($this->at(4))->method('getParameter')->with('parameter.bar')->will($this->returnValue('bar'));
$router = new Router($sc, 'foo');
$route = $router->getRouteCollection()->get('foo');
$this->assertEquals(
array(
'foo' => 'before_foo',
'bar' => 'bar_after',
'baz' => '%unescaped%',
),
$route->getRequirements()
);
}
public function testPatternPlaceholders()
{
$routes = new RouteCollection();
$routes->add('foo', new Route('/before/%parameter.foo%/after/%%unescaped%%'));
$sc = $this->getServiceContainer($routes);
$sc->expects($this->at(1))->method('hasParameter')->with('parameter.foo')->will($this->returnValue(true));
$sc->expects($this->at(2))->method('getParameter')->with('parameter.foo')->will($this->returnValue('foo'));
$router = new Router($sc, 'foo');
$route = $router->getRouteCollection()->get('foo');
$this->assertEquals(
'/before/foo/after/%unescaped%',
$route->getPattern()
);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException
* @expectedExceptionMessage You have requested a non-existent parameter "nope".
*/
public function testExceptionOnNonExistentParameter()
{
$routes = new RouteCollection();
$routes->add('foo', new Route('/%nope%'));
$sc = $this->getServiceContainer($routes);
$sc->expects($this->at(1))->method('hasParameter')->with('nope')->will($this->returnValue(false));
$router = new Router($sc, 'foo');
$router->getRouteCollection()->get('foo');
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage A string value must be composed of strings and/or numbers,but found parameter "object" of type object inside string value "/%object%".
*/
public function testExceptionOnNonStringParameter()
{
$routes = new RouteCollection();
$routes->add('foo', new Route('/%object%'));
$sc = $this->getServiceContainer($routes);
$sc->expects($this->at(1))->method('hasParameter')->with('object')->will($this->returnValue(true));
$sc->expects($this->at(2))->method('getParameter')->with('object')->will($this->returnValue(new \stdClass()));
$router = new Router($sc, 'foo');
$router->getRouteCollection()->get('foo');
}
private function getServiceContainer(RouteCollection $routes)
{
$sc = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
$sc
->expects($this->once())
->method('get')
->will($this->returnValue($this->getLoader($routes)))
;
return $sc;
}
private function getLoader(RouteCollection $routes)
{
$loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface');
$loader
->expects($this->any())
->method('load')
->will($this->returnValue($routes))
;
return $loader;
$sc = $this->getMock('Symfony\\Component\\DependencyInjection\\ContainerInterface');
$sc
->expects($this->once())
->method('get')
->will($this->returnValue($loader))
;
return $sc;
}
}

View File

@@ -0,0 +1,56 @@
<?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\Bundle\FrameworkBundle\Tests\Templating;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateFilenameParser;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
class TemplateFilenameParserTest extends TestCase
{
protected $parser;
protected function setUp()
{
$this->parser = new TemplateFilenameParser();
}
protected function tearDown()
{
$this->parser = null;
}
/**
* @dataProvider getFilenameToTemplateProvider
*/
public function testParseFromFilename($file, $ref)
{
$template = $this->parser->parse($file);
if ($ref === false) {
$this->assertFalse($template);
} else {
$this->assertEquals($template->getLogicalName(), $ref->getLogicalName());
}
}
public function getFilenameToTemplateProvider()
{
return array(
array('/path/to/section/name.format.engine', new TemplateReference('', '/path/to/section', 'name', 'format', 'engine')),
array('\\path\\to\\section\\name.format.engine', new TemplateReference('', '/path/to/section', 'name', 'format', 'engine')),
array('name.format.engine', new TemplateReference('', '', 'name', 'format', 'engine')),
array('name.format', false),
array('name', false),
);
}
}

View File

@@ -84,30 +84,4 @@ class TemplateNameParserTest extends TestCase
array('FooBundle:Post:foo:bar'),
);
}
/**
* @dataProvider getFilenameToTemplateProvider
*/
public function testParseFromFilename($file, $ref)
{
$template = $this->parser->parseFromFilename($file);
if ($ref === false) {
$this->assertFalse($template);
} else {
$this->assertEquals($template->getLogicalName(), $ref->getLogicalName());
}
}
public function getFilenameToTemplateProvider()
{
return array(
array('/path/to/section/name.format.engine', new TemplateReference('', '/path/to/section', 'name', 'format', 'engine')),
array('\\path\\to\\section\\name.format.engine', new TemplateReference('', '/path/to/section', 'name', 'format', 'engine')),
array('name.format.engine', new TemplateReference('', '', 'name', 'format', 'engine')),
array('name.format', false),
array('name', false),
);
}
}

View File

@@ -80,6 +80,45 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('no translation', $translator->trans('no translation'));
}
public function testGetLocale()
{
$request = $this->getMock('Symfony\Component\HttpFoundation\Request');
$request
->expects($this->once())
->method('getLocale')
->will($this->returnValue('en'))
;
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
$container
->expects($this->exactly(2))
->method('isScopeActive')
->with('request')
->will($this->onConsecutiveCalls(false, true))
;
$container
->expects($this->once())
->method('has')
->with('request')
->will($this->returnValue(true))
;
$container
->expects($this->once())
->method('get')
->with('request')
->will($this->returnValue($request))
;
$translator = new Translator($container, new MessageSelector());
$this->assertNull($translator->getLocale());
$this->assertSame('en', $translator->getLocale());
}
protected function getCatalogue($locale, $messages)
{
$catalogue = new MessageCatalogue($locale);

View File

@@ -65,7 +65,7 @@ class Translator extends BaseTranslator
*/
public function getLocale()
{
if (null === $this->locale && $this->container->has('request')) {
if (null === $this->locale && $this->container->isScopeActive('request') && $this->container->has('request')) {
$this->locale = $this->container->get('request')->getLocale();
}

View File

@@ -303,9 +303,11 @@ class MainConfiguration implements ConfigurationInterface
->example(array(
'memory' => array(
'name' => 'memory',
'users' => array(
'foo' => array('password' => 'foo', 'roles' => 'ROLE_USER'),
'bar' => array('password' => 'bar', 'roles' => '[ROLE_USER, ROLE_ADMIN]')
'memory' => array(
'users' => array(
'foo' => array('password' => 'foo', 'roles' => 'ROLE_USER'),
'bar' => array('password' => 'bar', 'roles' => '[ROLE_USER, ROLE_ADMIN]')
),
)
),
'entity' => array('entity' => array('class' => 'SecurityBundle:User', 'property' => 'username'))

View File

@@ -91,21 +91,14 @@ class SecurityExtension extends Extension
// add some required classes for compilation
$this->addClassesToCompile(array(
'Symfony\\Component\\Security\\Http\\Firewall',
'Symfony\\Component\\Security\\Http\\FirewallMapInterface',
'Symfony\\Component\\Security\\Core\\SecurityContext',
'Symfony\\Component\\Security\\Core\\SecurityContextInterface',
'Symfony\\Component\\Security\\Core\\User\\UserProviderInterface',
'Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationProviderManager',
'Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface',
'Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager',
'Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface',
'Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface',
'Symfony\\Bundle\\SecurityBundle\\Security\\FirewallMap',
'Symfony\\Bundle\\SecurityBundle\\Security\\FirewallContext',
'Symfony\\Component\\HttpFoundation\\RequestMatcher',
'Symfony\\Component\\HttpFoundation\\RequestMatcherInterface',
));
}
@@ -181,7 +174,6 @@ class SecurityExtension extends Extension
}
$this->addClassesToCompile(array(
'Symfony\\Component\\Security\\Http\\AccessMapInterface',
'Symfony\\Component\\Security\\Http\\AccessMap',
));
@@ -551,13 +543,14 @@ class SecurityExtension extends Extension
{
$exceptionListenerId = 'security.exception_listener.'.$id;
$listener = $container->setDefinition($exceptionListenerId, new DefinitionDecorator('security.exception_listener'));
$listener->replaceArgument(3, null === $defaultEntryPoint ? null : new Reference($defaultEntryPoint));
$listener->replaceArgument(3, $id);
$listener->replaceArgument(4, null === $defaultEntryPoint ? null : new Reference($defaultEntryPoint));
// access denied handler setup
if (isset($config['access_denied_handler'])) {
$listener->replaceArgument(5, new Reference($config['access_denied_handler']));
$listener->replaceArgument(6, new Reference($config['access_denied_handler']));
} elseif (isset($config['access_denied_url'])) {
$listener->replaceArgument(4, $config['access_denied_url']);
$listener->replaceArgument(5, $config['access_denied_url']);
}
return $exceptionListenerId;

View File

@@ -128,6 +128,7 @@
<service id="security.http_utils" class="%security.http_utils.class%" public="false">
<argument type="service" id="router" on-invalid="null" />
<argument type="service" id="router" on-invalid="null" />
</service>
<!-- Validator -->

View File

@@ -158,6 +158,7 @@
<argument type="service" id="security.context" />
<argument type="service" id="security.authentication.trust_resolver" />
<argument type="service" id="security.http_utils" />
<argument />
<argument type="service" id="security.authentication.entry_point" on-invalid="null" />
<argument>%security.access.denied_url%</argument>
<argument type="service" id="security.access.denied_handler" on-invalid="null" />

View File

@@ -1,5 +1,4 @@
framework:
charset: UTF-8
secret: test
csrf_protection:
enabled: true
@@ -9,7 +8,6 @@ framework:
test: ~
default_locale: en
session:
auto_start: true
storage_id: session.storage.mock_file
profiler: { only_exceptions: false }

View File

@@ -92,14 +92,12 @@ class TwigExtension extends Extension
$this->addClassesToCompile(array(
'Twig_Environment',
'Twig_ExtensionInterface',
'Twig_Extension',
'Twig_Extension_Core',
'Twig_Extension_Escaper',
'Twig_Extension_Optimizer',
'Twig_LoaderInterface',
'Twig_Markup',
'Twig_TemplateInterface',
'Twig_Template',
));
}

View File

@@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\WebProfilerBundle\Profiler\TemplateManager;
/**
* ProfilerController.
@@ -25,6 +26,8 @@ use Symfony\Component\HttpFoundation\Request;
*/
class ProfilerController extends ContainerAware
{
protected $templateManager;
/**
* Renders a profiler panel for the given token.
*
@@ -49,13 +52,13 @@ class ProfilerController extends ContainerAware
throw new NotFoundHttpException(sprintf('Panel "%s" is not available for token "%s".', $panel, $token));
}
return $this->container->get('templating')->renderResponse($this->getTemplateName($profiler, $panel), array(
return $this->container->get('templating')->renderResponse($this->getTemplateManager()->getName($profile, $panel), array(
'token' => $token,
'profile' => $profile,
'collector' => $profile->getCollector($panel),
'panel' => $panel,
'page' => $page,
'templates' => $this->getTemplates($profiler),
'templates' => $this->getTemplateManager()->getTemplates($profile),
'is_ajax' => $request->isXmlHttpRequest(),
));
}
@@ -181,7 +184,7 @@ class ProfilerController extends ContainerAware
return $this->container->get('templating')->renderResponse('WebProfilerBundle:Profiler:toolbar.html.twig', array(
'position' => $position,
'profile' => $profile,
'templates' => $this->getTemplates($profiler),
'templates' => $this->getTemplateManager()->getTemplates($profile),
'profiler_url' => $url,
));
}
@@ -308,51 +311,17 @@ class ProfilerController extends ContainerAware
return new Response($phpinfo);
}
protected function getTemplateNames($profiler)
protected function getTemplateManager()
{
$templates = array();
foreach ($this->container->getParameter('data_collector.templates') as $arguments) {
if (null === $arguments) {
continue;
}
list($name, $template) = $arguments;
if (!$profiler->has($name)) {
continue;
}
if ('.html.twig' === substr($template, -10)) {
$template = substr($template, 0, -10);
}
if (!$this->container->get('templating')->exists($template.'.html.twig')) {
throw new \UnexpectedValueException(sprintf('The profiler template "%s.html.twig" for data collector "%s" does not exist.', $template, $name));
}
$templates[$name] = $template.'.html.twig';
if (null === $this->templateManager) {
$this->templateManager = new TemplateManager(
$this->container->get('profiler'),
$this->container->get('templating'),
$this->container->get('twig'),
$this->container->getParameter('data_collector.templates')
);
}
return $templates;
}
protected function getTemplateName($profiler, $panel)
{
$templates = $this->getTemplateNames($profiler);
if (!isset($templates[$panel])) {
throw new NotFoundHttpException(sprintf('Panel "%s" is not registered.', $panel));
}
return $templates[$panel];
}
protected function getTemplates($profiler)
{
$templates = $this->getTemplateNames($profiler);
foreach ($templates as $name => $template) {
$templates[$name] = $this->container->get('twig')->loadTemplate($template);
}
return $templates;
return $this->templateManager;
}
}

View File

@@ -0,0 +1,123 @@
<?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\Bundle\WebProfilerBundle\Profiler;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\Templating\EngineInterface;
/**
* Profiler Templates Manager
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Artur Wielogórski <wodor@wodor.net>
*/
class TemplateManager
{
protected $templating;
protected $twig;
protected $templates;
protected $profiler;
/**
* Constructor.
*
* @param Profiler $profiler
* @param TwigEngine $templating
* @param \Twig_Environment $twig
* @param array $templates
*/
public function __construct(Profiler $profiler, EngineInterface $templating, \Twig_Environment $twig, array $templates)
{
$this->profiler = $profiler;
$this->templating = $templating;
$this->twig = $twig;
$this->templates = $templates;
}
/**
* Gets the template name for a given panel.
*
* @param Profile $profile
* @param string $panel
*
* @return mixed
*
* @throws NotFoundHttpException
*/
public function getName(Profile $profile, $panel)
{
$templates = $this->getNames($profile);
if (!isset($templates[$panel])) {
throw new NotFoundHttpException(sprintf('Panel "%s" is not registered in profiler or is not present in viewed profile.', $panel));
}
return $templates[$panel];
}
/**
* Gets the templates for a given profile.
*
* @param Profile $profile
*
* @return array
*/
public function getTemplates(Profile $profile)
{
$templates = $this->getNames($profile);
foreach ($templates as $name => $template) {
$templates[$name] = $this->twig->loadTemplate($template);
}
return $templates;
}
/**
* Gets template names of templates that are present in the viewed profile.
*
* @param Profile $profile
*
* @return array
*
* @throws \UnexpectedValueException
*/
protected function getNames(Profile $profile)
{
$templates = array();
foreach ($this->templates as $arguments) {
if (null === $arguments) {
continue;
}
list($name, $template) = $arguments;
if (!$this->profiler->has($name) || !$profile->hasCollector($name)) {
continue;
}
if ('.html.twig' === substr($template, -10)) {
$template = substr($template, 0, -10);
}
if (!$this->templating->exists($template.'.html.twig')) {
throw new \UnexpectedValueException(sprintf('The profiler template "%s.html.twig" for data collector "%s" does not exist.', $template, $name));
}
$templates[$name] = $template.'.html.twig';
}
return $templates;
}
}

View File

@@ -67,7 +67,7 @@
<b>Token</b>
<span>
{% if profiler_url %}
<a style="color: #2f2f2f" href="{{ profiler_url }}">{{ collector.token }}</a>
<a href="{{ profiler_url }}">{{ collector.token }}</a>
{% else %}
{{ collector.token }}
{% endif %}

View File

@@ -10,6 +10,17 @@
{% endblock %}
{% block panel %}
{% if collector.calledlisteners|length %}
{{ block('panelContent') }}
{% else %}
<h2>Events</h2>
<p>
<em>No events have been recorded. Are you sure that debugging is enabled in the kernel ?</em>
</p>
{% endif %}
{% endblock %}
{% block panelContent %}
<h2>Called Listeners</h2>
<table>

View File

@@ -14,14 +14,15 @@
{% endif %}
{% block toolbar %}
{% set total_time = collector.events|length ? '%.0f ms'|format(collector.totaltime) : 'n/a' %}
{% set icon %}
<img width="16" height="28" alt="Time" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAcCAYAAABoMT8aAAABqUlEQVR42t2Vv0sCYRyHX9OmEhsMx/YKGlwLQ69DTEUSBJEQEy5J3FRc/BsuiFqEIIcQIRo6ysUhoaBBWhoaGoJwiMJLglRKrs8bXgienmkQdPDAwX2f57j3fhFJkkbiPwTK5bIiFoul3kmPud8MqKMewDXpwuGww+12n9hsNhFnlijYf/Z4PDmO45Yxo+10ZFGTyWRMEItU6AdCx7lczkgd6n7J2Wx2xm63P6jJMk6n80YQBBN1aUDv9XqvlAbbm2LE7/cLODRB0un0VveAeoDC8/waCQQC18MGQqHQOcEKvw8bcLlcL6TfYnVtCrGRAlartUUYhmn1jKg/E3USjUYfhw3E4/F7ks/nz4YNFIvFQ/ogbUYikdefyqlU6gnuOg2YK5XKvs/n+xhUDgaDTVEUt+HO04ABOBA5isViDTU5kUi81Wq1AzhWMEkDGmAEq2C3UCjcYXGauDvfEsuyUjKZbJRKpVvM8IABU9SVX+cxYABmwIE9cFqtVi9xtgvsC2AHbIAFoKey0gdlHEyDObAEWLACFsEsMALdIJ80+dK0bTS95v7+v/AJnis0eO906QwAAAAASUVORK5CYII="/>
<span>{{ '%.0f'|format(collector.totaltime) }} ms</span>
<span>{{ total_time }}</span>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>Total time</b>
<span>{{ '%.0f'|format(collector.totaltime) }} ms</span>
<span>{{ total_time }}</span>
</div>
{% endset %}
{% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %}
@@ -36,7 +37,16 @@
{% block panel %}
<h2>Timeline</h2>
{% if collector.events|length %}
{{ block('panelContent') }}
{% else %}
<p>
<em>No timing events have been recorded. Are you sure that debugging is enabled in the kernel ?</em>
</p>
{% endif %}
{% endblock %}
{% block panelContent %}
<form id="timeline-control" action="" method="get">
<input type="hidden" name="panel" value="time" />
<table>
@@ -413,7 +423,6 @@
elementThresholdControl.onclick = canvasAutoUpdateOnThresholdChange;
elementThresholdControl.onchange = canvasAutoUpdateOnThresholdChange;
elementThresholdControl.onkeyup = canvasAutoUpdateOnThresholdChange;
//]]></script>
{% endblock %}

View File

@@ -55,6 +55,11 @@
.sf-toolbar-block .sf-toolbar-info-piece:last-child {
padding-bottom: 0;
}
.sf-toolbar-block .sf-toolbar-info-piece a,
.sf-toolbar-block .sf-toolbar-info-piece abbr {
color: #2f2f2f;
}
.sf-toolbar-block .sf-toolbar-info-piece b {
display: inline-block;

View File

@@ -64,6 +64,9 @@ class WebProfilerExtensionTest extends TestCase
$this->container->setParameter('kernel.cache_dir', __DIR__);
$this->container->setParameter('kernel.debug', false);
$this->container->setParameter('kernel.root_dir', __DIR__);
$this->container->register('profiler', $this->getMockClass('Symfony\\Component\\HttpKernel\\Profiler\\Profiler'))
->addArgument(new Definition($this->getMockClass('Symfony\\Component\\HttpKernel\\Profiler\\ProfilerStorageInterface')));
$this->container->setParameter('data_collector.templates', array());
$this->container->set('kernel', $this->kernel);
}

View File

@@ -0,0 +1,178 @@
<?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\Bundle\WebProfilerBundle\Tests\Profiler;
use Symfony\Bundle\WebProfilerBundle\Tests\TestCase;
use Symfony\Bundle\WebProfilerBundle\Profiler\TemplateManager;
/**
* Test for TemplateManager class.
*
* @author Artur Wielogórski <wodor@wodor.net>
*/
class TemplateManagerTest extends TestCase
{
/**
* @var \Symfony\Bundle\TwigBundle\TwigEngine
*/
protected $twigEngine;
/**
* @var \Twig_Environment
*/
protected $twigEnvironment;
/**
* @var \Symfony\Component\HttpKernel\Profiler\Profiler
*/
protected $profiler;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
protected $profile;
/**
* @var \Symfony\Bundle\WebProfilerBundle\Profiler\TemplateManager
*/
protected $templateManager;
public function setUp()
{
parent::setUp();
$profiler = $this->mockProfiler();
$twigEngine = $this->mockTwigEngine();
$twigEnvironment = $this->mockTwigEnvironment();
$templates = array(
'data_collector.foo'=>array('foo','FooBundle:Collector:foo'),
'data_collector.bar'=>array('bar','FooBundle:Collector:bar'),
'data_collector.baz'=>array('baz','FooBundle:Collector:baz')
);
$this->templateManager = new TemplateManager($profiler, $twigEngine, $twigEnvironment, $templates);
}
/**
* @expectedException Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function testGetNameOfInvalidTemplate()
{
$profile = $this->mockProfile();
$this->templateManager->getName($profile, 'notexistingpanel');
}
/**
* if template exists in both profile and profiler then its name should be returned
*/
public function testGetNameValidTemplate()
{
$this->profiler->expects($this->any())
->method('has')
->withAnyParameters()
->will($this->returnCallback(array($this, 'profilerHasCallback')));
$profile = $this->mockProfile();
$profile->expects($this->any())
->method('hasCollector')
->will($this->returnCallback(array($this, 'profileHasCollectorCallback')));
$this->assertEquals('FooBundle:Collector:foo.html.twig', $this->templateManager->getName($profile, 'foo'));
}
/**
* template should be loaded for 'foo' because other collectors are
* missing in profile or in profiler
*/
public function testGetTemplates()
{
$profile = $this->mockProfile();
$profile->expects($this->any())
->method('hasCollector')
->will($this->returnCallback(array($this, 'profilerHasCallback')));
$this->profiler->expects($this->any())
->method('has')
->withAnyParameters()
->will($this->returnCallback(array($this, 'profileHasCollectorCallback')));
$result = $this->templateManager->getTemplates($profile);
$this->assertArrayHasKey('foo',$result);
$this->assertArrayNotHasKey('bar',$result);
$this->assertArrayNotHasKey('baz',$result);
}
public function profilerHasCallback($panel)
{
switch ($panel) {
case 'foo':
case 'bar':
return true;
default:
return false;
}
}
public function profileHasCollectorCallback($panel)
{
switch ($panel) {
case 'foo':
case 'baz':
return true;
default:
return false;
}
}
protected function mockProfile()
{
$this->profile = $this->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profile')
->disableOriginalConstructor()
->getMock();
return $this->profile;
}
protected function mockTwigEnvironment()
{
$this->twigEnvironment = $this->getMockBuilder('Twig_Environment')->getMock();
$this->twigEnvironment->expects($this->any())
->method('loadTemplate')
->will($this->returnValue('loadedTemplate'));
return $this->twigEnvironment;
}
protected function mockTwigEngine()
{
$this->twigEngine = $this->getMockBuilder('Symfony\Bundle\TwigBundle\TwigEngine')
->disableOriginalConstructor()
->getMock();
$this->twigEngine->expects($this->any())
->method('exists')
->will($this->returnValue(true));
return $this->twigEngine;
}
protected function mockProfiler()
{
$this->profiler = $this->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler')
->disableOriginalConstructor()
->getMock();
return $this->profiler;
}
}

View File

@@ -55,7 +55,7 @@ class ApcClassLoader
public function __construct($prefix, $classFinder)
{
if (!extension_loaded('apc')) {
throw new \RuntimeException('Unable to use ApcUniversalClassLoader as APC is not enabled.');
throw new \RuntimeException('Unable to use ApcClassLoader as APC is not enabled.');
}
if (!method_exists($classFinder, 'findFile')) {

View File

@@ -19,6 +19,7 @@ namespace Symfony\Component\ClassLoader;
class ClassCollectionLoader
{
static private $loaded;
static private $seen;
/**
* Loads a list of classes and caches them in one big file.
@@ -41,14 +42,21 @@ class ClassCollectionLoader
self::$loaded[$name] = true;
$declared = array_merge(get_declared_classes(), get_declared_interfaces());
if (function_exists('get_declared_traits')) {
$declared = array_merge($declared, get_declared_traits());
}
if ($adaptive) {
// don't include already declared classes
$classes = array_diff($classes, get_declared_classes(), get_declared_interfaces());
$classes = array_diff($classes, $declared);
// the cache is different depending on which classes are already declared
$name = $name.'-'.substr(md5(implode('|', $classes)), 0, 5);
}
$classes = array_unique($classes);
$cache = $cacheDir.'/'.$name.$extension;
// auto-reload
@@ -61,6 +69,9 @@ class ClassCollectionLoader
$time = filemtime($cache);
$meta = unserialize(file_get_contents($metadata));
sort($meta[1]);
sort($classes);
if ($meta[1] != $classes) {
$reload = true;
} else {
@@ -83,18 +94,17 @@ class ClassCollectionLoader
$files = array();
$content = '';
foreach ($classes as $class) {
if (!class_exists($class) && !interface_exists($class) && (!function_exists('trait_exists') || !trait_exists($class))) {
throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class));
foreach (self::getOrderedClasses($classes) as $class) {
if (in_array($class->getName(), $declared)) {
continue;
}
$r = new \ReflectionClass($class);
$files[] = $r->getFileName();
$files[] = $class->getFileName();
$c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($r->getFileName()));
$c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($class->getFileName()));
// add namespace declaration for global code
if (!$r->inNamespace()) {
if (!$class->inNamespace()) {
$c = "\nnamespace\n{\n".self::stripComments($c)."\n}\n";
} else {
$c = self::fixNamespaceDeclarations('<?php '.$c);
@@ -220,4 +230,82 @@ class ClassCollectionLoader
return $output;
}
/**
* Gets an ordered array of passed classes including all their dependencies.
*
* @param array $classes
*
* @return array An array of sorted \ReflectionClass instances (dependencies added if needed)
*
* @throws \InvalidArgumentException When a class can't be loaded
*/
static private function getOrderedClasses(array $classes)
{
$map = array();
self::$seen = array();
foreach ($classes as $class) {
try {
$reflectionClass = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class));
}
$map = array_merge($map, self::getClassHierarchy($reflectionClass));
}
return $map;
}
static private function getClassHierarchy(\ReflectionClass $class)
{
if (isset(self::$seen[$class->getName()])) {
return array();
}
self::$seen[$class->getName()] = true;
$classes = array($class);
$parent = $class;
while (($parent = $parent->getParentClass()) && $parent->isUserDefined() && !isset(self::$seen[$parent->getName()])) {
self::$seen[$parent->getName()] = true;
array_unshift($classes, $parent);
}
if (function_exists('get_declared_traits')) {
foreach ($classes as $c) {
foreach (self::getTraits($c) as $trait) {
self::$seen[$trait->getName()] = true;
array_unshift($classes, $trait);
}
}
}
foreach ($class->getInterfaces() as $interface) {
if ($interface->isUserDefined() && !isset(self::$seen[$interface->getName()])) {
self::$seen[$interface->getName()] = true;
array_unshift($classes, $interface);
}
}
return $classes;
}
static private function getTraits(\ReflectionClass $class)
{
$traits = $class->getTraits();
$classes = array();
while ($trait = array_pop($traits)) {
if ($trait->isUserDefined() && !isset(self::$seen[$trait->getName()])) {
$classes[] = $trait;
$traits = array_merge($traits, $trait->getTraits());
}
}
return $classes;
}
}

View File

@@ -13,8 +13,106 @@ namespace Symfony\Component\ClassLoader\Tests;
use Symfony\Component\ClassLoader\ClassCollectionLoader;
require_once __DIR__.'/Fixtures/ClassesWithParents/CInterface.php';
require_once __DIR__.'/Fixtures/ClassesWithParents/B.php';
require_once __DIR__.'/Fixtures/ClassesWithParents/A.php';
class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getDifferentOrders
*/
public function testClassReordering(array $classes)
{
$expected = array(
'ClassesWithParents\\CInterface',
'ClassesWithParents\\B',
'ClassesWithParents\\A',
);
$r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader');
$m = $r->getMethod('getOrderedClasses');
$m->setAccessible(true);
$ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', $classes);
$this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered));
}
public function getDifferentOrders()
{
return array(
array(array(
'ClassesWithParents\\A',
'ClassesWithParents\\CInterface',
'ClassesWithParents\\B',
)),
array(array(
'ClassesWithParents\\B',
'ClassesWithParents\\A',
'ClassesWithParents\\CInterface',
)),
array(array(
'ClassesWithParents\\CInterface',
'ClassesWithParents\\B',
'ClassesWithParents\\A',
)),
array(array(
'ClassesWithParents\\A',
)),
);
}
/**
* @dataProvider getDifferentOrdersForTraits
*/
public function testClassWithTraitsReordering(array $classes)
{
if (version_compare(phpversion(), '5.4.0', '<')) {
$this->markTestSkipped('Requires PHP > 5.4.0.');
return;
}
require_once __DIR__.'/Fixtures/ClassesWithParents/ATrait.php';
require_once __DIR__.'/Fixtures/ClassesWithParents/BTrait.php';
require_once __DIR__.'/Fixtures/ClassesWithParents/CTrait.php';
require_once __DIR__.'/Fixtures/ClassesWithParents/D.php';
require_once __DIR__.'/Fixtures/ClassesWithParents/E.php';
$expected = array(
'ClassesWithParents\\CInterface',
'ClassesWithParents\\CTrait',
'ClassesWithParents\\ATrait',
'ClassesWithParents\\BTrait',
'ClassesWithParents\\B',
'ClassesWithParents\\A',
'ClassesWithParents\\D',
'ClassesWithParents\\E',
);
$r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader');
$m = $r->getMethod('getOrderedClasses');
$m->setAccessible(true);
$ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', $classes);
$this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered));
}
public function getDifferentOrdersForTraits()
{
return array(
array(array(
'ClassesWithParents\\E',
'ClassesWithParents\\ATrait',
)),
array(array(
'ClassesWithParents\\E',
)),
);
}
public function testFixNamespaceDeclarations()
{
$source = <<<EOF

View File

@@ -0,0 +1,5 @@
<?php
namespace ClassesWithParents;
class A extends B {}

View File

@@ -0,0 +1,7 @@
<?php
namespace ClassesWithParents;
trait ATrait
{
}

View File

@@ -0,0 +1,5 @@
<?php
namespace ClassesWithParents;
class B implements CInterface {}

View File

@@ -0,0 +1,8 @@
<?php
namespace ClassesWithParents;
trait BTrait
{
use ATrait;
}

View File

@@ -0,0 +1,5 @@
<?php
namespace ClassesWithParents;
interface CInterface {}

View File

@@ -0,0 +1,7 @@
<?php
namespace ClassesWithParents;
trait CTrait
{
}

View File

@@ -0,0 +1,8 @@
<?php
namespace ClassesWithParents;
class D extends A
{
use BTrait;
}

View File

@@ -0,0 +1,8 @@
<?php
namespace ClassesWithParents;
class E extends D
{
use CTrait;
}

View File

@@ -344,7 +344,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition
}
/**
* Validate the confifuration of a concrete node.
* Validate the configuration of a concrete node.
*
* @param NodeInterface $node The related node
*

View File

@@ -0,0 +1,210 @@
<?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\Config\Tests\Definition\Builder;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
class ExprBuilderTest extends \PHPUnit_Framework_TestCase
{
public function testAlwaysExpression()
{
$test = $this->getTestBuilder()
->always($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
}
public function testIfTrueExpression()
{
$test = $this->getTestBuilder()
->ifTrue()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test, array('key'=>true));
$test = $this->getTestBuilder()
->ifTrue( function($v){ return true; })
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
$test = $this->getTestBuilder()
->ifTrue( function($v){ return false; })
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('value',$test);
}
public function testIfStringExpression()
{
$test = $this->getTestBuilder()
->ifString()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
$test = $this->getTestBuilder()
->ifString()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs(45, $test, array('key'=>45));
}
public function testIfNullExpression()
{
$test = $this->getTestBuilder()
->ifNull()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test, array('key'=>null));
$test = $this->getTestBuilder()
->ifNull()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('value', $test);
}
public function testIfArrayExpression()
{
$test = $this->getTestBuilder()
->ifArray()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test, array('key'=>array()));
$test = $this->getTestBuilder()
->ifArray()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('value', $test);
}
public function testIfInArrayExpression()
{
$test = $this->getTestBuilder()
->ifInArray(array('foo', 'bar', 'value'))
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
$test = $this->getTestBuilder()
->ifInArray(array('foo', 'bar'))
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('value', $test);
}
public function testIfNotInArrayExpression()
{
$test = $this->getTestBuilder()
->ifNotInArray(array('foo', 'bar'))
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
$test = $this->getTestBuilder()
->ifNotInArray(array('foo', 'bar', 'value_from_config' ))
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
}
public function testThenEmptyArrayExpression()
{
$test = $this->getTestBuilder()
->ifString()
->thenEmptyArray()
->end();
$this->assertFinalizedValueIs(array(), $test);
}
/**
* @expectedException Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
public function testThenInvalid()
{
$test = $this->getTestBuilder()
->ifString()
->thenInvalid('Invalid value')
->end();
$this->finalizeTestBuilder($test);
}
public function testThenUnsetExpression()
{
$test = $this->getTestBuilder()
->ifString()
->thenUnset()
->end();
$this->assertEquals(array(), $this->finalizeTestBuilder($test));
}
/**
* Create a test treebuilder with a variable node, and init the validation
* @return TreeBuilder
*/
protected function getTestBuilder()
{
$builder = new TreeBuilder();
return $builder
->root('test')
->children()
->variableNode('key')
->validate()
;
}
/**
* Close the validation process and finalize with the given config
* @param TreeBuilder $testBuilder The tree builder to finalize
* @param array $config The config you want to use for the finalization, if nothing provided
* a simple array('key'=>'value') will be used
* @return array The finalized config values
*/
protected function finalizeTestBuilder($testBuilder, $config=null)
{
return $testBuilder
->end()
->end()
->end()
->buildTree()
->finalize($config === null ? array('key'=>'value') : $config)
;
}
/**
* Return a closure that will return the given value
* @param $val The value that the closure must return
* @return Closure
*/
protected function returnClosure($val) {
return function($v) use ($val) {
return $val;
};
}
/**
* Assert that the given test builder, will return the given value
* @param mixed $value The value to test
* @param TreeBuilder $test The tree builder to finalize
* @param mixed $config The config values that new to be finalized
*/
protected function assertFinalizedValueIs($value, $treeBuilder, $config=null)
{
$this->assertEquals(array('key'=>$value), $this->finalizeTestBuilder($treeBuilder, $config));
}
}

View File

@@ -882,7 +882,7 @@ class Application
*/
protected function getCommandName(InputInterface $input)
{
return $input->getFirstArgument('command');
return $input->getFirstArgument();
}
/**

View File

@@ -10,3 +10,6 @@ CHANGELOG
* added Definition::clearTag()
* component exceptions that inherit base SPL classes are now used exclusively
(this includes dumped containers)
* [BC BREAK] fixed unescaping of class arguments, method
ParameterBag::unescapeValue() was made public

View File

@@ -220,8 +220,8 @@ class Container implements IntrospectableContainerInterface
/**
* Gets a service.
*
* If a service is both defined through a set() method and
* with a set*Service() method, the former has always precedence.
* If a service is defined both through a set() method and
* with a get{$id}Service() method, the former has always precedence.
*
* @param string $id The service identifier
* @param integer $invalidBehavior The behavior when the service does not exist

View File

@@ -728,24 +728,28 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*/
private function createService(Definition $definition, $id)
{
$parameterBag = $this->getParameterBag();
if (null !== $definition->getFile()) {
require_once $this->getParameterBag()->resolveValue($definition->getFile());
require_once $parameterBag->resolveValue($definition->getFile());
}
$arguments = $this->resolveServices($this->getParameterBag()->resolveValue($definition->getArguments()));
$arguments = $this->resolveServices(
$parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments()))
);
if (null !== $definition->getFactoryMethod()) {
if (null !== $definition->getFactoryClass()) {
$factory = $this->getParameterBag()->resolveValue($definition->getFactoryClass());
$factory = $parameterBag->resolveValue($definition->getFactoryClass());
} elseif (null !== $definition->getFactoryService()) {
$factory = $this->get($this->getParameterBag()->resolveValue($definition->getFactoryService()));
$factory = $this->get($parameterBag->resolveValue($definition->getFactoryService()));
} else {
throw new RuntimeException('Cannot create service from factory method without a factory service or factory class.');
}
$service = call_user_func_array(array($factory, $definition->getFactoryMethod()), $arguments);
} else {
$r = new \ReflectionClass($this->getParameterBag()->resolveValue($definition->getClass()));
$r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));
$service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
}
@@ -774,11 +778,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
if ($ok) {
call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->resolveValue($call[1])));
call_user_func_array(array($service, $call[0]), $this->resolveServices($parameterBag->resolveValue($call[1])));
}
}
$properties = $this->resolveServices($this->getParameterBag()->resolveValue($definition->getProperties()));
$properties = $this->resolveServices($parameterBag->resolveValue($definition->getProperties()));
foreach ($properties as $name => $value) {
$service->$name = $value;
}
@@ -787,7 +791,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
if (is_array($callable) && is_object($callable[0]) && $callable[0] instanceof Reference) {
$callable[0] = $this->get((string) $callable[0]);
} elseif (is_array($callable)) {
$callable[0] = $this->getParameterBag()->resolveValue($callable[0]);
$callable[0] = $parameterBag->resolveValue($callable[0]);
}
if (!is_callable($callable)) {

View File

@@ -345,7 +345,7 @@ EOF
;
$current = libxml_use_internal_errors(true);
$valid = $dom->schemaValidateSource($source);
$valid = @$dom->schemaValidateSource($source);
foreach ($tmpfiles as $tmpfile) {
@unlink($tmpfile);
}

View File

@@ -253,7 +253,28 @@ class ParameterBag implements ParameterBagInterface
return $this->resolved;
}
private function unescapeValue($value)
/**
* {@inheritDoc}
*/
public function escapeValue($value)
{
if (is_string($value)) {
return str_replace('%', '%%', $value);
}
if (is_array($value)) {
$result = array();
foreach ($value as $k => $v) {
$result[$k] = $this->escapeValue($v);
}
return $result;
}
return $value;
}
public function unescapeValue($value)
{
if (is_string($value)) {
return str_replace('%%', '%', $value);

View File

@@ -94,4 +94,22 @@ interface ParameterBagInterface
* @throws ParameterNotFoundException if a placeholder references a parameter that does not exist
*/
function resolveValue($value);
/**
* Escape parameter placeholders %
*
* @param mixed $value
*
* @return mixed
*/
function escapeValue($value);
/**
* Unescape parameter placeholders %
*
* @param mixed $value
*
* @return mixed
*/
function unescapeValue($value);
}

View File

@@ -251,9 +251,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
{
$builder = new ContainerBuilder();
$builder->register('bar', 'stdClass');
$builder->register('foo1', 'FooClass')->addArgument(array('foo' => '%value%', '%value%' => 'foo', new Reference('bar')));
$builder->register('foo1', 'FooClass')->addArgument(array('foo' => '%value%', '%value%' => 'foo', new Reference('bar'), '%%unescape_it%%'));
$builder->setParameter('value', 'bar');
$this->assertEquals(array('foo' => 'bar', 'bar' => 'foo', $builder->get('bar')), $builder->get('foo1')->arguments, '->createService() replaces parameters and service references in the arguments provided by the service definition');
$this->assertEquals(array('foo' => 'bar', 'bar' => 'foo', $builder->get('bar'), '%unescape_it%'), $builder->get('foo1')->arguments, '->createService() replaces parameters and service references in the arguments provided by the service definition');
}
/**

View File

@@ -201,6 +201,22 @@ class ParameterBagTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array('bar' => array('ding' => 'I\'m a bar %foo %bar')), $bag->get('foo'), '->resolveValue() supports % escaping by doubling it');
}
/**
* @covers Symfony\Component\DependencyInjection\ParameterBag\ParameterBag::escapeValue
*/
public function testEscapeValue()
{
$bag = new ParameterBag();
$bag->add(array(
'foo' => $bag->escapeValue(array('bar' => array('ding' => 'I\'m a bar %foo %bar', 'zero' => null))),
'bar' => $bag->escapeValue('I\'m a %foo%'),
));
$this->assertEquals('I\'m a %%foo%%', $bag->get('bar'), '->escapeValue() escapes % by doubling it');
$this->assertEquals(array('bar' => array('ding' => 'I\'m a bar %%foo %%bar', 'zero' => null)), $bag->get('foo'), '->escapeValue() escapes % by doubling it');
}
/**
* @covers Symfony\Component\DependencyInjection\ParameterBag\ParameterBag::resolve
* @dataProvider stringsWithSpacesProvider

View File

@@ -12,3 +12,5 @@ CHANGELOG
* added searching based on the file content via Finder::contains() and
Finder::notContains()
* added support for the != operator in the Comparator
* [BC BREAK] filter expressions (used for file name and content) are no more
considered as regexps but glob patterns when they are enclosed in '*' or '?'

View File

@@ -58,7 +58,7 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator
$end = substr($m[1], -1);
if ($start === $end) {
return !preg_match('/[[:alnum:] \\\\]/', $start);
return !preg_match('/[*?[:alnum:] \\\\]/', $start);
}
if ($start === '{' && $end === '}') {

View File

@@ -38,6 +38,8 @@ class MultiplePcreFilterIteratorTest extends \PHPUnit_Framework_TestCase
array('/foo/imsxu', true, 'valid regex with multiple modifiers'),
array('#foo#', true, '"#" is a valid delimiter'),
array('{foo}', true, '"{,}" is a valid delimiter pair'),
array('*foo.*', false, '"*" is not considered as a valid delimiter'),
array('?foo.?', false, '"?" is not considered as a valid delimiter'),
);
}
}

View File

@@ -139,3 +139,5 @@ CHANGELOG
* deprecated `getChildren` in Form and FormBuilder in favor of `all`
* deprecated `hasChildren` in Form and FormBuilder in favor of `count`
* FormBuilder now implements \IteratorAggregate
* [BC BREAK] compound forms now always need a data mapper
* FormBuilder now maintains the order when explicitely adding form builders as children

View File

@@ -23,22 +23,24 @@ class PropertyPathMapper implements DataMapperInterface
*/
public function mapDataToForms($data, array $forms)
{
if (!empty($data) && !is_array($data) && !is_object($data)) {
throw new UnexpectedTypeException($data, 'Object, array or empty');
if (null === $data || array() === $data) {
return;
}
if (!empty($data)) {
$iterator = new VirtualFormAwareIterator($forms);
$iterator = new \RecursiveIteratorIterator($iterator);
if (!is_array($data) && !is_object($data)) {
throw new UnexpectedTypeException($data, 'object, array or empty');
}
foreach ($iterator as $form) {
/* @var FormInterface $form */
$propertyPath = $form->getPropertyPath();
$config = $form->getConfig();
$iterator = new VirtualFormAwareIterator($forms);
$iterator = new \RecursiveIteratorIterator($iterator);
if (null !== $propertyPath && $config->getMapped()) {
$form->setData($propertyPath->getValue($data));
}
foreach ($iterator as $form) {
/* @var FormInterface $form */
$propertyPath = $form->getPropertyPath();
$config = $form->getConfig();
if (null !== $propertyPath && $config->getMapped()) {
$form->setData($propertyPath->getValue($data));
}
}
}
@@ -48,6 +50,14 @@ class PropertyPathMapper implements DataMapperInterface
*/
public function mapFormsToData(array $forms, &$data)
{
if (null === $data) {
return;
}
if (!is_array($data) && !is_object($data)) {
throw new UnexpectedTypeException($data, 'object, array or empty');
}
$iterator = new VirtualFormAwareIterator($forms);
$iterator = new \RecursiveIteratorIterator($iterator);

View File

@@ -42,8 +42,9 @@ class FormType extends AbstractType
->setMapped($options['mapped'])
->setByReference($options['by_reference'])
->setVirtual($options['virtual'])
->setCompound($options['compound'])
->setData($options['data'])
->setDataMapper(new PropertyPathMapper())
->setDataMapper($options['compound'] ? new PropertyPathMapper() : null)
;
if ($options['trim']) {
@@ -112,11 +113,11 @@ class FormType extends AbstractType
'max_length' => $options['max_length'],
'pattern' => $options['pattern'],
'size' => null,
'label' => $options['label'] ?: $this->humanize($form->getName()),
'label' => $options['label'],
'multipart' => false,
'attr' => $options['attr'],
'label_attr' => $options['label_attr'],
'compound' => $options['compound'],
'compound' => $form->getConfig()->getCompound(),
'types' => $types,
'translation_domain' => $translationDomain,
));
@@ -160,7 +161,7 @@ class FormType extends AbstractType
}
return function (FormInterface $form) {
return count($form) > 0 ? array() : '';
return $form->getConfig()->getCompound() ? array() : '';
};
};
@@ -226,9 +227,4 @@ class FormType extends AbstractType
{
return 'form';
}
private function humanize($text)
{
return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text))));
}
}

View File

@@ -261,7 +261,12 @@ class ValidatorTypeGuesser implements FormTypeGuesserInterface
return new ValueGuess(sprintf('.{%s,%s}', (string) $constraint->min, (string) $constraint->max), Guess::LOW_CONFIDENCE);
case 'Symfony\Component\Validator\Constraints\Regex':
return new ValueGuess($constraint->pattern, Guess::HIGH_CONFIDENCE );
$htmlPattern = $constraint->getHtmlPattern();
if (null !== $htmlPattern) {
return new ValueGuess($htmlPattern, Guess::HIGH_CONFIDENCE);
}
break;
case 'Symfony\Component\Validator\Constraints\Min':
return new ValueGuess(sprintf('.{%s,}', strlen((string) $constraint->limit)), Guess::LOW_CONFIDENCE);

View File

@@ -15,6 +15,7 @@ use Symfony\Component\Form\Exception\FormException;
use Symfony\Component\Form\Exception\AlreadyBoundException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Util\FormUtil;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -130,6 +131,13 @@ class Form implements \IteratorAggregate, FormInterface
$config = new UnmodifiableFormConfig($config);
}
// Compound forms always need a data mapper, otherwise calls to
// `setData` and `add` will not lead to the correct population of
// the child forms.
if ($config->getCompound() && !$config->getDataMapper()) {
throw new FormException('Compound forms need a data mapper');
}
$this->config = $config;
$this->setData($config->getData());
@@ -345,7 +353,7 @@ class Form implements \IteratorAggregate, FormInterface
$viewData = $this->normToView($normData);
// Validate if view data matches data class (unless empty)
if (!empty($viewData)) {
if (!FormUtil::isEmpty($viewData)) {
$dataClass = $this->config->getDataClass();
$actualType = is_object($viewData) ? 'an instance of class ' . get_class($viewData) : ' a(n) ' . gettype($viewData);
@@ -378,7 +386,7 @@ class Form implements \IteratorAggregate, FormInterface
$this->viewData = $viewData;
$this->synchronized = true;
if (count($this->children) > 0 && $this->config->getDataMapper()) {
if ($this->config->getCompound()) {
// Update child forms from the data
$this->config->getDataMapper()->mapDataToForms($viewData, $this->children);
}
@@ -477,25 +485,29 @@ class Form implements \IteratorAggregate, FormInterface
$this->config->getEventDispatcher()->dispatch(FormEvents::BIND_CLIENT_DATA, $event);
$submittedData = $event->getData();
// Build the data in the view format
// By default, the submitted data is also the data in view format
$viewData = $submittedData;
if (count($this->children) > 0) {
if (null === $viewData || '' === $viewData) {
$viewData = array();
}
// Check whether the form is compound.
// This check is preferrable over checking the number of children,
// since forms without children may also be compound.
// (think of empty collection forms)
if ($this->config->getCompound()) {
if (!is_array($submittedData)) {
if (!FormUtil::isEmpty($submittedData)) {
throw new UnexpectedTypeException($submittedData, 'array');
}
if (!is_array($viewData)) {
throw new UnexpectedTypeException($viewData, 'array');
$submittedData = array();
}
foreach ($this->children as $name => $child) {
if (!isset($viewData[$name])) {
$viewData[$name] = null;
if (!isset($submittedData[$name])) {
$submittedData[$name] = null;
}
}
foreach ($viewData as $name => $value) {
foreach ($submittedData as $name => $value) {
if ($this->has($name)) {
$this->children[$name]->bind($value);
} else {
@@ -503,14 +515,13 @@ class Form implements \IteratorAggregate, FormInterface
}
}
// If we have a data mapper, use old view data and merge
// data from the children into it later
if ($this->config->getDataMapper()) {
$viewData = $this->getViewData();
}
// If the form is compound, the default data in view format
// is reused. The data of the children is merged into this
// default data using the data mapper.
$viewData = $this->getViewData();
}
if (null === $viewData || '' === $viewData) {
if (FormUtil::isEmpty($viewData)) {
$emptyData = $this->config->getEmptyData();
if ($emptyData instanceof \Closure) {
@@ -522,7 +533,7 @@ class Form implements \IteratorAggregate, FormInterface
}
// Merge form data from children into existing view data
if (count($this->children) > 0 && $this->config->getDataMapper() && null !== $viewData) {
if ($this->config->getCompound()) {
$this->config->getDataMapper()->mapFormsToData($this->children, $viewData);
}
@@ -542,7 +553,6 @@ class Form implements \IteratorAggregate, FormInterface
$this->config->getEventDispatcher()->dispatch(FormEvents::BIND_NORM_DATA, $event);
$normData = $event->getData();
// Synchronize representations - must not change the content!
$modelData = $this->normToModel($normData);
$viewData = $this->normToView($normData);
@@ -591,7 +601,7 @@ class Form implements \IteratorAggregate, FormInterface
// Form bound without name
$params = $request->request->all();
$files = $request->files->all();
} elseif (count($this->children) > 0) {
} elseif ($this->config->getCompound()) {
// Form bound with name and children
$params = $request->request->get($name, array());
$files = $request->files->get($name, array());
@@ -692,7 +702,7 @@ class Form implements \IteratorAggregate, FormInterface
}
}
return array() === $this->modelData || null === $this->modelData || '' === $this->modelData;
return FormUtil::isEmpty($this->modelData) || array() === $this->modelData;
}
/**
@@ -841,13 +851,15 @@ class Form implements \IteratorAggregate, FormInterface
throw new AlreadyBoundException('You cannot add children to a bound form');
}
if (!$this->config->getCompound()) {
throw new FormException('You cannot add children to a simple form. Maybe you should set the option "compound" to true?');
}
$this->children[$child->getName()] = $child;
$child->setParent($this);
if ($this->config->getDataMapper()) {
$this->config->getDataMapper()->mapDataToForms($this->getViewData(), array($child));
}
$this->config->getDataMapper()->mapDataToForms($this->getViewData(), array($child));
return $this;
}
@@ -1037,9 +1049,12 @@ class Form implements \IteratorAggregate, FormInterface
*/
private function normToView($value)
{
if (!$this->config->getViewTransformers()) {
// Scalar values should always be converted to strings to
// facilitate differentiation between empty ("") and zero (0).
// Scalar values should be converted to strings to
// facilitate differentiation between empty ("") and zero (0).
// Only do this for simple forms, as the resulting value in
// compound forms is passed to the data mapper and thus should
// not be converted to a string before.
if (!$this->config->getViewTransformers() && !$this->config->getCompound()) {
return null === $value || is_scalar($value) ? (string) $value : $value;
}

View File

@@ -103,6 +103,8 @@ class FormBuilder extends FormConfig implements \IteratorAggregate, FormBuilderI
throw new CircularReferenceException(is_string($type) ? $this->getFormFactory()->getType($type) : $type);
}
// Add to "children" to maintain order
$this->children[$child] = null;
$this->unresolvedChildren[$child] = array(
'type' => $type,
'options' => $options,

View File

@@ -53,6 +53,11 @@ class FormConfig implements FormConfigEditorInterface
*/
private $virtual = false;
/**
* @var Boolean
*/
private $compound = false;
/**
* @var array
*/
@@ -356,6 +361,14 @@ class FormConfig implements FormConfigEditorInterface
return $this->virtual;
}
/**
* {@inheritdoc}
*/
public function getCompound()
{
return $this->compound;
}
/**
* {@inheritdoc}
*/
@@ -632,6 +645,16 @@ class FormConfig implements FormConfigEditorInterface
return $this;
}
/**
* {@inheritdoc}
*/
public function setCompound($compound)
{
$this->compound = $compound;
return $this;
}
/**
* {@inheritdoc}
*/

View File

@@ -199,6 +199,17 @@ interface FormConfigEditorInterface extends FormConfigInterface
*/
function setVirtual($virtual);
/**
* Sets whether the form should be compound.
*
* @param Boolean $compound Whether the form should be compound.
*
* @return self The configuration object.
*
* @see FormConfigInterface::getCompound()
*/
function setCompound($compound);
/**
* Set the types.
*

View File

@@ -65,6 +65,17 @@ interface FormConfigInterface
*/
function getVirtual();
/**
* Returns whether the form is compound.
*
* This property is independent of whether the form actually has
* children. A form can be compound and have no children at all, like
* for example an empty collection form.
*
* @return Boolean Whether the form is compound.
*/
function getCompound();
/**
* Returns the form types used to construct the form.
*

Some files were not shown because too many files have changed in this diff Show More