8000 merged branch bschussek/performance (PR #4881) · sigues/symfony@7a138f0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7a138f0

Browse files
committed
merged branch bschussek/performance (PR symfony#4881)
Commits ------- a924dab [OptionsResolver] Made the OptionsResolver clonable 70307e5 [Form] Improved EntityType performance by caching the EntityChoiceList 8298d8c [Form] Improved ChoiceType performance by caching ChoiceList objects Discussion ---------- [Form] Improved performance of ChoiceType and EntityType Bug fix: no Feature addition: no Backwards compatibility break: no Symfony2 tests pass: yes Fixes the following tickets: - Todo: -
2 parents c4f3719 + a924dab commit 7a138f0

File tree

9 files changed

+338
-13
lines changed

9 files changed

+338
-13
lines changed

src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ abstract class DoctrineType extends AbstractType
2929
*/
3030
protected $registry;
3131

32+
/**
33+
* @var array
34+
*/
35+
private $choiceListCache = array();
36+
3237
public function __construct(ManagerRegistry $registry)
3338
{
3439
$this->registry = $registry;
@@ -46,6 +51,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
4651

4752
public function setDefaultOptions(OptionsResolverInterface $resolver)
4853
{
54+
$choiceListCache =& $this->choiceListCache;
4955
$registry = $this->registry;
5056
$type = $this;
5157

@@ -59,17 +65,34 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
5965
return null;
6066
};
6167

62-
$choiceList = function (Options $options) use ($registry) {
68+
$choiceList = function (Options $options) use ($registry, &$choiceListCache, &$time) {
6369
$manager = $registry->getManager($options['em']);
6470

65-
return new EntityChoiceList(
66-
$manager,
71+
$choiceHashes = is_array($options['choices'])
72+
? array_map('spl_object_hash', $options['choices'])
73+
: $options['choices'];
74+
75+
$hash = md5(json_encode(array(
76+
spl_object_hash($manager),
6777
$options['class'],
6878
$options['property'],
6979
$options['loader'],
70-
$options['choices'],
80+
$choiceHashes,
7181
$options['group_by']
72-
);
82+
)));
83+
84+
if (!isset($choiceListCache[$hash])) {
85+
$choiceListCache[$hash] = new EntityChoiceList(
86+
$manager,
87+
$options['class'],
88+
$options['property'],
89+
$options['loader'],
90+
$options['choices'],
91+
$options['group_by']
92+
);
93+
}
94+
95+
return $choiceListCache[$hash];
7396
};
7497

7598
$resolver->setDefaults(array(
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Tests\Form\Type;
13+
14+
use Symfony\Component\Form\Tests\FormPerformanceTestCase;
15+
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIdentEntity;
16+
use Doctrine\ORM\Tools\SchemaTool;
17+
use Symfony\Bridge\Doctrine\Tests\DoctrineOrmTestCase;
18+
use Symfony\Component\Form\Extension\Core\CoreExtension;
19+
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
20+
21+
/**
22+
* @author Bernhard Schussek <bschussek@gmail.com>
23+
*/
24+
class EntityTypePerformanceTest extends FormPerformanceTestCase
25+
{
26+
const ENTITY_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIdentEntity';
27+
28+
/**
29+
* @var \Doctrine\ORM\EntityManager
30+
*/
31+
private $em;
32+
33+
protected function getExtensions()
34+
{
35+
$manager = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry');
36+
$manager->expects($this->any())
37+
->method('getManager')
38+
->will($this->returnValue($this->em));
39+
40+
return array(
41+
new CoreExtension(),
42+
new DoctrineOrmExtension($manager)
43+
);
44+
}
45+
46+
protected function setUp()
47+
{
48+
if (!class_exists('Symfony\Component\Form\Form')) {
49+
$this->markTestSkipped('The "Form" component is not available');
50+
}
51+
52+
if (!class_exists('Doctrine\DBAL\Platforms\MySqlPlatform')) {
53+
$this->markTestSkipped('Doctrine DBAL is not available.');
54+
}
55+
56+
if (!class_exists('Doctrine\Common\Version')) {
57+
$this->markTestSkipped('Doctrine Common is not available.');
58+
}
59+
60+
if (!class_exists('Doctrine\ORM\EntityManager')) {
61+
$this->markTestSkipped('Doctrine ORM is not available.');
62+
}
63+
64+
$this->em = DoctrineOrmTestCase::createTestEntityManager();
65+
66+
parent::setUp();
67+
68+
$schemaTool = new SchemaTool($this->em);
69+
$classes = array(
70+
$this->em->getClassMetadata(self::ENTITY_CLASS),
71+
);
72+
73+
try {
74+
$schemaTool->dropSchema($classes);
75+
} catch (\Exception $e) {
76+
}
77+
78+
try {
79+
$schemaTool->createSchema($classes);
80+
} catch (\Exception $e) {
81+
}
82+
83+
$ids = range(1, 300);
84+
85+
foreach ($ids as $id) {
86+
$name = 65 + chr($id % 57);
87+
$this->em->persist(new SingleIdentEntity($id, $name));
88+
}
89+
90+
$this->em->flush();
91+
}
92+
93+
/**
94+
* This test case is realistic in collection forms where each
95+
* row contains the same entity field.
96+
*/
97+
public function testCollapsedEntityField()
98+
{
99+
$this->setMaxRunningTime(1);
100+
101+
for ($i = 0; $i < 20; ++$i) {
102+
$form = $this->factory->create('entity', null, array(
103+
'class' => self::ENTITY_CLASS,
104+
));
105+
106+
// force loading of the choice list
107+
$form->createView();
108+
}
109+
}
110+
}

src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@
3131

3232
class ChoiceType extends AbstractType
3333
{
34+
/**
35+
* Caches created choice lists.
36+
* @var array
37+
*/
38+
private $choiceListCache = array();
39+
3440
/**
3541
* {@inheritdoc}
3642
*/
@@ -123,12 +129,20 @@ public function finishView(FormViewInterface $view, FormInterface $form, array $
123129
*/
124130
public function setDefaultOptions(OptionsResolverInterface $resolver)
125131
{
126-
$choiceList = function (Options $options) {
127-
return new SimpleChoiceList(
128-
// Harden against NULL values (like in EntityType and ModelType)
129-
null !== $options['choices'] ? $options['choices'] : array(),
130-
$options['preferred_choices']
131-
);
132+
$choiceListCache =& $this->choiceListCache;
133+
134+
$choiceList = function (Options $options) use (&$choiceListCache) {
135+
// Harden against NULL values (like in EntityType and ModelType)
136+
$choices = null !== $options['choices'] ? $options['choices'] : array();
137+
138+
// Reuse existing choice lists in order to increase performance
139+
$hash = md5(json_encode(array($choices, $options['preferred_choices'])));
140+
141+
if (!isset($choiceListCache[$hash])) {
142+
$choiceListCache[$hash] = new SimpleChoiceList($choices, $options['preferred_choices']);
143+
}
144+
145+
return $choiceListCache[$hash];
132146
};
133147

134148
$emptyData = function (Options $options) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
13+
14+
use Symfony\Component\Form\Tests\FormPerformanceTestCase;
15+
16+
/**
17+
* @author Bernhard Schussek <bschussek@gmail.com>
18+
*/
19+
class ChoiceTypePerformanceTest extends FormPerformanceTestCase
20+
{
21+
/**
22+
* This test case is realistic in collection forms where each
23+
* row contains the same choice field.
24+
*/
25+
public function testSameChoiceFieldCreatedMultipleTimes()
26+
{
27+
$this->setMaxRunningTime(1);
28+
$choices = range(1, 300);
29+
30+
for ($i = 0; $i < 100; ++$i) {
31+
$this->factory->create('choice', rand(1, 400), array(
32+
'choices' => $choices,
33+
));
34+
}
35+
}
36+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests;
13+
14+
use Symfony\Component\Form\FormFactory;
15+
use Symfony\Component\Form\Extension\Core\CoreExtension;
16+
17+
/**
18+
* @author Bernhard Schussek <bschussek@gmail.com>
19+
*/
20+
class FormIntegrationTestCase extends \PHPUnit_Framework_TestCase
21+
{
22+
/**
23+
* @var \Symfony\Component\Form\FormFactoryInterface
24+
*/
25+
protected $factory;
26+
27+
protected function setUp()
28+
{
29+
if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
30+
$this->markTestSkipped('The "EventDispatcher" component is not available');
31+
}
32+
33+
$this->factory = new FormFactory($this->getExtensions());
34+
}
35+
36+
protected function getExtensions()
37+
{
38+
return array(
39+
new CoreExtension(),
40+
);
41+
}
42+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests;
13+
14+
/**
15+
* Base class for performance tests.
16+
*
17+
* Copied from Doctrine 2's OrmPerformanceTestCase.
18+
*
19+
* @author robo
20+
* @author Bernhard Schussek <bschussek@gmail.com>
21+
*/
22+
class FormPerformanceTestCase extends FormIntegrationTestCase
23+
{
24+
/**
25+
* @var integer
26+
*/
27+
protected $maxRunningTime = 0;
28+
29+
/**
30+
*/
31+
protected function runTest()
32+
{
33+
$s = microtime(true);
34+
parent::runTest();
35+
$time = microtime(true) - $s;
36+
37+
if ($this->maxRunningTime != 0 && $time > $this->maxRunningTime) {
38+
$this->fail(
39+
sprintf(
40+
'expected running time: <= %s but was: %s',
41+
42+
$this->maxRunningTime,
43+
$time
44+
)
45+
);
46+
}
47+
}
48+
49+
/**
50+
* @param integer $maxRunningTime
51+
* @throws InvalidArgumentException
52+
* @since Method available since Release 2.3.0
53+
*/
54+
public function setMaxRunningTime($maxRunningTime)
55+
{
56+
if (is_integer($maxRunningTime) && $maxRunningTime >= 0) {
57+
$this->maxRunningTime = $maxRunningTime;
58+
} else {
59+
throw new \InvalidArgumentException;
60+
}
61+
}
62+
63+
/**
64+
* @return integer
65+
* @since Method available since Release 2.3.0
66+
*/
67+
public function getMaxRunningTime()
68+
{
69+
return $this->maxRunningTime;
70+
}
71+
}

src/Symfony/Component/OptionsResolver/OptionsResolver.php

Copy file name to clipboard
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ public function __construct()
6767
$this->defaultOptions = new Options();
6868
}
6969

70+
/**
71+
* Clones the resolver.
72+
*/
73+
public function __clone()
74+
{
75+
$this->defaultOptions = clone $this->defaultOptions;
76+
}
77+
7078
/**
7179
* {@inheritdoc}
7280
*/
@@ -210,7 +218,7 @@ public function isRequired($option)
210218
/**
211219
* {@inheritdoc}
212220
*/
213-
public function resolve(array $options)
221+
public function resolve(array $options = array())
214222
{
215223
$this->validateOptionsExistence($options);
216224
$this->validateOptionsCompleteness($options);

src/Symfony/Component/OptionsResolver/OptionsResolverInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,5 +206,5 @@ public function isRequired($option);
206206
* @throws Exception\OptionDefinitionException If a cyclic dependency is detected
207207
* between two lazy options.
208208
*/
209-
public function resolve(array $options);
209+
public function resolve(array $options = array());
210210
}

0 commit comments

Comments
 (0)
108
0