Element constructor have different behaviours #63
Description
Hi, #43 with #45 introduced an unexpected behaviour that I consider a BC break.
All starts by defining a custom element as invokable
:
class My\Custom\Element extends Zend\Form\Element
{}
$formElementManager = $this->getServiceLocator()->get('FormElementManager');
$formElementManager->setInvokableClass('my_element', 'My\Custom\Element');
At this point, behavious are different depending on built-in or custom elements, and what I specify as options
array key:
// Case 1
$element = $formElementManager->get('my_element', array(
'name' => 'my_name',
'my_option' => true,
));
// is equivalent to
$element = new My\Custom\Element(array(
'name' => 'my_name',
'my_option' => true,
));
var_dump(gettype($element->getName())); // array
Because it's handled by https://github.com/zendframework/zend-servicemanager/blob/release-2.7.6/src/AbstractPluginManager.php#L254
// Case 2
$element = $formElementManager->get('Zend\Form\Element', array(
'name' => 'my_name',
'my_option' => true,
));
// is equivalent to
$element = new Zend\Form\Element('my_name', array(
'name' => 'my_name',
'my_option' => true,
));
var_dump(gettype($element->getName())); // string
Because it's handled by https://github.com/zendframework/zend-form/blob/release-2.8.1/src/ElementFactory.php#L69
And this is going even harder to understand if we use the Zend\Form\Factory
// Case 3
$factory = new Zend\Form\Factory($formElementManager);
$element = $factory->create(array(
'type' => 'my_element',
'name' => 'my_name',
'options' => array(
'my_option' => true,
),
));
// is equivalent to
$element = new My\Custom\Element(array(
'my_option' => true,
));
$element->setName('my_name');
$element->setOptions(array(
'my_option' => true,
));
var_dump(gettype($element->getName())); // string
// Case 4
$factory = new Zend\Form\Factory($formElementManager);
$element = $factory->create(array(
'type' => 'Zend\Form\Element',
'name' => 'my_name',
'options' => array(
'my_option' => true,
),
));
// is equivalent to
$element = new Zend\Form\Element('element', array(
'my_option' => true,
));
$element->setName('my_name');
$element->setOptions(array(
'my_option' => true,
));
var_dump(gettype($element->getName())); // string
Without name
// Case 5
$factory = new Zend\Form\Factory($formElementManager);
$element = $factory->create(array(
'type' => 'my_element',
'options' => array(
'my_option' => true,
),
));
// is equivalent to
$element = new My\Custom\Element(array(
'my_option' => true,
));
$element->setOptions(array(
'my_option' => true,
));
var_dump(gettype($element->getName())); // array
// Case 6
$factory = new Zend\Form\Factory($formElementManager);
$element = $factory->create(array(
'type' => 'Zend\Form\Element',
'options' => array(
'my_option' => true,
),
));
// is equivalent to
$element = new Zend\Form\Element('element', array(
'my_option' => true,
));
$element->setOptions(array(
'my_option' => true,
));
var_dump(gettype($element->getName())); // string
The transition fro SMv2 to SMv3 is irrelevant, because Zend\ServiceManager\Factory\InvokableFactory
acts exactly like Zend\ServiceManager\AbstractPluginManager::createFromInvokable
.
The sources of misunderstanding are:
- The
name
acts differently in different contexts, inside or outsideoptions
, through Factory or not - Built-in plugins use
ElementFactory
to face [1], while custom invokables are instantiated naturally - The constructor may behave weird when inherited by
Zend\Form\Element
and not overwritten
The fact is, if name and options are required, the constructor must be declared in the interface.
If name and options are optional, Zend\Form\Element
must not have a constructor at all.
In my humble opinion, we should decide to be clear about this topic, and:
- Remove the constructor from
Zend\Form\Element
- Lazy-load
Zend\Form\Fieldset::$iterator
[PriorityList] - Remove the new
Zend\Form\ElementFactory
(in favour ofcreateFromInvokable
orInvokableFactory
depending on SM version) - Document that FormElementManager only instantiate the object, and configuration must be done with the Factory
So
- Built-in elements will behave like always
- Custom elements extended by
Zend\Form\Element
without constructor will behave like build-in elements - Custom elements extended by
Zend\Form\Element
with constructor will receiveoptions
array - Custom elements implementing
Zend\Form\ElementInterface
with constructor will receiveoptions
array
With no more array-name.
What do you think?