How does PHP spl_autoload_register resolve circular dependencies with require_once?

How does PHP spl_autoload_register resolve circular dependencies with require_once?

Circular dependencies can be solved in some cases, but not in all. Let's start with an example when it fails. Suppose we have three classes defined in separate files:

cat.php

class Cat extends Animal {} 

animal.php

 require_once('cat.php'); class Animal extends Creature {} 

creature.php

 class Creature {} 

Let's say we also have a script that has an autoloader and creates an instance of Animal:

run.php

 spl_autoload_register(function($className) { require_once("$className.php"); }); $a = new Animal(); 

Running this script with "php run.php" will result in a Fatal Fatal error:

PHP Fatal error: class "Animal" not found in ... /Cat.php

I think this makes intuitive sense to me due to the circular relationship between Animal and Cat:

  • Autoloader tries to load animal.php
  • Loading animal.php causes the loading of cat.php due to require_once ()
  • Downloading cat.php failed because it extends Animal, and Animal cannot be loaded twice by the autoloader.

Here are some changes that guarantee that we do not get fatal

  • animal.php should not have require_once ('cat.php')
    • This seems like the best solution as it effectively eliminates the circular relationship between Animal and Cat
  • The class of animals should not expand the Being
  • Instead of using autoloader in run.php just use require_once () for animal.php and creature.php

Questions:

  • Why does # 2 work? Why does an Animal that does not spread the Creature resolve the circular relationship between Animal and Cat?
  • Why does # 3 work? Doesn't the autoloader just execute require_once () under the hood?

The full code (with some additional logging) from these examples can be found here.

+6
source share
3 answers

Since your autoloader does — what its name says — automatically load your classes, you do not need any other requirements other than those in the autoloader function.

If you use require_once instead of require in it, it will still load it only once, regardless of whether you extend it or just create an object.

So just use the code you posted in your question and remove require_once () in your animal.php, since the autoloader already requires it.


Note: if you do not want to create your own autoloader, you can use composer autoloader. It is easy to install and very useful as it deals with subdirectories and forces you to follow a strict namespace convention.

If you want to do this, you must first install the composer. Then you create a file called composer.json in your base directory with the following contents

 { "autoload": { "psr-4": { "YourProject\\": "src/" } } } 

Then you need to run the following command on the command line:

 cd path/to/your/project composer dump-autoload 

If you have done this, put your classes in basedirectory / src. Note that now you need to provide all classes with a namespace, in this case, if there was a namespace YourProject . You are finally done!

Now go to your base directory and create a file, call index.php:

 require_once ('vendor/autoloader.php'); $a = new YourProject\Animal(); 

Sorry for the long note, sir!

+3
source

Remove require_once('cat.php'); from animal.php

If the object does not exist, the autoloader is called a separate call to each object that does not exist. Therefore, if each object has its own file name, you are fine.

Currently, the code stream looks something like this:

 $a = new Animal(); #animal does not exist, spl_autoload('animal'); #include cat.php #creature does not exist, spl_autoload('creature') 

However, this goes the way of the script. The value of cat.php may not be in the same directory as animal.php , so the file may not exist.

However, for a more dynamic startup system, I recommend the PSR-4 autoloader . It uses namespaces to locate the class in the directory tree.

 new \path\to\Animal(); 

Causes the autoloader to include the file / path / to / animal.php

0
source

The reason your example does not work is because you are trying to use the definition of Animal before defining it.

First, you try to autostart Animal:

 $a = new Animal(); // This triggers the require_once of animal.php 

In animal.php, you need cat.php before you define the Animal class

 require_once('cat.php'); // This file will be fully evaluated before the next line class Animal extends Creature {} // Since the prior line relied on the existence of Animal, you get a compile error. 

The Animal class cannot be automatically loaded inside cat.php since you already called require_once in its file; he simply was not fully appreciated.

As others have said, this is the result of mixing direct require_once calls with the autoloader. As another workaround, you can move your require_once('cat.php') to the end of the file, and it should work as you expect.

0
source

Source: https://habr.com/ru/post/1011494/


All Articles