admin管理员组

文章数量:1208155

Because I want my applications to follow some kind of DDD structure, I want my Controller, Entity, Repository, Form etc to sit within a specific directory. For example: src/Appication/Dealer/Controller/DealerController.php

In order to load this controller, i have created a routing service: src/Routing/ApplicationLoader.php. That works fine if i run it in a test script. It gives me a list of the routes that are available within the dealer controller.

But symfony is giving me this:

Service "@App\Routing\ApplicationLoader" not found: the container inside "Symfony\Component\DependencyInjection\Argument\ServiceLocator" is a smaller service locator that only knows about the "kernel" and "security.route_loader.logout" services in @App\Routing\ApplicationLoader (which is being imported from "/var/www/html/config/routes.yaml"). Make sure the "App\Routing\ApplicationLoader" bundle is correctly registered and loaded in the application kernel class. If the bundle is registered, make sure the bundle path "@App\Routing\ApplicationLoader" is not empty.

It's not even hitting the ApplicationLoader.php (tested by adding an exit e.g.)

How can i get symfony to understand it needs to load my script? ;)

(and i think my routes and services are correct - see below)

To test this, just create a clean symfony installation, create an entity and a crud with the maker, create the directory structure: src/Application/%entityName%/

  • Controller/
  • Entity/
  • Repository/
  • Form/

and put the respective files in there. Then modify the services and routes.yaml according to the ones below and put the ApplicationLoader.php in the src/Routing folder.

This is my services.yaml

parameters:

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: false      # Automatically injects dependencies in your services.
        autoconfigure: false # Automatically registers your services as commands, event subscribers, etc.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Kernel.php'

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones
    App\Routing\ApplicationLoader:
        public: true
        arguments:
            $controllerDirectory: '%kernel.project_dir%/src/Application'
        tags:
            - { name: 'router.loader', priority: 0 }

This is my routes.yaml

# Handles routes for generic controllers in src/Controller/

controllers:
resource:
path: ../src/Controller/
namespace: App\Controller
type: attribute

# Handles custom routes for controllers under src/Application

custom_routes:
resource: '@App\Routing\ApplicationLoader'
type: service

This is my ApplicationLoader.php:

<?php

namespace App\Routing;

use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Finder\Finder;
use ReflectionClass;

class ApplicationLoader extends Loader
{
private bool $isLoaded = false;

    private string $controllerDirectory;
    
    public function __construct(string $controllerDirectory)
    {
        $this->controllerDirectory = $controllerDirectory;
    }
    
    public function load($resource, ?string $type = null): RouteCollection
    {
    
        error_log("Loading resource: $resource with type: $type");
        if (true === $this->isLoaded) {
            throw new \RuntimeException('Do not add the "application" loader twice');
        }
    
        $routes = new RouteCollection();
    
        // Use Symfony Finder to scan the directory for controllers
        $finder = new Finder();
        $finder->files()->in($this->controllerDirectory)->name('*Controller.php');
    
        // Iterate through controller files and add routes
        foreach ($finder as $file) {
            $className = $this->getClassNameFromFile($file);
            error_log("Processing controller: $className");
    
            $reflectionClass = new ReflectionClass($className);
    
            // Check if the class has route annotations or attributes
            if ($reflectionClass->isSubclassOf('Symfony\Bundle\FrameworkBundle\Controller\AbstractController')) {
                $this->addRoutesFromAttributes($reflectionClass, $routes);
            }
        }
    
        return $routes;
    }
    
    public function supports($resource, ?string $type = null): bool
    {
        error_log("Checking support for resource: $resource with type: $type");
    
        return 'service' === $type;
    }
    
    private function getClassNameFromFile($file): string
    {
        // Resolve the absolute path of the controller directory
        $controllerDirectoryRealPath = realpath($this->controllerDirectory);
    
        // Resolve the file's real path
        $fileRealPath = $file->getRealPath();
    
        // Calculate the relative path by removing the base directory
        $relativePath = substr($fileRealPath, strlen($controllerDirectoryRealPath) + 1);
    
        // Normalize directory separators to backslashes for namespaces
        $relativePath = str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath);
    
        // Remove the file extension (.php) and prepend the base namespace
        return 'App\Application\' . str_replace('.php', '', $relativePath);
    }
    
    private function addRoutesFromAttributes(\ReflectionClass $reflectionClass, RouteCollection $routes): void
    {
        // Check the class-level attributes for the base path (e.g., #[Route('/dealer')])
        $classAttributes = $reflectionClass->getAttributes(\Symfony\Component\Routing\Attribute\Route::class);
        $classBasePath = '';
    
        foreach ($classAttributes as $classAttribute) {
            $classRoute = $classAttribute->newInstance();
            $classBasePath = $classRoute->getPath();
        }
    
        // Iterate through each method to find route attributes
        foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
            // Ignore inherited methods from AbstractController
    
            // Skip methods from parent classes
            if ($method->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
                continue;
            }
    
            // Get method attributes for routes
            $methodAttributes = $method->getAttributes(\Symfony\Component\Routing\Attribute\Route::class);
    
            foreach ($methodAttributes as $methodAttribute) {
    
                $methodRoute = $methodAttribute->newInstance();
    
                // Combine the class-level base path and the method-level path
                $fullPath = rtrim($classBasePath, '/') . '/' . ltrim($methodRoute->getPath() ?? '', '/');
    
                // Add the route to the collection
                $routes->add(
                    $methodRoute->getName(),
                    new Route(
                        $fullPath,
                        ['_controller' => $reflectionClass->getName() . '::' . $method->getName()],
                        [],        // Default requirements
                        [],        // Default options
                        '',        // Host
                        [],        // Schemes
                        $methodRoute->getMethods()
                    )
                );
            }
        }
    }

}

