yiisoft / yii2

Yii 2: The Fast, Secure and Professional PHP Framework
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
14.23k stars 6.91k forks source link

PHP 7.4 preloading - unresolved initializer for static property - Validator classes #17633

Closed EtienneBruines closed 3 years ago

EtienneBruines commented 4 years ago

What steps will reproduce the problem?

Using PHP 7.4 with preloading.

What is the expected result?

No errors.

What do you get instead?

[23-Oct-2019 13:48:50 UTC] PHP Warning:  Can't preload class yii\validators\Validator with unresolved initializer for static property $builtInValidators in /home/e.bruines00000/workspaces/mossaino/prod/vendor/yiisoft/yii2/validators/Validator.php on line 57
[23-Oct-2019 13:48:50 UTC] PHP Warning:  Can't preload unlinked class yii\validators\UrlValidator: Parent with unresolved initializers yii\validators\Validator in /home/e.bruines00000/workspaces/mossaino/prod/vendor/yiisoft/yii2/validators/UrlValidator.php on line 24
[23-Oct-2019 13:48:50 UTC] PHP Warning:  Can't preload unlinked class yii\validators\UniqueValidator: Parent with unresolved initializers yii\validators\Validator in /home/e.bruines00000/workspaces/mossaino/prod/vendor/yiisoft/yii2/validators/UniqueValidator.php on line 42
[23-Oct-2019 13:48:50 UTC] PHP Warning:  Can't preload unlinked class yii\validators\StringValidator: Parent with unresolved initializers yii\validators\Validator in /home/e.bruines00000/workspaces/mossaino/prod/vendor/yiisoft/yii2/validators/StringValidator.php on line 20

Preload.php

<?php

opcache_compile_file('vendor/yiisoft/yii2/base/Configurable.php');
opcache_compile_file('vendor/yiisoft/yii2/base/BaseObject.php');
opcache_compile_file('vendor/yiisoft/yii2/base/Component.php');
opcache_compile_file('vendor/yiisoft/yii2/validators/Validator.php');
opcache_compile_file('vendor/yiisoft/yii2/validators/DateValidator.php');

Problem guesstimate

Circular reference between Validator (in $builtInValidators) and DateValidator.

Additional info

Q A
Yii version 2.0.29
PHP version 7.4 RC
Operating system Linux
samdark commented 4 years ago

Any idea on how to fix it?

EtienneBruines commented 4 years ago

The only "working" solution so far has been to make Validator not (statically) dependent on DateValidator. The two constants (TYPE_DATETIME and TYPE_TIME) would then be defined on the Validator class and not the DateValidator class.

For backwards-compatibility, one would probably define those constants on Validator as well as on DateValidator, but with the same values.


class Validator extends Component
{
    public const TYPE_DATETIME = 'datetime';
    public const TYPE_TIME = 'time';
}

class DateValidator extends Validator
{
    public const TYPE_DATETIME = Validator::TYPE_DATETIME;
    public const TYPE_TIME = Validator::TYPE_TIME;
}
EtienneBruines commented 4 years ago

Alternatively, depending on personal preference:

Separate classes for DateTimeValidator and TimeValidator (possibly extending DateValidator), so the class member type does not need to be configured for the Validator::$builtInValidators static variable.

rob006 commented 4 years ago

