-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[Form] Improved performance of ChoiceType and EntityType #4881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,11 @@ abstract class DoctrineType extends AbstractType | |
*/ | ||
protected $registry; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
private $choiceListCache = array(); | ||
|
||
public function __construct(ManagerRegistry $registry) | ||
{ | ||
$this->registry = $registry; | ||
|
@@ -46,6 +51,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) | |
|
||
public function setDefaultOptions(OptionsResolverInterface $resolver) | ||
{ | ||
$choiceListCache =& $this->choiceListCache; | ||
$registry = $this->registry; | ||
$type = $this; | ||
|
||
|
@@ -59,17 +65,34 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) | |
return null; | ||
}; | ||
|
||
$choiceList = function (Options $options) use ($registry) { | ||
$choiceList = function (Options $options) use ($registry, &$choiceListCache, &$time) { | ||
$manager = $registry->getManager($options['em']); | ||
|
||
return new EntityChoiceList( | ||
$manager, | ||
$choiceHashes = is_array($options['choices']) | ||
? array_map('spl_object_hash', $options['choices']) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Getting |
||
: $options['choices']; | ||
|
||
$hash = md5(json_encode(array( | ||
spl_object_hash($manager), | ||
$options['class'], | ||
$options['property'], | ||
$options['loader'], | ||
$options['choices'], | ||
$choiceHashes, | ||
$options['group_by'] | ||
); | ||
))); | ||
|
||
if (!isset($choiceListCache[$hash])) { | ||
$choiceListCache[$hash] = new EntityChoiceList( | ||
$manager, | ||
$options['class'], | ||
$options['property'], | ||
$options['loader'], | ||
$options['choices'], | ||
$options['group_by'] | ||
); | ||
} | ||
|
||
return $choiceListCache[$hash]; | ||
}; | ||
|
||
$resolver->setDefaults(array( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Bridge\Doctrine\Tests\Form\Type; | ||
|
||
use Symfony\Component\Form\Tests\FormPerformanceTestCase; | ||
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIdentEntity; | ||
use Doctrine\ORM\Tools\SchemaTool; | ||
use Symfony\Bridge\Doctrine\Tests\DoctrineOrmTestCase; | ||
use Symfony\Component\Form\Extension\Core\CoreExtension; | ||
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension; | ||
|
||
/** | ||
* @author Bernhard Schussek <bschussek@gmail.com> | ||
*/ | ||
class EntityTypePerformanceTest extends FormPerformanceTestCase | ||
{ | ||
const ENTITY_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIdentEntity'; | ||
|
||
/** | ||
* @var \Doctrine\ORM\EntityManager | ||
*/ | ||
private $em; | ||
|
||
protected function getExtensions() | ||
{ | ||
$manager = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); | ||
$manager->expects($this->any()) | ||
->method('getManager') | ||
->will($this->returnValue($this->em)); | ||
|
||
return array( | ||
new CoreExtension(), | ||
new DoctrineOrmExtension($manager) | ||
); | ||
} | ||
|
||
protected function setUp() | ||
{ | ||
if (!class_exists('Symfony\Component\Form\Form')) { | ||
$this->markTestSkipped('The "Form" component is not available'); | ||
} | ||
|
||
if (!class_exists('Doctrine\DBAL\Platforms\MySqlPlatform')) { | ||
$this->markTestSkipped('Doctrine DBAL is not available.'); | ||
} | ||
|
||
if (!class_exists('Doctrine\Common\Version')) { | ||
$this->markTestSkipped('Doctrine Common is not available.'); | ||
} | ||
|
||
if (!class_exists('Doctrine\ORM\EntityManager')) { | ||
$this->markTestSkipped('Doctrine ORM is not available.'); | ||
} | ||
|
||
$this->em = DoctrineOrmTestCase::createTestEntityManager(); | ||
|
||
parent::setUp(); | ||
|
||
$schemaTool = new SchemaTool($this->em); | ||
$classes = array( | ||
$this->em->getClassMetadata(self::ENTITY_CLASS), | ||
); | ||
|
||
try { | ||
$schemaTool->dropSchema($classes); | ||
} catch (\Exception $e) { | ||
} | ||
|
||
try { | ||
$schemaTool->createSchema($classes); | ||
} catch (\Exception $e) { | ||
} | ||
|
||
$ids = range(1, 300); | ||
|
||
foreach ($ids as $id) { | ||
$name = 65 + chr($id % 57); | ||
$this->em->persist(new SingleIdentEntity($id, $name)); | ||
} | ||
|
||
$this->em->flush(); | ||
} | ||
|
||
/** | ||
* This test case is realistic in collection forms where each | ||
* row contains the same entity field. | ||
*/ | ||
public function testCollapsedEntityField() | ||
{ | ||
$this->setMaxRunningTime(1); | ||
|
||
for ($i = 0; $i < 20; ++$i) { | ||
$form = $this->factory->create('entity', null, array( | ||
'class' => self::ENTITY_CLASS, | ||
)); | ||
|
||
// force loading of the choice list | ||
$form->createView(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Form\Tests\Extension\Core\Type; | ||
|
||
use Symfony\Component\Form\Tests\FormPerformanceTestCase; | ||
|
||
/** | ||
* @author Bernhard Schussek <bschussek@gmail.com> | ||
*/ | ||
class ChoiceTypePerformanceTest extends FormPerformanceTestCase | ||
{ | ||
/** | ||
* This test case is realistic in collection forms where each | ||
* row contains the same choice field. | ||
*/ | ||
public function testSameChoiceFieldCreatedMultipleTimes() | ||
{ | ||
$this->setMaxRunningTime(1); | ||
$choices = range(1, 300); | ||
|
||
for ($i = 0; $i < 100; ++$i) { | ||
$this->factory->create('choice', rand(1, 400), array( | ||
'choices' => $choices, | ||
)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Form\Tests; | ||
|
||
use Symfony\Component\Form\FormFactory; | ||
use Symfony\Component\Form\Extension\Core\CoreExtension; | ||
|
||
/** | ||
* @author Bernhard Schussek <bschussek@gmail.com> | ||
*/ | ||
class FormIntegrationTestCase extends \PHPUnit_Framework_TestCase | ||
{ | ||
/** | ||
* @var \Symfony\Component\Form\FormFactoryInterface | ||
*/ | ||
protected $factory; | ||
|
||
protected function setUp() | ||
{ | ||
if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { | ||
$this->markTestSkipped('The "EventDispatcher" component is not available'); | ||
} | ||
|
||
$this->factory = new FormFactory($this->getExtensions()); | ||
} | ||
|
||
protected function getExtensions() | ||
{ | ||
return array( | ||
new CoreExtension(), | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Form\Tests; | ||
|
||
/** | ||
* Base class for performance tests. | ||
* | ||
* Copied from Doctrine 2's OrmPerformanceTestCase. | ||
* | ||
* @author robo | ||
* @author Bernhard Schussek <bschussek@gmail.com> | ||
*/ | ||
class FormPerformanceTestCase extends FormIntegrationTestCase | ||
{ | ||
/** | ||
* @var integer | ||
*/ | ||
protected $maxRunningTime = 0; | ||
|
||
/** | ||
*/ | ||
protected function runTest() | ||
{ | ||
$s = microtime(true); | ||
parent::runTest(); | ||
$time = microtime(true) - $s; | ||
|
||
if ($this->maxRunningTime != 0 && $time > $this->maxRunningTime) { | ||
$this->fail( | ||
sprintf( | ||
'expected running time: <= %s but was: %s', | ||
|
||
$this->maxRunningTime, | ||
$time | ||
) | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* @param integer $maxRunningTime | ||
* @throws InvalidArgumentException | ||
* @since Method available since Release 2.3.0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is wrong :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oops, will be fixed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well I said I copied the code :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, but then it should mention it is a Doctrine release :) |
||
*/ | ||
public function setMaxRunningTime($maxRunningTime) | ||
{ | ||
if (is_integer($maxRunningTime) && $maxRunningTime >= 0) { | ||
$this->maxRunningTime = $maxRunningTime; | ||
} else { | ||
throw new \InvalidArgumentException; | ||
} | ||
} | ||
|
||
/** | ||
* @return integer | ||
* @since Method available since Release 2.3.0 | ||
*/ | ||
public function getMaxRunningTime() | ||
{ | ||
return $this->maxRunningTime; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is
$time
here ? it seems undefined and unusedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed already. I was faster than you hehehe ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bschussek it is not fixed: https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php#L67
It seems like @fabpot was faster to merge :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's in the next PR.