this is my test.php script that lives in /public

<?php

require_once __DIR__ . '/../vendor/autoload.php';

use App\\Routing\ApplicationLoader;
use Symfony\Component\Routing\RouteCollection;

try {
// Define the directory where your Application controllers are located
$controllerDirectory = __DIR__ . '/../src/Application';

    // Instantiate your ApplicationLoader with the specified directory
    $loader = new ApplicationLoader($controllerDirectory);
    
    // Invoke the loader to load routes
    $routes = $loader->load(null, 'service');
    
    // Display loaded routes
    echo "Loaded Routes:\n";
    foreach ($routes as $name => $route) {
        echo " - $name: " . $route->getPath() . "<br/>";
        echo "   Controller: " . $route->getDefault('_controller') . "<br/>";
        echo "   Methods: " . implode(', ', $route->getMethods()) . "<br/><br/>";
    }

} catch (Throwable $e) {
// Catch and display errors for debugging
echo "Error: " . $e-\>getMessage() . "\<br/\>";
echo $e-\>getTraceAsString() . "\<br/\>";
}

Please help ;)

Because I want my applications to follow some kind of DDD structure, I want my Controller, Entity, Repository, Form etc to sit within a specific directory. For example: src/Appication/Dealer/Controller/DealerController.php

In order to load this controller, i have created a routing service: src/Routing/ApplicationLoader.php. That works fine if i run it in a test script. It gives me a list of the routes that are available within the dealer controller.

But symfony is giving me this:

Service "@App\Routing\ApplicationLoader" not found: the container inside "Symfony\Component\DependencyInjection\Argument\ServiceLocator" is a smaller service locator that only knows about the "kernel" and "security.route_loader.logout" services in @App\Routing\ApplicationLoader (which is being imported from "/var/www/html/config/routes.yaml"). Make sure the "App\Routing\ApplicationLoader" bundle is correctly registered and loaded in the application kernel class. If the bundle is registered, make sure the bundle path "@App\Routing\ApplicationLoader" is not empty.

It's not even hitting the ApplicationLoader.php (tested by adding an exit e.g.)

How can i get symfony to understand it needs to load my script? ;)

(and i think my routes and services are correct - see below)

To test this, just create a clean symfony installation, create an entity and a crud with the maker, create the directory structure: src/Application/%entityName%/

  • Controller/
  • Entity/
  • Repository/
  • Form/

and put the respective files in there. Then modify the services and routes.yaml according to the ones below and put the ApplicationLoader.php in the src/Routing folder.

This is my services.yaml

parameters:

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: false      # Automatically injects dependencies in your services.
        autoconfigure: false # Automatically registers your services as commands, event subscribers, etc.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Kernel.php'

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones
    App\Routing\ApplicationLoader:
        public: true
        arguments:
            $controllerDirectory: '%kernel.project_dir%/src/Application'
        tags:
            - { name: 'router.loader', priority: 0 }

This is my routes.yaml

# Handles routes for generic controllers in src/Controller/

controllers:
resource:
path: ../src/Controller/
namespace: App\Controller
type: attribute

# Handles custom routes for controllers under src/Application

custom_routes:
resource: '@App\Routing\ApplicationLoader'
type: service

This is my ApplicationLoader.php:

<?php

namespace App\Routing;

use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Finder\Finder;
use ReflectionClass;

class ApplicationLoader extends Loader
{
private bool $isLoaded = false;

    private string $controllerDirectory;
    
    public function __construct(string $controllerDirectory)
    {
        $this->controllerDirectory = $controllerDirectory;
    }
    
    public function load($resource, ?string $type = null): RouteCollection
    {
    
        error_log("Loading resource: $resource with type: $type");
        if (true === $this->isLoaded) {
            throw new \RuntimeException('Do not add the "application" loader twice');
        }
    
        $routes = new RouteCollection();
    
        // Use Symfony Finder to scan the directory for controllers
        $finder = new Finder();
        $finder->files()->in($this->controllerDirectory)->name('*Controller.php');
    
        // Iterate through controller files and add routes
        foreach ($finder as $file) {
            $className = $this->getClassNameFromFile($file);
            error_log("Processing controller: $className");
    
            $reflectionClass = new ReflectionClass($className);
    
            // Check if the class has route annotations or attributes
            if ($reflectionClass->isSubclassOf('Symfony\Bundle\FrameworkBundle\Controller\AbstractController')) {
                $this->addRoutesFromAttributes($reflectionClass, $routes);
            }
        }
    
        return $routes;
    }
    
