3

I'm attempting to make use of the new preloading feature available since PHP 7.4.

I ran composer install --no-dev --optimize-autoloader to generate a list of all available classes in the project, and used the following preload.php script to preload them:

$files = require 'vendor/composer/autoload_classmap.php';

foreach (array_unique($files) as $file) {
    opcache_compile_file($file);
}

And configured this preload script in my opcache.ini file:

opcache.preload=/path/to/preload.php

And restarted php-fpm. Now systemctl status php-fpm.service reports the following warnings:

PHP Warning: Can't preload unlinked class Brick\Money\Context\CashContext: Unknown type dependencies in ... on line 16
PHP Warning: Can't preload unlinked class Brick\Money\Context\AutoContext: Unknown type dependencies in ... on line 17
PHP Warning: Can't preload unlinked class Brick\Math\BigRational: Unknown type dependencies in ... on line 17
PHP Warning: Can't preload unlinked class Brick\Math\BigInteger: Unknown type dependencies in ... on line 20
PHP Warning: Can't preload unlinked class Brick\Math\BigDecimal: Unknown type dependencies in ... on line 15

What does "Unknown type dependencies" mean? How can I get these classes preloaded?

Note: I'm the maintainer of the offending libraries Brick\Math and Brick\Money, so if these are in need of modifications to make them preloadable, I'm all ears!

BenMorel
  • 34,448
  • 50
  • 182
  • 322
  • The RFC mentions this: *Only classes without unresolved parent, interfaces, traits and constant values may be preloaded.* This means there are files containing types (classes, interfaces, traits) that you have to preload *before* the ones mentioned in the error messages. – Kubo2 Dec 29 '19 at 15:23
  • What OS is it? . – revo Dec 29 '19 at 15:24
  • @Kubo2 I'm not sure whether they have to be preloaded *before*: what about circular dependencies? I would expect them to work in any order, but I may be wrong. Anyway, I've had other warnings related to classes that were not included in the preloading script, but these were different, they said "Unknown parent" when a class attempted to extend a non-preloaded class. This one says "Unknown type dependencies", and I'm trying to find out what this means. – BenMorel Dec 29 '19 at 15:30
  • @revo It's CentOS 7.7, does that make any difference? – BenMorel Dec 29 '19 at 15:32
  • To avoid dependency issues, you can preload using `require` instead of `opcache_compile_file()`. This will handle circular dependencies fine, but will hard error if preloading fails (instead of just warning). – NikiC Dec 29 '19 at 16:52
  • 1
    @NikiC I somehow expected the PHP files to be just *compiled to opcodes* during the execution of the preload script, and the linking to be done **after** the script has ended, and all necessary classes have been preloaded; this would prevent any dependency issue. Isn't this the case? Does the order matter? – BenMorel Dec 29 '19 at 18:00
  • That is how it basically works. The caveat is that a class can only be preloaded IFF its dependencies can be preloaded. When opcache_compile_file() is used, this needs to be proven a priori, which is quite hard. Circular dependencies are not supported in this mode right now. When require is used, this is checked a posteriori instead, which is easy and has no issues with circular dependencies. – NikiC Dec 29 '19 at 18:08

1 Answers1

0

It means that PHP couldn't find a class in a preloaded file at runtime. This only happens when there might be methods that are incompatible:

Preloading: Relax known type restrictions
Check whether there is a parent/interface/trait method with the same name and only then require the type to be known. This reduces the number of cases where this triggers in practice a lot.

Preloaded files don't use the Composer autoloader, so classes that don't get preloaded won't exist.

Symfony fixed this by creating a Preloader class that can load its dependencies. This is how to use it to preload LoaderInterface and AnnotationClassLoder:

<?php 
// preload.php
$classes[] = 'Symfony\Component\Config\Loader\LoaderInterface';
$classes[] = 'Symfony\Component\Routing\Loader\AnnotationClassLoader';

Preloader::preload($classes);

Until there's a better way to do it, you can copy that class or preload all classes that the library depends on.


If you can allow preloading failures to be hard errors, use the answer from NikiC:

To avoid dependency issues, you can preload using require instead of opcache_compile_file(). This will handle circular dependencies fine, but will hard error if preloading fails (instead of just warning).

Anonymous
  • 11,748
  • 6
  • 35
  • 57
  • Thank you for your answer. I still can't figure out what's wrong with the offending classes, though. Take for example [CashContext.php on line 16](https://github.com/brick/money/blob/d86bf7f36599bd9e9228221c5e8f10a509d49fa3/src/Context/CashContext.php#L16), it's just a class implementing an interface **in the same package**. – BenMorel Dec 29 '19 at 15:42
  • @Benjamin The error could be better but I think `BigNumber` is the problem there. PHP is still not very good at handling covariance and contravariance in preloaded files. – Anonymous Dec 29 '19 at 15:53
  • So you mean that any class accepting or returning `BigNumber` in any of its methods could potentially not be preloaded? – BenMorel Dec 29 '19 at 16:33
  • @Benjamin Yes. There are some times when PHP can figure it out by comparing the full class name strings but it isn't doing that here. It could be because `BigNumber` is `abstract` or another overload exists somewhere else. Preloading is still new so it should get better. – Anonymous Dec 29 '19 at 17:01
  • @Rain Yes, preloading can cause a hard error when you use `require`. But that should mean that there's an actual error. NikiC helped a lot with the [preloading pull request](https://github.com/php/php-src/pull/3538) so he knows what he's talking about. – Anonymous Dec 31 '19 at 13:07