Description
openedon Nov 25, 2024
Pimcore version
11.x (since 6.x)
Steps to reproduce
Pimcore generates its Data Object classes on class creation/update in backend and via commands pimcore:deployment:classes-rebuild
and the newer pimcore:build:classes
. The latter doesn't require a database and thus is especially useful in CI workflows such as running static analysis with PHPStan.
There is no issue with this approach with a working environment, but may become one in CI, where those data object classes do not yet exist and need to be created.
This will be the case when you reference the data object class within a Symfony service: Either by referencing a class constant (e.g. of a model class override) or referencing it in the service method's signature - see examples below.
As the console commands for (re)building the data object classes itself is a Symfony Console Command, running it will result in all services being built (CompilerPasses, DI-Container). If one of those services reference the data object class in a specific way, there's a chicken-and-egg problem as the class doesn't exist yet and the build-command can't run and exits with an error, that the class file cannot be found.
Example referencing class constant (of class override)
<?php
namespace App\EventListener;
use App\Model\DataObject\Foobar; // extends Pimcore\Model\DataObject\Foobar
use Pimcore\Event\Model\DataObjectEvent;
class TestListener
{
private const VALID_STATES = [Foobar::STATE_FOO, Foobar::STATE_BAR]; // Autoloader error
// ...
}
Example error when referencing a class constant:
Attempted to load class "Foobar" from namespace "Pimcore\Model\DataObject". Did you forget a "use" statement for another namespace?
Example referencing class in signature
App\TargetResolver\FoobarTargetResolver: ~
App\TargetResolver\TargetResolverRegistry:
arguments:
$resolvers:
foobar: '@App\TargetResolver\FoobarTargetResolver'
<?php
namespace App\TargetResolver;
readonly class TargetResolverRegistry
{
/**
* @param TargetResolverInterface[] $resolvers
*/
public function __construct(private array $resolvers)
{
}
public function get(string $type): TargetResolverInterface
{
if (!isset($this->resolvers[$type])) {
throw new \InvalidArgumentException("No resolver registered for type: $type");
}
return $this->resolvers[$type];
}
}
<?php
namespace App\TargetResolver;
use Pimcore\Model\DataObject\Concrete;
interface TargetResolverInterface
{
public function resolve(string $targetId): ?Concrete;
}
<?php
namespace App\TargetResolver;
use Pimcore\Model\DataObject\Concrete;
use Pimcore\Model\DataObject\Foobar;
class FoobarTargetResolver implements TargetResolverInterface
{
public function resolve(string $targetId): ?Foobar // ReflectionError
{
return Foobar::getById($targetId);
}
}
Example error when returning an instance of the class in a service:
Error: During inheritance of App\TargetResolver\FoobarTargetResolver, while autoloading Pimcore\Model\DataObject\Foobar: Uncaught ReflectionException: Class "Pimcore\Model\DataObject\Foobar" not found while loading "App\TargetResolver\FoobarTargetResolver".
Workarounds/Solutions
- App: Don't reference a data object class model/listing in a way described above.
- Pimcore: Make sure data object classes can be built or bootstrapped even without a functioning Symfony environment.
- Docs: Mention that in some cases it's not possible to reference those classes in services.
Crude bootstrap workaround
A CLI script (not using Symfony framework) iterates over all class definitions and creates stub/bootstrap classes for data object models/listings so that pimcore:build:classes
can build the real class files.
Actual Behavior
In CI or a pristine dev environment data object classes can't be build, if they are referenced in a way that Symfony resolves them while building DI container/services.
Expected Behavior
In CI environment, data object classes can be bootstrapped.
Activity