I'm using Laravel as framework to build an application framework which can be used to build their own application by fellow developers. Now I'm running into a little PSR-4 namespacing problem in the composer packages I'm developing.
BaseMapper.php
(namespace: App\MyApplicationName\Mapper
)BaseController.php
BaseMapper.php
(namespace: MyVendorName\MyApplicationName\Controller
)This works correctly. I can make the BaseMapper.php
in the app
folder extend the BaseMapper.php
in vendor
and in that way I can add extra methods and properties to the BaseMapper.php
while keeping (or overwriting) those in the base mapper file.
Contents of BaseMapper.php
in app
:
namespace App\MyApplicationName\Mapper;
use MyVendorName\MyApplicationName\Mapper as FrameworkMapper;
class BaseMapper extends FrameworkMapper;
{
public function executeFunction(): string
{
return "bar";
}
}
Contents of BaseMapper.php
in vendor
:
namespace MyVendorName\MyApplicationName\Mapper;
class BaseMapper
{
public function executeFunction(): string
{
return "foo";
}
}
When I call the mapper from within the Laravel project like:
$baseMapper = new \App\MyApplicationName\BaseMapper();
$result = $baseMapper->executeFunction(); // bar
I get bar
as a result and I'm very happy.
But here's my problem:
In the BaseController.php
in vendor
I call a BaseMapper.php
's method like:
$mapper = new \MyVendorName\MyApplicationName\Mapper\BaseMapper();
$result = $mapper->executeFunction(); // foo
Now $result
is foo
while I expect to get bar
.
I can solve it by adding:
$mapper = new \MyVendorName\MyApplicationName\Mapper\BaseMapper();
if (class_exists(\App\MyApplicationName\Mapper\BaseMapper::class)) {
$mapper = new \App\MyApplicationName\Mapper\BaseMapper();
}
$result = $mapper->executeFunction();
But I think it's ugly to add checks like that and besides it's easily forgotten to add a check like that when you instantiate a new class.
So I want to add some custom autoloading logic (in my package) to be able to search for the App
namespace first and to use the MyVendorName\MyApplicationName
namespace as a fallback.
I've already tried to do this in composer.json
:
"autoload": {
"psr-4": {
"MyVendorName\MyApplicationName": ["../../app/MyApplicationName/","src/"]
}
}
But this make it impossible to overwrite the class from within the app
folder as I'm using the same namespace.
So my only idea to achieve this is when there's a way to hook in on the autoloader of composer to add logic like this:
if (str_contains($className, "\\MyVendorName\\MyApplicationName\\")) {
$appClassName = str_replace("\\MyVendorName\\MyApplicationName\\", "\\App", $className);
if (class_exists($appClassName)) {
include_once $appClassName;
} else {
include_once $className;
}
}
Could you please help me out how to achieve this giving me another (better) solution for my problem?
This should not be addressed with a custom autoloader or swapping out classes at runtime. Ideally, vendor/MyVendorName/MyApplicationName/Controller/BaseController
would be written such that it could have the BaseMapper
classed injected at instantiation, so that you could very easily change its behavior simply by passing it the class it needs.
namespace MyVendorName\MyApplicationName\Controller;
class BaseController
{
protected $mapper;
public function __construct($mapper)
{
$this->mapper = $mapper;
}
public function whateverThisMethodIsNamed()
{
$result = $this->mapper->executeFunction();
}
}
Then when you create the controller class, you'd pass it the mapper you wanted it to use:
$mapper = new \MyApplicationName\Mapper();
$controller = new \MyVendorName\MyApplicationName\BaseController($mapper);
However, if you don't have the ability or the desire to change that vendor repository separately, then the simplest solution would probably be to create an extending BaseController
in your app namespace so that it extends the vendor BaseController
. Then provide an overriding method that has the updated logic:
Create app/MyApplicationName/Controller/BaseController.php
:
namespace MyApplicationName\Controller;
class BaseController extends \MyVendorName\MyApplicationName\Controller\BaseController
{
public function whateverThisMethodIsNamed()
{
$Mapper = new \MyApplicationName\Mapper\BaseMapper();
$result = $Mapper->executeFunction();
}
}
And then in your app, use the new controller:
//$controller = new \MyVendorName\MyApplicationName\Controller\BaseController();
$controller = new \MyApplicationName\Controller\BaseController();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With