merged branch bschussek/options-efficiency (PR #5102)

Commits
-------

57ac110 [Form] Removed unnecessary method call
27ab56d [OptionsResolver] Removed LazyOption class and improved performance a tiny bit

Discussion
----------

[OptionsResolver] Removed LazyOption class and improved performance

Bug fix: no
Feature addition: no
Backwards compatibility break: no
Symfony2 tests pass: yes
Fixes the following tickets: -
Todo: -
This commit is contained in:
Fabien Potencier 2012-07-29 16:31:48 +02:00
commit 83c41ff790
4 changed files with 38 additions and 143 deletions

View File

@ -131,7 +131,7 @@ class FormRenderer implements FormRendererInterface
*/
public function searchAndRenderBlock(FormView $view, $blockNameSuffix, array $variables = array())
{
$renderOnlyOnce = in_array($blockNameSuffix, array('row', 'widget'));
$renderOnlyOnce = 'row' === $blockNameSuffix || 'widget' === $blockNameSuffix;
if ($renderOnlyOnce && $view->isRendered()) {
return '';

View File

@ -1,73 +0,0 @@
<?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\OptionsResolver;
/**
* An option that is evaluated lazily using a closure.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LazyOption
{
/**
* The underlying closure.
* @var \Closure
*/
private $closure;
/**
* The previous default value of the option.
* @var mixed
*/
private $previousValue;
/**
* Creates a new lazy option.
*
* @param \Closure $closure The closure used for initializing the
* option value.
* @param mixed $previousValue The previous value of the option. This
* value is passed to the closure when it is
* evaluated.
*
* @see evaluate()
*/
public function __construct(\Closure $closure, $previousValue = null)
{
$this->closure = $closure;
$this->previousValue = $previousValue;
}
/**
* Evaluates the underlying closure and returns its result.
*
* The given Options instance is passed to the closure as first argument.
* The previous default value set in the constructor is passed as second
* argument.
*
* @param Options $options The container with all concrete options.
*
* @return mixed The result of the closure.
*/
public function evaluate(Options $options)
{
$previousValue = $this->previousValue;
$closure = $this->closure;
if ($previousValue instanceof self) {
$previousValue = $this->previousValue->evaluate($options);
}
// Performs a bit better than __invoke() and call_user_func()
return $closure($options, $previousValue);
}
}

View File

@ -21,7 +21,7 @@ use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException;
class Options implements \ArrayAccess, \Iterator, \Countable
{
/**
* A list of option values and LazyOption instances.
* A list of option values.
* @var array
*/
private $options = array();
@ -33,19 +33,13 @@ class Options implements \ArrayAccess, \Iterator, \Countable
private $normalizers = array();
/**
* A list storing the names of all options that need to be normalized.
* @var array
*/
private $normalization = array();
/**
* A list storing the names of all LazyOption instances as keys.
* A list of closures for evaluating lazy options.
* @var array
*/
private $lazy = array();
/**
* A list of Boolean locks for each LazyOption.
* A list containing the currently locked options.
* @var array
*/
private $lock = array();
@ -95,6 +89,7 @@ class Options implements \ArrayAccess, \Iterator, \Countable
// Setting is equivalent to overloading while discarding the previous
// option value
unset($this->options[$option]);
unset($this->lazy[$option]);
$this->overload($option, $value);
}
@ -126,9 +121,6 @@ class Options implements \ArrayAccess, \Iterator, \Countable
}
$this->normalizers[$option] = $normalizer;
// Each option for which a normalizer exists needs to be normalized
$this->normalization[$option] = true;
}
/**
@ -150,6 +142,7 @@ class Options implements \ArrayAccess, \Iterator, \Countable
}
$this->options = array();
$this->lazy = array();
foreach ($options as $option => $value) {
$this->overload($option, $value);
@ -184,25 +177,30 @@ class Options implements \ArrayAccess, \Iterator, \Countable
}
// If an option is a closure that should be evaluated lazily, store it
// inside a LazyOption instance.
// in the "lazy" property.
if ($value instanceof \Closure) {
$reflClosure = new \ReflectionFunction($value);
$params = $reflClosure->getParameters();
if (isset($params[0]) && null !== ($class = $params[0]->getClass()) && __CLASS__ === $class->name) {
$currentValue = isset($params[1]) && isset($this->options[$option]) ? $this->options[$option] : null;
$value = new LazyOption($value, $currentValue);
// Initialize the option if no previous value exists
if (!isset($this->options[$option])) {
$this->options[$option] = null;
}
// Store which options are lazy for more efficient resolving
$this->lazy[$option] = true;
// Ignore previous lazy options if the closure has no second parameter
if (!isset($this->lazy[$option]) || !isset($params[1])) {
$this->lazy[$option] = array();
}
$this->options[$option] = $value;
// Store closure for later evaluation
$this->lazy[$option][] = $value;
return;
}
}
// Reset lazy flag and locks by default
// Remove lazy options by default
unset($this->lazy[$option]);
$this->options[$option] = $value;
@ -233,7 +231,7 @@ class Options implements \ArrayAccess, \Iterator, \Countable
$this->resolve($option);
}
if (isset($this->normalization[$option])) {
if (isset($this->normalizers[$option])) {
$this->normalize($option);
}
@ -269,6 +267,7 @@ class Options implements \ArrayAccess, \Iterator, \Countable
unset($this->options[$option]);
unset($this->lazy[$option]);
unset($this->normalizers[$option]);
}
/**
@ -286,6 +285,7 @@ class Options implements \ArrayAccess, \Iterator, \Countable
$this->options = array();
$this->lazy = array();
$this->normalizers = array();
}
/**
@ -301,14 +301,16 @@ class Options implements \ArrayAccess, \Iterator, \Countable
// Performance-wise this is slightly better than
// while (null !== $option = key($this->lazy))
foreach ($this->lazy as $option => $isLazy) {
foreach ($this->lazy as $option => $closures) {
// Double check, in case the option has already been resolved
// by cascade in the previous cycles
if (isset($this->lazy[$option])) {
$this->resolve($option);
}
}
foreach ($this->normalization as $option => $normalize) {
if (isset($this->normalization[$option])) {
foreach ($this->normalizers as $option => $normalizer) {
if (isset($this->normalizers[$option])) {
$this->normalize($option);
}
}
@ -443,6 +445,10 @@ class Options implements \ArrayAccess, \Iterator, \Countable
*/
private function resolve($option)
{
// The code duplication with normalize() exists for performance
// reasons, in order to save a method call.
// Remember that this method is potentially called a couple of thousand
// times and needs to be as efficient as possible.
if (isset($this->lock[$option])) {
$conflicts = array();
@ -456,7 +462,9 @@ class Options implements \ArrayAccess, \Iterator, \Countable
}
$this->lock[$option] = true;
$this->options[$option] = $this->options[$option]->evaluate($this);
foreach ($this->lazy[$option] as $closure) {
$this->options[$option] = $closure($this, $this->options[$option]);
}
unset($this->lock[$option]);
// The option now isn't lazy anymore
@ -475,6 +483,10 @@ class Options implements \ArrayAccess, \Iterator, \Countable
*/
private function normalize($option)
{
// The code duplication with resolve() exists for performance
// reasons, in order to save a method call.
// Remember that this method is potentially called a couple of thousand
// times and needs to be as efficient as possible.
if (isset($this->lock[$option])) {
$conflicts = array();
@ -495,6 +507,6 @@ class Options implements \ArrayAccess, \Iterator, \Countable
unset($this->lock[$option]);
// The option is now normalized
unset($this->normalization[$option]);
unset($this->normalizers[$option]);
}
}

View File

@ -1,44 +0,0 @@
<?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\OptionsResolver\Tests;
use Symfony\Component\OptionsResolver\LazyOption;
use Symfony\Component\OptionsResolver\Options;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LazyOptionTest extends \PHPUnit_Framework_TestCase
{
public function testDontCacheEvaluatedPreviousValue()
{
$previousValue = new LazyOption(function (Options $options) {
return $options['foo'];
});
$lazyOption = new LazyOption(function (Options $options, $previousValue) {
return $previousValue;
}, $previousValue);
// If provided with two different option sets, two different results
// should be returned
$options1 = new Options();
$options1['foo'] = 'bar';
$this->assertSame('bar', $lazyOption->evaluate($options1));
$options2 = new Options();
$options2['foo'] = 'boo';
$this->assertSame('boo', $lazyOption->evaluate($options2));
}
}