diff --git a/.travis.sh b/.travis.sh
index 9eeecbeeb8..55020cb012 100644
--- a/.travis.sh
+++ b/.travis.sh
@@ -10,16 +10,15 @@ if [ -z "$components" ]; then
exit 1
fi
echo '{"packages": {' > packages.json
-components=$(
- for c in $components; do
- sed -i ':a;N;$!ba;s#^{\n\(\s*\)\("name"\)#{\n\1"repositories": \[{ "type": "composer", "url": "file://'$(pwd)'/" }\],\n\1\2#' $c/composer.json
- n=$(php -r '$n=json_decode(file_get_contents("'$c'/composer.json"));echo $n->name;')
- echo '"'$n'": {"'$branch'.x-dev": ' >> packages.json
- cat $c/composer.json >> packages.json
- echo '"version": "'$branch.x-dev'",\n "dist": {"type": "tar", "url": "file://'$(pwd)/$c'/package'$branch'.tar"}\n}},' >> packages.json
- echo $c
- done;
-)
+for c in $components; do
+ sed -i ':a;N;$!ba;s#^{\n\(\s*\)\("name"\)#{\n\1"repositories": \[{ "type": "composer", "url": "file://'$(pwd)'/" }\],\n\1\2#' $c/composer.json
+ n=$(php -r '$n=json_decode(file_get_contents("'$c'/composer.json"));echo $n->name;')
+ echo '"'$n'": {"'$branch'.x-dev": ' >> packages.json
+ cat $c/composer.json >> packages.json
+ echo '"version": "'$branch.x-dev'",\n "dist": {"type": "tar", "url": "file://'$(pwd)/$c'/package'$branch'.tar"}\n}},' >> packages.json
+done;
sed -i ':a;N;$!ba;s/\n}\n"/,\n "/g' packages.json
sed -i ':a;N;$!ba;s/}},$/\n}}\n}}/' packages.json
-echo "$components" | parallel --gnu "cd {}; tar -cf package$branch.tar --exclude='package*.tar' *"
+for c in $components; do
+ (cd $c && tar -cf package$branch.tar --exclude='package*.tar' *)
+done
diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md
index 2708da70db..f27ec77b17 100644
--- a/UPGRADE-3.0.md
+++ b/UPGRADE-3.0.md
@@ -330,6 +330,51 @@ UPGRADE FROM 2.x to 3.0
* The `request` service was removed. You must inject the `request_stack`
service instead.
+ * The `templating.helper.assets` was moved to `templating_php.xml`. You can
+ use the `assets.package` service instead.
+
+ Before:
+
+ ```php
+ use Symfony\Component\Templating\Helper\CoreAssetsHelper;
+
+ class DemoService
+ {
+ private $assetsHelper;
+
+ public function __construct(CoreAssetsHelper $assetsHelper)
+ {
+ $this->assetsHelper = $assetsHelper;
+ }
+
+ public function testMethod()
+ {
+ return $this->assetsHelper->getUrl('thumbnail.png', null, $this->assetsHelper->getVersion());
+ }
+ }
+ ```
+
+ After:
+
+ ```php
+ use Symfony\Component\Asset\Packages;
+
+ class DemoService
+ {
+ private $assetPackages;
+
+ public function __construct(Packages $assetPackages)
+ {
+ $this->assetPackages = $assetPackages;
+ }
+
+ public function testMethod()
+ {
+ return $this->assetPackages->getUrl('thumbnail.png').$this->assetPackages->getVersion();
+ }
+ }
+ ```
+
* The `enctype` method of the `form` helper was removed. You should use the
new method `start` instead.
diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php
index 79f2767e30..e9b89302db 100644
--- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php
+++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php
@@ -12,10 +12,16 @@
namespace Symfony\Bridge\Doctrine\Form\Type;
use Doctrine\Common\Persistence\ObjectManager;
+use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
class EntityType extends DoctrineType
{
+ /**
+ * @var ORMQueryBuilderLoader[]
+ */
+ private $loaderCache = array();
+
/**
* Return the default loader object.
*
@@ -27,11 +33,55 @@ class EntityType extends DoctrineType
*/
public function getLoader(ObjectManager $manager, $queryBuilder, $class)
{
- return new ORMQueryBuilderLoader(
- $queryBuilder,
- $manager,
- $class
- );
+ if (!$queryBuilder instanceof QueryBuilder) {
+ return new ORMQueryBuilderLoader(
+ $queryBuilder,
+ $manager,
+ $class
+ );
+ }
+
+ $queryBuilderHash = $this->getQueryBuilderHash($queryBuilder);
+ $loaderHash = $this->getLoaderHash($manager, $queryBuilderHash, $class);
+
+ if (!isset($this->loaderCache[$loaderHash])) {
+ $this->loaderCache[$loaderHash] = new ORMQueryBuilderLoader(
+ $queryBuilder,
+ $manager,
+ $class
+ );
+ }
+
+ return $this->loaderCache[$loaderHash];
+ }
+
+ /**
+ * @param QueryBuilder $queryBuilder
+ *
+ * @return string
+ */
+ private function getQueryBuilderHash(QueryBuilder $queryBuilder)
+ {
+ return hash('sha256', json_encode(array(
+ 'sql' => $queryBuilder->getQuery()->getSQL(),
+ 'parameters' => $queryBuilder->getParameters(),
+ )));
+ }
+
+ /**
+ * @param ObjectManager $manager
+ * @param string $queryBuilderHash
+ * @param string $class
+ *
+ * @return string
+ */
+ private function getLoaderHash(ObjectManager $manager, $queryBuilderHash, $class)
+ {
+ return hash('sha256', json_encode(array(
+ 'manager' => spl_object_hash($manager),
+ 'queryBuilder' => $queryBuilderHash,
+ 'class' => $class,
+ )));
}
public function getName()
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php
index 64ef35c18a..25afbed492 100644
--- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php
+++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php
@@ -11,7 +11,11 @@
namespace Symfony\Bridge\Doctrine\Tests\Form\Type;
+use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser;
+use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
+use Symfony\Component\Form\FormBuilder;
+use Symfony\Component\Form\Forms;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity;
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity;
@@ -22,6 +26,7 @@ use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
+use Symfony\Component\PropertyAccess\PropertyAccess;
class EntityTypeTest extends TypeTestCase
{
@@ -656,7 +661,7 @@ class EntityTypeTest extends TypeTestCase
'class' => self::SINGLE_IDENT_CLASS,
'query_builder' => function ($repository) {
return $repository->createQueryBuilder('e')
- ->where('e.id IN (1, 2)');
+ ->where('e.id IN (1, 2)');
},
'property' => 'name',
));
@@ -680,7 +685,7 @@ class EntityTypeTest extends TypeTestCase
'class' => self::COMPOSITE_IDENT_CLASS,
'query_builder' => function ($repository) {
return $repository->createQueryBuilder('e')
- ->where('e.id1 IN (10, 50)');
+ ->where('e.id1 IN (10, 50)');
},
'property' => 'name',
));
@@ -766,13 +771,75 @@ class EntityTypeTest extends TypeTestCase
));
}
+ public function testLoaderCaching()
+ {
+ $entity1 = new SingleIntIdEntity(1, 'Foo');
+ $entity2 = new SingleIntIdEntity(2, 'Bar');
+ $entity3 = new SingleIntIdEntity(3, 'Baz');
+
+ $this->persist(array($entity1, $entity2, $entity3));
+
+ $repository = $this->em->getRepository(self::SINGLE_IDENT_CLASS);
+ $qb = $repository->createQueryBuilder('e')->where('e.id IN (1, 2)');
+
+ $entityType = new EntityType(
+ $this->emRegistry,
+ PropertyAccess::createPropertyAccessor()
+ );
+
+ $entityTypeGuesser = new DoctrineOrmTypeGuesser($this->emRegistry);
+
+ $factory = Forms::createFormFactoryBuilder()
+ ->addType($entityType)
+ ->addTypeGuesser($entityTypeGuesser)
+ ->getFormFactory();
+
+ $formBuilder = $factory->createNamedBuilder('form', 'form');
+
+ $formBuilder->add('property1', 'entity', array(
+ 'em' => 'default',
+ 'class' => self::SINGLE_IDENT_CLASS,
+ 'query_builder' => $qb,
+ ));
+
+ $formBuilder->add('property2', 'entity', array(
+ 'em' => 'default',
+ 'class' => self::SINGLE_IDENT_CLASS,
+ 'query_builder' => $qb,
+ ));
+
+ $formBuilder->add('property3', 'entity', array(
+ 'em' => 'default',
+ 'class' => self::SINGLE_IDENT_CLASS,
+ 'query_builder' => $qb,
+ ));
+
+ $form = $formBuilder->getForm();
+
+ $form->submit(array(
+ 'property1' => 1,
+ 'property2' => 1,
+ 'property3' => 2,
+ ));
+
+ $reflectionClass = new \ReflectionObject($entityType);
+ $reflectionProperty = $reflectionClass->getProperty('loaderCache');
+ $reflectionProperty->setAccessible(true);
+
+ $loaders = $reflectionProperty->getValue($entityType);
+
+ $reflectionProperty->setAccessible(false);
+
+ $this->assertCount(1, $loaders);
+ }
+
protected function createRegistryMock($name, $em)
{
$registry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry');
$registry->expects($this->any())
- ->method('getManager')
- ->with($this->equalTo($name))
- ->will($this->returnValue($em));
+ ->method('getManager')
+ ->with($this->equalTo($name))
+ ->will($this->returnValue($em));
return $registry;
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 5755dbe4f8..20ec2e2dbf 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -40,6 +40,7 @@ class FrameworkExtension extends Extension
*
* @param array $configs
* @param ContainerBuilder $container
+ *
* @throws LogicException
*/
public function load(array $configs, ContainerBuilder $container)
@@ -686,6 +687,8 @@ class FrameworkExtension extends Extension
foreach ($dirs as $dir) {
$container->addResource(new DirectoryResource($dir));
}
+
+ $files = array();
$finder = Finder::create()
->files()
->filter(function (\SplFileInfo $file) {
@@ -695,10 +698,15 @@ class FrameworkExtension extends Extension
;
foreach ($finder as $file) {
- // filename is domain.locale.format
list($domain, $locale, $format) = explode('.', $file->getBasename(), 3);
- $translator->addMethodCall('addResource', array($format, (string) $file, $locale, $domain));
+ if (!isset($files[$locale])) {
+ $files[$locale] = array();
+ }
+
+ $files[$locale][] = (string) $file;
}
+
+ $translator->replaceArgument(4, $files);
}
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml
index 59da78fc41..7b723227da 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml
@@ -14,6 +14,7 @@
Symfony\Component\Templating\Loader\CacheLoader
Symfony\Component\Templating\Loader\ChainLoader
Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplateFinder
+ Symfony\Bundle\FrameworkBundle\Templating\Helper\AssetsHelper
@@ -58,5 +59,14 @@
+
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml
index 556206d43b..5b4f8073fe 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml
@@ -15,7 +15,6 @@
Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper
Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper
Symfony\Bundle\FrameworkBundle\Templating\Helper\StopwatchHelper
- Symfony\Bundle\FrameworkBundle\Templating\Helper\AssetsHelper
Symfony\Component\Form\Extension\Templating\TemplatingRendererEngine
Symfony\Component\Form\FormRenderer
Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables
@@ -66,12 +65,6 @@
-
-
-
-
-
-
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml
index b0dac08e3f..1c6eb323b0 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml
@@ -44,6 +44,7 @@
%kernel.cache_dir%/translations
%kernel.debug%
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
index 7e7db49afd..16a4239a99 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
@@ -232,18 +232,11 @@ abstract class FrameworkExtensionTest extends TestCase
public function testTranslator()
{
$container = $this->createContainerFromFile('full');
-
$this->assertTrue($container->hasDefinition('translator.default'), '->registerTranslatorConfiguration() loads translation.xml');
$this->assertEquals('translator.default', (string) $container->getAlias('translator'), '->registerTranslatorConfiguration() redefines translator service from identity to real translator');
+ $resources = $container->getDefinition('translator.default')->getArgument(4);
- $resources = array();
- foreach ($container->getDefinition('translator.default')->getMethodCalls() as $call) {
- if ('addResource' == $call[0]) {
- $resources[] = $call[1];
- }
- }
-
- $files = array_map(function ($resource) { return realpath($resource[1]); }, $resources);
+ $files = array_map(function ($resource) { return realpath($resource); }, $resources['en']);
$ref = new \ReflectionClass('Symfony\Component\Validator\Validation');
$this->assertContains(
strtr(dirname($ref->getFileName()).'/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR),
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/translations/messages.fr.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/translations/messages.fr.yml
new file mode 100644
index 0000000000..767141de62
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/translations/messages.fr.yml
@@ -0,0 +1 @@
+folder: répertoire
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php
index 2df09ce1a6..b3df0339b3 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php
@@ -95,13 +95,50 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
public function testTransWithCachingWithInvalidLocale()
{
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
- $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), '\Symfony\Bundle\FrameworkBundle\Tests\Translation\TranslatorWithInvalidLocale');
+ $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), array(), 'loader', '\Symfony\Bundle\FrameworkBundle\Tests\Translation\TranslatorWithInvalidLocale');
$translator->setLocale('invalid locale');
$this->setExpectedException('\InvalidArgumentException');
$translator->trans('foo');
}
+ public function testLoadRessourcesWithCaching()
+ {
+ $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
+ $resourceFiles = array(
+ 'fr' => array(
+ __DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
+ ),
+ );
+
+ // prime the cache
+ $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), $resourceFiles, 'yml');
+ $translator->setLocale('fr');
+
+ $this->assertEquals('répertoire', $translator->trans('folder'));
+
+ // do it another time as the cache is primed now
+ $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), array(), 'yml');
+ $translator->setLocale('fr');
+
+ $this->assertEquals('répertoire', $translator->trans('folder'));
+ }
+
+ public function testLoadRessourcesWithoutCaching()
+ {
+ $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
+ $resourceFiles = array(
+ 'fr' => array(
+ __DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
+ ),
+ );
+
+ $translator = $this->getTranslator($loader, array(), $resourceFiles, 'yml');
+ $translator->setLocale('fr');
+
+ $this->assertEquals('répertoire', $translator->trans('folder'));
+ }
+
protected function getCatalogue($locale, $messages)
{
$catalogue = new MessageCatalogue($locale);
@@ -182,22 +219,25 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
return $container;
}
- public function getTranslator($loader, $options = array(), $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator')
+ public function getTranslator($loader, $options = array(), $resources = array(), $loaderFomat = 'loader', $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator')
{
$translator = new $translatorClass(
$this->getContainer($loader),
new MessageSelector(),
- array('loader' => array('loader')),
- $options
+ array($loaderFomat => array($loaderFomat)),
+ $options,
+ $resources
);
- $translator->addResource('loader', 'foo', 'fr');
- $translator->addResource('loader', 'foo', 'en');
- $translator->addResource('loader', 'foo', 'es');
- $translator->addResource('loader', 'foo', 'pt-PT'); // European Portuguese
- $translator->addResource('loader', 'foo', 'pt_BR'); // Brazilian Portuguese
- $translator->addResource('loader', 'foo', 'fr.UTF-8');
- $translator->addResource('loader', 'foo', 'sr@latin'); // Latin Serbian
+ if ('loader' === $loaderFomat) {
+ $translator->addResource('loader', 'foo', 'fr');
+ $translator->addResource('loader', 'foo', 'en');
+ $translator->addResource('loader', 'foo', 'es');
+ $translator->addResource('loader', 'foo', 'pt-PT'); // European Portuguese
+ $translator->addResource('loader', 'foo', 'pt_BR'); // Brazilian Portuguese
+ $translator->addResource('loader', 'foo', 'fr.UTF-8');
+ $translator->addResource('loader', 'foo', 'sr@latin'); // Latin Serbian
+ }
return $translator;
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php
index 80b4d198ac..8e9843931d 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php
@@ -24,6 +24,7 @@ class Translator extends BaseTranslator
{
protected $container;
protected $loaderIds;
+ protected $resourceFiles;
protected $options = array(
'cache_dir' => null,
@@ -38,17 +39,19 @@ class Translator extends BaseTranslator
* * cache_dir: The cache directory (or null to disable caching)
* * debug: Whether to enable debugging or not (false by default)
*
- * @param ContainerInterface $container A ContainerInterface instance
- * @param MessageSelector $selector The message selector for pluralization
- * @param array $loaderIds An array of loader Ids
- * @param array $options An array of options
+ * @param ContainerInterface $container A ContainerInterface instance
+ * @param MessageSelector $selector The message selector for pluralization
+ * @param array $loaderIds An array of loader Ids
+ * @param array $options An array of options
+ * @param array $resourceFiles An array of resource directories
*
* @throws \InvalidArgumentException
*/
- public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array())
+ public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array(), $resourceFiles = array())
{
$this->container = $container;
$this->loaderIds = $loaderIds;
+ $this->resourceFiles = $resourceFiles;
// check option names
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
@@ -66,6 +69,7 @@ class Translator extends BaseTranslator
protected function initializeCatalogue($locale)
{
$this->initialize();
+ $this->loadResources($locale);
parent::initializeCatalogue($locale);
}
@@ -77,4 +81,19 @@ class Translator extends BaseTranslator
}
}
}
+
+ private function loadResources($locale)
+ {
+ $locales = array_merge(array($locale), $this->computeFallbackLocales($locale));
+ foreach ($locales as $locale) {
+ if (isset($this->resourceFiles[$locale])) {
+ foreach ($this->resourceFiles[$locale] as $file) {
+ // filename is domain.locale.format
+ list($domain, $locale, $format) = explode('.', basename($file), 3);
+ $this->addResource($format, $file, $locale, $domain);
+ }
+ unset($this->resourceFiles[$locale]);
+ }
+ }
+ }
}
diff --git a/src/Symfony/Component/BrowserKit/Response.php b/src/Symfony/Component/BrowserKit/Response.php
index d00836fa83..7a7130b95e 100644
--- a/src/Symfony/Component/BrowserKit/Response.php
+++ b/src/Symfony/Component/BrowserKit/Response.php
@@ -123,8 +123,9 @@ class Response
*/
public function getHeader($header, $first = true)
{
+ $normalizedHeader = str_replace('-', '_', strtolower($header));
foreach ($this->headers as $key => $value) {
- if (str_replace('-', '_', strtolower($key)) == str_replace('-', '_', strtolower($header))) {
+ if (str_replace('-', '_', strtolower($key)) === $normalizedHeader) {
if ($first) {
return is_array($value) ? (count($value) ? $value[0] : '') : $value;
}