    public function supports($resource, ?string $type = null): bool
    {
        error_log("Checking support for resource: $resource with type: $type");
    
        return 'service' === $type;
    }
    
    private function getClassNameFromFile($file): string
    {
        // Resolve the absolute path of the controller directory
        $controllerDirectoryRealPath = realpath($this->controllerDirectory);
    
        // Resolve the file's real path
        $fileRealPath = $file->getRealPath();
    
        // Calculate the relative path by removing the base directory
        $relativePath = substr($fileRealPath, strlen($controllerDirectoryRealPath) + 1);
    
        // Normalize directory separators to backslashes for namespaces
        $relativePath = str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath);
    
        // Remove the file extension (.php) and prepend the base namespace
        return 'App\Application\' . str_replace('.php', '', $relativePath);
    }
    
    private function addRoutesFromAttributes(\ReflectionClass $reflectionClass, RouteCollection $routes): void
    {
        // Check the class-level attributes for the base path (e.g., #[Route('/dealer')])
        $classAttributes = $reflectionClass->getAttributes(\Symfony\Component\Routing\Attribute\Route::class);
        $classBasePath = '';
    
        foreach ($classAttributes as $classAttribute) {
            $classRoute = $classAttribute->newInstance();
            $classBasePath = $classRoute->getPath();
        }
    
        // Iterate through each method to find route attributes
        foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
            // Ignore inherited methods from AbstractController
    
            // Skip methods from parent classes
            if ($method->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
                continue;
            }
    
            // Get method attributes for routes
            $methodAttributes = $method->getAttributes(\Symfony\Component\Routing\Attribute\Route::class);
    
            foreach ($methodAttributes as $methodAttribute) {
    
                $methodRoute = $methodAttribute->newInstance();
    
                // Combine the class-level base path and the method-level path
                $fullPath = rtrim($classBasePath, '/') . '/' . ltrim($methodRoute->getPath() ?? '', '/');
    
                // Add the route to the collection
                $routes->add(
                    $methodRoute->getName(),
                    new Route(
                        $fullPath,
                        ['_controller' => $reflectionClass->getName() . '::' . $method->getName()],
                        [],        // Default requirements
                        [],        // Default options
                        '',        // Host
                        [],        // Schemes
                        $methodRoute->getMethods()
                    )
                );
            }
        }
    }

}

this is my test.php script that lives in /public

<?php

require_once __DIR__ . '/../vendor/autoload.php';

use App\\Routing\ApplicationLoader;
use Symfony\Component\Routing\RouteCollection;

try {
// Define the directory where your Application controllers are located
$controllerDirectory = __DIR__ . '/../src/Application';

    // Instantiate your ApplicationLoader with the specified directory
    $loader = new ApplicationLoader($controllerDirectory);
    
    // Invoke the loader to load routes
    $routes = $loader->load(null, 'service');
    
    // Display loaded routes
    echo "Loaded Routes:\n";
    foreach ($routes as $name => $route) {
        echo " - $name: " . $route->getPath() . "<br/>";
        echo "   Controller: " . $route->getDefault('_controller') . "<br/>";
        echo "   Methods: " . implode(', ', $route->getMethods()) . "<br/><br/>";
    }

} catch (Throwable $e) {
// Catch and display errors for debugging
echo "Error: " . $e-\>getMessage() . "\<br/\>";
echo $e-\>getTraceAsString() . "\<br/\>";
}

Please help ;)

Share Improve this question edited Jan 20 at 10:04 Stok asked Jan 19 at 1:27 StokStok 511 silver badge4 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2

OK, the answer was actually much simpler and what I wanted could actually be achieved with just changing the routes.yaml to:

Standard controllers (keep this as it's working):

resource:
    path: ../src/Controller/
    namespace: App\Controller
type: attribute

All application routes (this will scan all Controller directories under Application application_routes):

resource:
    path: ../src/Application/
    namespace: App\Application
type: attribute

Leave the services.yaml as it is originally and change the mapping section in the config/packages/doctrine.yaml to this:

    mappings:
        Default:
            is_bundle: false
            type: attribute
            dir: '%kernel.project_dir%/src/Entity'
            prefix: 'App\Entity'
            alias: Default
        Controller:
            is_bundle: false
            type: attribute
            dir: '%kernel.project_dir%/src/Controller'
            prefix: 'App\Controller'
            alias: Controller
        Application:
            is_bundle: false
            type: attribute
            dir: '%kernel.project_dir%/src/Application'
            prefix: 'App\Application'
            alias: Application

This way applications that live in src/Controller are loaded but also everything inside src/Application/MyApp/Controller.

本文标签: