45

In the src/Utils directory, I created a custom class Foo for various things. I'm looking for a way to get the absolute root path of the symfony 4 project

From a controller, its easy with :

$webPath = $this->get('kernel')->getProjectDir();

But from a custom class I created in my src/Utils directory, how can I get the root path directory ?

I could pass the path from the controller to the Foo class :

$webPath = $this->get('kernel')->getProjectDir();
$faa = new Foo($webPath);
$faa->doSomething();

but I think its more proper to store this information in the Foo class and have only "controller logic" in the controller class

spacecodeur
  • 2,206
  • 7
  • 35
  • 71

5 Answers5

71

In Symfony AppKernel class is handling the project root directory under method getProjectDir(). To get it in the controller you can do:

$projectRoot = $this->get('kernel')->getProjectDir();

it will return you a project root directory.

If you need the project root directory in one of your classes you have two choices which I will present to you. First is passing AppKernel as dependency:

class Foo 
{
    /** KernelInterface $appKernel */
    private $appKernel;

    public function __construct(KernelInterface $appKernel)
    {
        $this->appKernel = $appKernel;
    }
}

Thanks to Symfony 4 autowiring dependencies it will be autmomaticaly injeted into your class and you could access it by doing:

$this->appKernel->getProjectDir();

But please notice: I don't think it's a good idea, until you have real need and more to do with AppKernel class than getting the project root dir. Specially if you think later on creating about unit tests for your class. You would automatically increase complexity by having a need to create mock of AppKernel for example.

Second option and IMHO better would be to pass only a string with path to directory. You could achieve this by defining a service inside config/services.yaml like this:

services:
    (...)
    MyNamespace\Foo:
        arguments:
            - %kernel.project_dir%

and your constructor would look like:

class Foo 
{
    /** string $rootPath */
    private $rootPath;

    public function __construct(string $rootPath)
    {
        $this->rootPath = $rootPath;
    }
}
Tomasz
  • 4,847
  • 2
  • 32
  • 41
  • 3
    For the very first explanation you should also mention to include `use Symfony\Component\HttpKernel\KernelInterface;` above the class file. – Kyeno Oct 15 '20 at 07:40
  • `config/services.yaml and $rootPath` are good idea instead of `KernelInterface` – Selim Reza Jun 17 '22 at 09:10
34

Without Kernel injection

config/services.yaml

services:
    _defaults:
        autowire: true
        autoconfigure: true
        bind:
            $projectDir: '%kernel.project_dir%'

....

class Foo
{
    private $projectDir;

    public function __construct(string $projectDir)
    {
        $this->projectDir = $projectDir;
    }
}
Artem
  • 1,426
  • 12
  • 17
  • solution in the official doc : https://symfony.com/blog/new-in-symfony-4-2-important-deprecations – Dte Jun 19 '20 at 12:04
27

If your class is extending: Symfony\Bundle\FrameworkBundle\Controller\AbstractController, then you can get the root dir as

$projectRoot = $this->getParameter('kernel.project_dir');

or

Inject ContainerBagInterface to your controller

protected $projectRoot;

public function __construct(ContainerBagInterface $containerBag)
{
    $this->projectRoot = $containerBag->get('kernel.project_dir');;
}

or

Even better and the recommended approach

Inject the root_dir to your Foo class. Add the following to your config under services

services:
    foo:
        class:     App\Path\To\Foo
        arguments: ['%kernel.project_dir%']

The container should pass the argument to your class on resolving, the Foo class should look like this

<?php

namespace App\Path\To;

class Foo {

  private $projectDir;

  public function __construct($projectDir) 
  {
     $this->projectDir = $projectDir;
  }
}
f_i
  • 3,084
  • 28
  • 31
  • In 2019/2020/2021, with current versions of Symfony (4,5+) this is the way to do this. – gview Dec 07 '20 at 18:57
  • 1
    The last example worked great for me. However, I had to change `%kernel.root_dir%` to `%kernel.project_dir%`. – Nathanael Jan 22 '21 at 18:52
8

This is work :

// from Foo class
use Symfony\Component\HttpKernel\KernelInterface;
...
class Foo{
    private $rootDir;
    public function __construct(KernelInterface $kernel)
    {
        $this->rootDir = $kernel->getProjectDir();
    }
    public function myfoomethod(){
        return $this->getRootDir();
    }
    public function getRootDir(){
        return $this->rootDir;
    }
}


// from the controller class 
use App\Utils\Foo;
...
class FaaController extends AbstractController
{
    /**
     * @Route("/scenario", name="scenario")
     */
    public function new(Foo $foo)
    {
        dump($foo->myfoomethod()); //show the dir path !
        return $this->render('faa/index.html.twig');
    }
}
spacecodeur
  • 2,206
  • 7
  • 35
  • 71
-2

The easiest way to do this without injecting useless dependencies, is to create an utility method like this (in my case it's in src\Utils\Utils.php):

namespace App\Utils;

public static function getRootDir(): string
{
    return __DIR__ . '/../..';
}

Then simply call it anywhere with

Utils::getRootDir()

It works on both Windows and Linux, but if you prefer you can use the DIRECTORY_SEPARATOR constant instead of '/'


Also, it's much more efficient than the Kernel::getProjectDir() method, which instantiates a ReflectionObject and then does a recursion until it finds the folder with composer.json, with multiple checks that involves I/O such as is_file(). Using the __DIR__ costant is much better since its value is already available in memory, without the need to access the physical disk.

Sofia Grillo
  • 405
  • 2
  • 13