3

I have a symfony 5 project.

And I faced a problem with autoloading.

I use default autoloading configuration, provided by the framework.

I have class, abstract class and interface defined in the same directory under the same namespace.

The problem is, when I describe my class like:

class MyClass extends MyAbstractClass

class MyAbstractClass implements MyClassInterface

I get autoloading error:

Attempted to load class "MyClass" from namespace "App\Entities".  
Did you forget a "use" statement for another namespace? 

A line from logs:

Error thrown while running command "myproject:mycommand". Message: "Class 'App\Entities\MyClass' not found" ...

If I use:

class MyClass extends MyAbstractClass

or

class MyClass implements MyClassInterface

or

class MyClass extends MyAbstractClass implements MyClassInterface

then the error is gone - everything works fine.

The error appears only if I use

class MyAbstractClass implements MyClassInterface

How to resolve the issue?

I need to make that abstract class would implement an interface, so any classes that will extend the AbstractClass would be compatible with the interface.

PHP 7.4.1 (cli)

Sergej
  • 2,030
  • 1
  • 18
  • 28
  • Typically your abstract class would basically operate as an interface. But it's kind of hard to follow your question. Consider updating it with an actual class, abstract class and interface complete with namespaces. They can all be empty but should be testable. I might add that the Symfony AbstractController implements an interface so I suspect you just have a typo somewhere. – Cerad Jan 23 '20 at 15:43
  • no typo ... if I place `implements MyClassIntterface` in every class that extends `MyAbstractClass` - then it works. But once I place `implements MyVlassInterface` in the `class MyAbstractClass` - the error appears. – Sergej Jan 23 '20 at 15:56
  • Fair enough. I just ran a quick test with empty classes and it all worked as expected. I even added a method to the interface, implemented it in the abstract class and it still works. I just think it is a bit unusual in Symfony to have a directory called Entities instead of Entity. Hence the typo remark. – Cerad Jan 23 '20 at 16:08
  • 1. Symfony is flexible, so you can call it like you want. And that is not typo. 2. Entity usualy is a directory with entities that are binded with doctrine ORM. In my case these entities are not binded with doctrine ORM. I don't use it it all. 3. There is definately something strange. I do not belive in magic, but something happens wrong. I have created two total empty classes. without any inheritance/implementations. Named one Abc, named the second Test. If I write class MyClass extends Test - I get similar error. If I type class MyClass extends Abc - I get no error. brrrr. – Sergej Jan 24 '20 at 08:03
  • Also I tried directly to use `require_once __DIR__ . DIRECTORY_SEPARATOR . 'MyClassInterface.php'` and it throws the fatal error, that the `MyClassInterface` is already defined... but if I remove the require_once - it says that `MyClass` not found... (`MyClass extends AbstractClass` and `AbstractClass implements MyClassInterface`) – Sergej Jan 24 '20 at 08:05
  • And If I remove `implements MyClassInterface` - then it works again – Sergej Jan 24 '20 at 08:11
  • I debugged this case a bit. And found out that the file with the class is included, but is missing in `get_declared_classes()`. How can it be? – Sergej Jan 24 '20 at 09:35
  • Updated php to the latest v7.4.2, error still present – Sergej Jan 24 '20 at 09:50
  • Yes! I found the problem. Finally! I will post an answer – Sergej Jan 24 '20 at 10:03

1 Answers1

0

The problem is the order, the files are being loaded.

I found out the problem, bit I did not fix it yet.

The problem is that the autoloader includes files in the alphabetical order, but not in the order, the files must be included.

My actual class names are:

AbstractEntity

EntityInterface

Ticket

Implementation is:

interface EntityInterface {}

abstract class AbstractEntity implements EntityInterface {}

class Ticket extends AbstractEntity {}

The files are included in the next order (alphabetical):

AbstractEntity.php

EntityInterface.php

Ticket.php

AbstractEntity.php - this file declares the AbstractEntity class.

This class implements EntityInterface interface, so the EntityInterface.php file must be already included before AbstractEntity.php.

the correct order must be:

EntityInterface.php

AbstractEntity.php

Ticket.php

It is easy to check, by just renaming the AbstractEntity to ObstractEntity, then the alphabetical order will be:

EntityInterface.php

ObstractEntity.php

Ticket.php

For now it just left to fix the autoload order. But... How should it be fixed fast? Any ideas? Is it vendor autoload problem or Symfony?

AFAIK there is some kind of option in the composer.json which states:

    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "App\\Tests\\": "tests/"
        }
    },

UPDATE: Alright, I've created a new project - and everything works well. It works both For Entity directory and for Entities. Versions all the same.

But once I renamed Entites to Entity in my source project - everything started to work with no errors.

So this is something strange thing.

I can only think that Symfony is not so flexible and the classes should be stored in Entity directory then the autolaod works well.

For now it's

Ticket.php

AbstractEntity.php

EntityInterface.php

In the way they require each other.

Sergej
  • 2,030
  • 1
  • 18
  • 28
  • 1
    That is indeed strange. Autoload is a php thing and it includes files in the order that they are needed. So interfaces will be loaded before classes. Abstract classes will be loaded before derived classes. etc. I wonder if this is an issue with preloading which was introduced in 7.4? Though 7.4.2 was supposed to fix the preload issues. You might try disabling preloading if indeed you have it enabled. https://stitcher.io/blog/preloading-in-php-74 – Cerad Jan 24 '20 at 15:24