Does it work if you include also UrlValidator,UniqueValidatorandStringValidator`?

EtienneBruines commented 4 years ago

@rob006 Unfortunately not. It is really related to the DateValidator::TYPE_DATETIME and DateValidator::TYPE_TIME statements.

brussens commented 4 years ago

Author right, because all classes are loaded according to their hierarchy. When compiling yii\base\Validator, the preloader does not know anything about yii\base\DateValidator yet. If yii\base\DateValidator is loaded before yii\base\Validator, everything happens exactly the opposite.

There are actually two ways out.

  1. Without breaking BC to saw a crutch in the form of yii\base\DateValidatorInterface and to transfer in it constants, use it in yii\base\Validator and implements in yii\base\DateValidator.
// crutch interface
interface DateValidatorInterface
{
    const TYPE_DATE = 'date';
    const TYPE_DATETIME = 'datetime';
    const TYPE_TIME = 'time';
}
use DateValidatorInterface;

class Validator extends Component
{
    public static $builtInValidators = [
       ...
        'datetime' => [
            'class' => 'yii\validators\DateValidator',
            'type' => DateValidatorInterface::TYPE_DATETIME,
        ],
        'time' => [
            'class' => 'yii\validators\DateValidator',
            'type' => DateValidatorInterface::TYPE_TIME,
        ],
        ...
    ];
}
class DateValidator extends Validator implements DateValidatorInterface
{
}

and preload DateValidatorInterface before yii\validators\Validator

  1. Break a little BC and remove the crutches declarations in yii\base\Validator a la date, datetime and force them to register in yii\base\Model::rules().
class Validator extends Component
{
    public static $builtInValidators = [
       ...
        'datetime' => [
            'class' => 'yii\validators\DateValidator',
        ]
        ...
    ];
}

I'm afraid the third option hasn't occurred to me yet. Please don't throw tomatoes for such delusional ideas)

rob006 commented 4 years ago

You could use require instead of opcache_compile_file() - it should trigger autoloading and load all necessary classes in correct order.

brussens commented 4 years ago

@rob006 Yeah, there's a fairy tale that require might help us. Except for one problem. Require throws a fatal error in case of a file search problem. Neither include nor require is working at the moment. Fatal errors are thrown out because DateTimeValidator is not defined in the so-called compilation. In any case, I will be glad to see a real working piece of code that can solve the warning problem.

rob006 commented 4 years ago

@brussens Do you have enabled Composer autoloader while loading these files? Also PHP 7.4.1 should have some fixes related to autoloading, it may be worth checking this on this version.

brussens commented 4 years ago

@rob006 It's quite possible that at 7.4.1 this was fixed. Now I'm finishing the compilation of 7.4.1 RC, so let's see what comes out of it.

brussens commented 4 years ago

In general, 7.4.1RC1 is better, but still does not work. On Linux, unfortunately, there is no way to try it yet, since 7.3 is everywhere. Windows 10x64:

PHP Fatal error:  Class yii\web\UrlNormalizerRedirectException uses internal class Throwable during preloading, which is not supported on Windows in Unknown on line 0

Apparently, classes that implement system interfaces cannot be connected via include.

rob006 commented 4 years ago

Classes with dependencies on internal classes cannot be preloaded on Windows at all - this is known limitation of preloading.

brussens commented 4 years ago

Tonight I'll try to finish the preloader and put it out.

brussens commented 4 years ago

By the way, experimentally it turned out that the php daemon simply falls on Windows when trying to preload some classes. This behavior is not quite clear, but such files are essentially 2:

  1. yii\log\FileTarget
  2. yii\db\mysql\Schema

yii\db\mysql\QueryBuilder can't be preloaded because yii\db\mysql\Schema not exists in preload.

brussens commented 4 years ago

But we need a cross-platform solution to this problem, so I will install Debian today and make an extension for preload.

samdark commented 4 years ago

@brussens any luck with it?

brussens commented 4 years ago

@samdark currently experimenting with writing a plugin for composer

aksafan commented 4 years ago

@samdark @brussens Any solutions so far?

samdark commented 4 years ago

No.

SamMousa commented 3 years ago

I don't think we should attempt to solve this in Yii2 at all. We should however take this issue to heart as one of the reasons we should try to avoid static properties / dependencies, and make 100% sure we don't have them in Yii3.

This article does have some insights into how the errors of OP can be avoided: https://stitcher.io/blog/preloading-in-php-74

rob006 commented 3 years ago

@SamMousa I don't think it is related to static properties / dependencies. It is more like a problem of recursive dependencies in class definitions - you need to have Validator definition in order to parse DateValidator, but you need DateValidator definition in order to parse Validator. You probably will get the same problem with constant or non-static property.

BTW: Is anyone tested this issue with autoloader & class_exists() instead of opcache_compile_file() (on non-windows environment)? Preloading had rough start, but I would be surprised if this would still not work.

SamMousa commented 3 years ago

The first error in OP is about static properties, it causes in this example, Validator to not get preloaded. This in turn causes subclasses to fail.

The dependency loading can be automated using the autoloader, that's explained in the article I mentioned.

rob006 commented 3 years ago

The first error in OP is about static properties

It is not because of static property, it is because parent class uses symbols from child class in its definition, so you have chicken-egg problem. You will get exact the same result with non-static property or constant.

Also I have verified this issue with autoloader + class_exists() and preloading works fine, so I suggest to just close this issue - using opcache_compile_file() in preloading scripts seems to be pretty niche anyway.

SamMousa commented 3 years ago

It is not because of static property, it is because parent class uses symbols from child class in its definition, so you have chicken-egg problem. You will get exact the same result with non-static property or constant.

Ah, I see now what the static property default value is.. you're right. And I agree with closing it :)