Open jrajamaki opened 1 week ago
Would you agree to take PR for this?
If so, how it should work?
Yes. Sure. PR is very welcome. Skipping the check sounds alright.
We could save class in begin()
and use it in end()
instead of getting definition from container.
We could save class in
begin()
and use it inend()
instead of getting definition from container.
That's how it works already. end()
just has some safeguards so that developer wouldn't do anything stupid like trigger end()
without begin()
or mix two widgets:
WidgetA::begin([...]);
WidgetB::begin([...]);
WidgetA::end();
WidgetB::end();
The initial problem is that WidgetA
might not be WidgetA
because begin()
uses Yii::createObject()
to create the widget and widget's class might change if configured so in container configuration. But get_called_class
always returns WidgetA
in end()
and wouldn't match the widget's class created in begin()
if reconfigured. https://github.com/yiisoft/yii2/commit/0dbc4d3f35b1bfa0c4217ae744e30a139c8ad2c8 tried to fix this but didn't consider scenario where container configuration is callable.
I mean we could this in begin()
:
self::$resolvedClasses[get_called_class()] = get_class($widget);
and this in end()
:
$calledClass = self::$resolvedClasses[get_called_class()] ?? get_called_class();
then we could remove these lines completely:
I mean we could this
Seems good, this is something what I had in my mind
public static function end()
{
if (empty(self::$stack)) {
throw new InvalidCallException('Unexpected ' . get_called_class() . '::end() call. A matching begin() is not found.');
}
$widget = array_pop(self::$stack);
$calledClass = get_called_class();
if (Yii::$container->has($calledClass)) {
$definition = Yii::$container->getDefinitions()[$calledClass];
if (is_callable($definition)) {
$reflection = new \ReflectionFunction($definition);
$returnType = $reflection->getReturnType();
if ($returnType && $returnType instanceof \ReflectionNamedType) {
$calledClass = $returnType->getName();
} else {
unset($calledClass);
}
} elseif (isset($definition['class'])) {
$calledClass = $definition['class'];
}
}
if (isset($calledClass) && get_class($widget) !== $calledClass) {
throw new InvalidCallException('Expecting end() of ' . get_class($widget) . ', found ' . $calledClass);
}
/* @var $widget Widget */
if ($widget->beforeRun()) {
$result = $widget->run();
$result = $widget->afterRun($result);
echo $result;
}
return $widget;
}
but using self::$resolvedClasses
seems more straightforward.
What steps will reproduce the problem?
Using basic app as an example, create your own widget, e.g.
and configure di container to replace the original implementation using closure:
Now when accessing basic app's "Contact" page, then app crashes with error "Cannot use object of type Closure as array".
What is the expected result?
It would work.
What do you get instead?
"Cannot use object of type Closure as array" error.
Additional info
Similar to yiisoft/yii2-debug#234. Caused by commit 0dbc4d3f35b1bfa0c4217ae744e30a139c8ad2c8.