How to override the resourcePath() function defined in Illuminate/Foundation/Application.php - laravel

I am modularizing laravel. I have decided to move all the default routes, controllers, resources, etc.. to /app/Modules/Pub. For the most part this has worked well. However I would like to change the default resources path of the application. Unfortunately this doesn't seem to be (easily) configurable.
So... using grep I was able to track down the resource_path() function to /var/www/sigma/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
I think it's possible to override this function somewhere but this seems like a subpar hack as this function consists simply of:
app()->resourcePath($path)
Again using grep I found out that this function is to be found in /var/www/sigma/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
This seems to be the thing to change since it does not reference any configuration value, rather the value is hard coded:
return $this->basePath.DIRECTORY_SEPARATOR.'resources'.($path ? DIRECTORY_SEPARATOR.$path : $path);
But I think it's safe to assume it's pretty foolish to change anything under the vendor folder manually. Obviously I need to override this function somewhere. I am unclear where and how to do this

Create a new Application class which extends the \Illuminate\Foundation\Application:
<?php
namespace <YOUR NAMESPACE HERE>;
class ApplicationCustom extends \Illuminate\Foundation\Application
{
public function __construct()
{
parent::__construct();
}
/**
* Get the path to the resources directory.
*
* #param string $path
* #return string
*/
public function resourcePath($path = '')
{
// Implement the custom method
}
}
Now, just change your bootstrap/app.php file to use the custom class:
$app = new YOUR_NAMESPACE\ApplicationCustom(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
Hope it helps.

You could create a class somewhere in your project and extend the default \Illuminate\Foundation\Application class. Then override the methods you need and switch the class instantiated in bootstrap/app.php with your custom one.

Related

How can I add extra view component class paths in Laravel?

The problem
The default namespace for view components is App\View\Components with the folder being app/View/Components. I am setting up a DDD file structure and wish to do two things:
Move "shared" view components to a namespace and folder of App\ViewComponents and src/app/ViewComponents respectively
Have view components specific to individual "apps" with their own namespace and folder of App\MyApplication\ViewComponents and /src/app/MyApplication/ViewComponets respectively.
The new App namespace/folder setup is done via composer psr-4 autoload keys and works fine. But Laravel always used the App\View\Components namespace when trying to load components.
My attempt
I have solved the first part of my problem, but I am hoping that there is a better way. For instance when I want to move views, I can just set the view.paths config directive in my AppServiceProvider but I don't see a similar way of, essentially, adding namespaces to where Laravel looks for view components. So what I ended up doing was:
Create a ViewServiceProvider class, extending Illuminate\View\ViewServiceProvider::class and point to it in bootstrap/app.php instead
In there, override the registerBladeEngine method, in there pointing towards my own BladeCompiler class instead of the built-in one
public function registerBladeEngine($resolver)
{
// The Compiler engine requires an instance of the CompilerInterface, which in
// this case will be the Blade compiler, so we'll first create the compiler
// instance to pass into the engine so it can compile the views properly.
$this->app->singleton('blade.compiler', function () {
return new BladeCompiler(
$this->app['files'],
$this->app['config']['view.compiled'],
);
});
$resolver->register('blade', function () {
return new CompilerEngine(
$this->app['blade.compiler']
);
});
}
In my own BladeCompiler class, which extends Illuminate\View\Compilers\BladeCompiler, override the component() and compileComponentTags() methods - basically anywhere that referenced View\\Components - with pretty much a carbon copy but instead using ViewComponents and also made sure that where they return a Illuminate\View\Compilers\ComponentTagCompiler I instead referenced my own ComponentTagCompiler
In my own TagCompiler I override the guessClassName() method, again with essentially a carbon copy, just renaming View\\Components to ViewComponents
As you can see, that's quite a lot of work just to change the path. And I also want to add another path. Multiple "apps" run under the same Laravel codebase, so for instance we might have App\Website\, App\Admin and App\Blog and, depending on which app is currently running, load a different namespace for the running app, i.e. the blog would be App\Blog\ViewComponents pointing to src/app/Blog/ViewComponents.
Is there a way to achieve this without as much overriding as above? If not, can you suggest a way to achieve the second part of the requirement?
Note: I haven't ruled out using sub folders and continuing with everything under the main App\View\Components namespace just yet - I don't want to fight Laravel more than I have to and am willing to concede if there's no better way, but if I can achieve the folder structure I want it would feel a lot tidier.
Update; got a working implementation by using a configuration and php 8 annotations
Follow the steps below to make it possible to add more lookup folders for the blade view components feature, based on your question and details you have provided. It would have helped to have posted that code you already had. But I have added a possible solution to get it to work, using Annotations and using a config with a namespace/path map.
Depending on how you switch between one application and the other, from which the details are not provided in your question, you have to modify the way the configuration is retreived in the MyComponentTagCompiler class.
Blade compiler
In order to change the ComponentTagCompiler we need to change the BladeCompiler class:
namespace App;
class YourBladeCompiler extends \Illuminate\View\Compilers\BladeCompiler
{
protected function compileComponentTags($value)
{
if (! $this->compilesComponentTags) {
return $value;
}
return (new \App\MyComponentTagCompiler( //it is about this line
$this->classComponentAliases, $this->classComponentNamespaces, $this
))->compile($value);
}
}
Service provider
Now register the YourBladeCompiler in YourViewServiceProvider :
class YourViewServiceProvider extends \Illuminate\View\ViewServiceProvider
{
public function registerBladeEngine($resolver)
{
$this->app->singleton('blade.compiler', function () {
return new \App\YourBladeCompiler( //it is about this line
$this->app['files'],
$this->app['config']['view.compiled'],
);
});
$resolver->register('blade', function () {
return new CompilerEngine(
$this->app['blade.compiler']
);
});
}
}
MyComponentTagCompiler
This is an implemention I created that works with PHP 8 Attributes, given below:
namespace App;
#[\Attribute]
class ViewComponentName
{
public string $name;
public string $package;
public function __construct(string $name, string $package)
{
$this->name = $name;
$this->package = $package;
}
}
With this attribute, you can declare the package name and component name on the view component class (see example at the bottom). So during lookup the component can be matched on these parameters.
But you can change it to your own requirements if needed.
What it does:
It first let's Laravel lookup the View Component through it's own mechanisms, in the parent::componentClass method.
If no component is found and an exception (InvalidArgumentException) is thrown, after which my implementation will walk through the given paths and namespaces (from the getLookupPaths method) and see if an attribute matches the component name and package name. If so it returns this class and the view component is loaded accordingly.
namespace App;
use App\View\ViewComponentName;
use Illuminate\View\Compilers\ComponentTagCompiler;
class MyComponentTagCompiler extends ComponentTagCompiler
{
protected function getLookupPaths() : array
{
/*
* add some logic here to get an application specific configuration
* since you have multiple application in one, I cannot know it works in your
* application, since the details are not provided in the question
*/
return config('view_component_paths');
}
private function getFiles(string $dir) : array
{
return scandir($dir);
}
private function isPhpFile(string $file) : bool
{
return strpos($file, ".php");
}
private function getClassNamespace(string $file, string $folderNamespace) : string
{
$class = str_replace(".php", "", $file);
$classNamespace = $folderNamespace . "\\" . $class;
return $classNamespace;
}
private function getComponentName(string $file, string $namespace) : ?ViewComponentName
{
$classNamespace = $this->getClassNamespace($file, $namespace);
$reflection = new \ReflectionClass($classNamespace);
if(method_exists($reflection, 'getAttributes')) {
$attribute = $reflection->getAttributes()[0];
if ($attribute->getName() == ViewComponentName::class) {
return $attribute->newInstance();
}
}
return null;
}
public function componentClass(string $component)
{
try {
parent::componentClass($component);
} catch(\InvalidArgumentException $e) {
list($lookupComponentPackage, $lookupComponentName) = explode("-", $component);
foreach($this->getLookupPaths() as $namespace=>$dir) {
foreach ($this->getFiles($dir) as $file) {
if ($this->isPhpFile($file)) {
if($componentName = $this->getComponentName($file, $namespace)) {
if($componentName->name == $lookupComponentName && $componentName->package == $lookupComponentPackage) {
return $this->getClassNamespace($file, $namespace);
}
}
}
}
}
throw $e;
}
}
}
Where the config contains (config/view_component_paths.php):
return [
"App\\Test"=>__DIR__ . "/Test/"
];
If you wish to replace the default laravel behavior completely or do not like my implementation based on annotations, consider implementing your own version of the method:
public function componentClass(string $component)
{
//return the class name here based the component name
//without calling parent
dd($component);
}
Example view component
namespace App\Test;
use App\View\ViewComponentName;
use Illuminate\View\Component;
#[ViewComponentName('test', 'namespace')]
class MyViewComponent extends Component
{
public function render()
{
return view('components.test');
}
}
In blade:
<x-namespace-test />
It should now be working. I think this is enough information to give you an idea of how to implement this in your own application. There seems to be no other way than to extend some base classes. But looking at this answer, it is possible to create a high level implementation based on a global lookup configuration and php annotations (or some other mechanism you wish, for example converting the class name with namespace to a view component name).
Old answer
Problem 2 as defined in your question
Have view components specific to individual "apps" with their own namespace and folder of App\MyApplication\ViewComponents and /src/app/MyApplication/ViewComponets respectively.
Sadly there seems to be no way of defining multiple class paths for view components in Laravel. But you can however change the application path and namespace prefix. As far as I found out you only have to overwrite the following properties in the Application class.
bootstrap/app.php
Replace the following lines:
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
With:
class YourApplication extends \Illuminate\Foundation\Application
{
protected $namespace = "App\\MyApplication";
protected $appPath = __DIR__ . "/../app/MyApplication";
}
$app = new YourApplication(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
This is enough to change the app folder to another and gives you an idea of how to change it dynamically for having multiple apps in different namespaces. If you now run laravel commands like php artisan make:component Test1234 it is created in your new app folder: app/MyApplication/View/Components/Test1234.php.
Hardcoded paths
Some paths like View/Components are hardcoded in Laravel, and therefor not that easy to change. If you change as defined above, in this case the view components namespace becomes: App\MyApplication\View\Components and the path: app/MyApplication/View/Components.
Problem 1 as defined in your question
Move "shared" view components to a namespace and folder of App\ViewComponents and src/app/ViewComponents respectively
When you change application paths as explained above it is not possible to have a "shared" View Component folder. Laravel, as it seems, has only one default View Components path, which is based on hard coded paths and a dynamic namespace prefix as explained above. But you can of course, create a shared namespace and register the view components manually:
View component (app/ViewComponents/ folder)
namespace App\ViewComponents;
use Illuminate\View\Component;
class Test extends Component
{
public function render()
{
return view('components.test');
}
}
Don't forget the components.test blade view.
ServiceProvider
\Blade::component("shared-test",\App\ViewComponents\Test::class);
Blade
<x-shared-test />

Laravel installation in sub-folder and horizon not working

I have installed the Laravel in sub-folder and is trying to install the horizon. After routing to "test.com/sub-folder/horizon", all the design in broken and also the internal links are pointing to main domain instead of main-domain-without-subfolder.
After the search, it seems to be the known issue which is already reported in github issue
Has there is any work around to make horizon work when Laravel is installed in sub-folder?
I have a solution that only involves PHP.
The issue, as pointed out by #Isaiahiroko, is the basePath defined for Horizon's interface. That code is in Laravel\Horizon\Http\Controllers\HomeController::index(). The idea is this: we are going to pass to Laravel's service container our own implementation of that controller that will override the basePath definition passed to Horizon's interface.
Create a new controller with code like this:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Laravel\Horizon\Horizon;
use Laravel\Horizon\Http\Controllers\HomeController;
class HorizonHomeController extends HomeController
{
/**
* Overrides default horizon route to support subdirectory hosting.
*/
public function index ()
{
// We use a plain request to check for the base url.
$request = request();
// Set up our base path.
$base_path = Str::substr($request->getBasePath(), 1);
if (!empty($base_path)) {
$base_path .= '/';
}
// Patch default horizon variables with our own base path.
$variables = Horizon::scriptVariables();
$variables['path'] = $base_path . config('horizon.path');
// Render horizon's home view.
return view('horizon::layout', [
'assetsAreCurrent' => Horizon::assetsAreCurrent(),
'horizonScriptVariables' => $variables,
'cssFile' => Horizon::$useDarkTheme ? 'app-dark.css' : 'app.css',
'isDownForMaintenance' => App::isDownForMaintenance(),
]);
}
}
What's left is telling Laravel's service container that when Horizon's HomeController is requested, it should provide our HorizonHomeController class. In your AppServiceProvider, at the end of the register() method, set this up:
// [...]
class AppServiceProvider extends ServiceProvider
{
// [...]
/**
* Register any application services.
*
* #return void
* #throws InvalidConfiguration
*/
public function register()
{
// [...]
// Horizon's subdirectory hack
$this->app->bind(
Laravel\Horizon\Http\Controllers\HomeController::class,
App\Http\Controllers\HorizonHomeController::class
);
}
// [...]
}
After that, you should be able to browse to http(s)://<your-host>/<your-sub-dir>/horizon normally.
Considerations:
To me this feels cleaner that patching a compiled js, which also has the downside that needs to be re-applied every time Horizon is updated (this can be mitigated with a post-update script in composer, tho). Also, for additional points, this solution is only overriding the method that renders the view, but not the route, which means all of Horizon's authentication mechanisms (middlewares and gates) are working exactly as described in the documentation.
If you desperately need to do this, here is a hack:
In public\vendor\horizon\app.js, search for window.Horizon.basePath
replace window.Horizon.basePath="/"+window.Horizon.path; with window.Horizon.basePath="/[you sub-directoy]/"+window.Horizon.path;
It should work...until you run update one day and it mysteriously stop working.

Pass variable in every view file - laravel

I want to send some variable in every views which contains data from database. I have written the following code in base controller because it is extended by all of the controller:
public function __construct()
{
$opening_hours = OpeningHours::first();
$social_media = SocialMedia::first();
$website = Website::first();
view()->share('opening_hours', $opening_hours)
->share('social_media', $social_media)
->share('website', $website);
}
Also I have also called parent::__construct(); in all of my controllers. But, I am still getting undefined variable $opening_hours in view file when I try to debug it. How can I send website data (website logo, contact, email) that has to be included in every views file?
Laravel provides us some features like this. You can try View Composers. These are very useful if we want some data on every screen. But we want to place this on separate place instead of writing code in every controller.
https://laravel.com/docs/master/views#view-composers
That will help us.
You can try this way
Create a one middleware and add this code into middleware and use middle where you want this data and data will be available on that view.
$opening_hours = OpeningHours::first();
$social_media = SocialMedia::first();
$website = Website::first();
view()->share('opening_hours', $opening_hours)
->share('social_media', $social_media)
->share('website', $website);
You are a file called AppServiceProvider.php inside of app/Providers folder, In there you can do the following:
<?php
namespace App\Providers;
use View;
use App\OpeningHours;
use App\SocialMedia;
use App\Website;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
public function boot()
{
$contact_details = [
'opening_hours' => OpeningHours::first(),
'social_media' = SocialMedia::first(),
'website' => Website::first(),
];
View::share('contact_details', $contact_details);
}
}
Updated and added a guess to the namespace of the models being used.

Why won't this Laravel 5.4 service provider register?

I am trying to do a hello world service provider with the new Laravel 5.4.
I have created the following service provider file:
//File: app/TestProvider/TestServiceProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind('Test', function ($app) {
return new Test();
});
}
}
I have created a simple class under the same namespace:
//File: app/TestProvider/Test.php
namespace App\TestProvider;
class Test
{
/**
* Register bindings in the container.
*
* #return void
*/
public function helloWorld()
{
echo "hello world";
}
}
The problem is, this is not registering. The register method is executing as when I put a breaker before the 'bind' method, it executes:
public function register()
{
dd("BREAKER");
$this->app->bind('Test', function ($app) {
return new Test();
});
}
So this outputs "BREAKER" as expected. However if I put the breaker in the closure, nothing happens which suggests for some reason, that 'bind' method isn't being executed??
Any ideas?
EDIT:
Just some further info: I know that the Test class is registered and in the correct namespace as I can do:
dd(new Test());
in the registration method, and it outputs the resource id as expected.
Explanation
The closure provided only runs when the binding is being resolved. That's why it's a closure, it can be saved in the service container and resolved at any time while the program runs.
Solution
To see the resolved binding, create a controller and resolve the class in that controller:
// File: app/Http/Controllers/TestController.php
namespace App\Http\Controllers;
// This isn't the best way, but it works. See the best way below
class TestController extends Controller {
public function index()
{
return \App::make('Test')->helloWorld();
}
}
Of course, don't forget to register the route:
// File: routes/web.php
Route::get('/', 'TestController#index');
The binding will resolve when you hit the homepage.
However, as I said, it's not the best way, so here I prepared a better way. Change the way you register the binding:
// File: app/Providers/TestProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
use App\TestProvider\Test;
// Better way
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
// Note: we bind the exact complete class name!
$this->app->bind(Test::class, function ($app) {
return new Test();
});
}
}
After this change the controller so that it looks like this:
namespace App\Http\Controllers;
use App\TestProvider\Test;
class TestController extends Controller {
/**
* #var Test $test
*/
private $test;
// Let Laravel resolve the dependency on constructing the class
public function __construct(Test $test)
{
$this->test = $test;
}
public function index()
{
return $this->test->helloWorld();
}
}
You will see that the exact same thing happens, but it looks more elegant and avoids conflicts.
Details
Laravel gives only a high level overview of the service container, which doesn't help to learn how it works on the inside. The best way to see that is to go down the call stack.
When you do that, you find that Laravel registers every class in the project in the service container. That means that whether you create a service provider or not, the class will be in the container. How exactly?
When you run php artisan optimize, Laravel creates files that have array with all the classes of the project. When you run the app, after registering everything from the service providers, Laravel registers the rest of the classes from that file.
That means that in your case, if you don't specifically register the Test class, it will still be resolvable. Basically, you only need to register classes that need some specific instructions to be resolved.
So how does Laravel resolve the dependencies?
When you run \App::make(Test::class) or inject dependency via type hinting in the constructor (the "better way" from my solution), Laravel looks for that dependency among the bindings.
When it finds the dependency, it resolves either the closure associated to it or the constructor of the class directly.
When it resolves the constructor directly, it looks for type hints among the constructor parameters and recursively resolves all of them until there's nothing else to resolve.
After that it returns the resolved class.
Of course, bear in mind that for Laravel to analyze the contructor of a class, it needs to be resolved via the service container in the first place. You can't just call $test = new Test(); and expect Laravel to do all the magic :)
Conclusion
This is a rather quick overview of Laravel's service container. The best way for you to learn it is, of course, studying the sources for yourself. It's truly elegant and it uses PHP's functionality to the fullest.
I really hope this shed some light on the service container for you and can help you in the future :)
The closure passed to the bind() method is not executed until you actually attempt to resolve the alias you are binding.
So, if you dd('breaker') inside the closure, this won't actually get executed until Test is resolved (whatever your preferred resolution method is):
Service provider:
// bind the closure to the 'Test' alias
public function register()
{
$this->app->bind('Test', function ($app) {
dd("BREAKER");
return new Test();
});
}
Code that resolve Test alias:
// different ways of resolving the alias out of the container.
// any of these will execute the bound closure.
$test = resolve('Test');
$test = app('Test');
$test = app()->make('Test');
$test = \App::make('Test');
try:
$this->app->bind(Test::class, function ($app) {
return new Test();
});

How to override the native zf2 view helpers with a custom helper

I wanted to create a custom basepath helper to replace the original zf2 basepath view helper.
So if i call $this->basepath, it will use my custom basepath instead of the original one. I am not sure if this is can be done. I want my custom basepath extends the original basepath class too.
I have found some answers on how to create custom helpers and how to register them in module.php or module.config.php
But i can't find any similar questions on how to override the original helpers!
Factory definition of the basepath view helper is declared as a hardcoded invokable in HelperPluginManager (on line 45) however this definition also overridden in ViewHelperManagerFactory (line 80 to 93) because BasePath view helper requires the Request instance from ServiceLocator:
$plugins->setFactory('basepath', function () use ($serviceLocator) {
// ...
})
I strongly recommend extending the built-in basepath helper with a different name (MyBasePath for example) instead of trying to override the existing one. Overriding that native helper may produce some unexpected headaches later (think about 3rd party modules which uses that helper to work).
For your question; yes, it is possible.
Create the Application\View\Helper\BasePath.php helper class like below:
namespace Application\View\Helper;
use Zend\View\Helper\BasePath as BaseBasePath; // This is not a typo
/**
* Custom basepath helper
*/
class BasePath extends BaseBasePath
{
/**
* Returns site's base path, or file with base path prepended.
*/
public function __invoke($file = null)
{
var_dump('This is custom helper');
}
}
And override the factory in the onBootstrap() method of the Module.php file like below:
namespace Application;
use Zend\Mvc\MvcEvent;
use Application\View\Helper\BasePath; // Your basepath helper.
use Zend\View\HelperPluginManager;
class Module
{
/**
* On bootstrap for application module.
*
* #param MvcEvent $event
* #return void
*/
public function onBootstrap(MvcEvent $event)
{
$services = $event->getApplication()->getServiceManager();
// The magic happens here
$services->get('ViewHelperManager')->setFactory('basepath', function (HelperPluginManager $manager) {
$helper = new BasePath();
// Here you can do whatever you want with the instance before returning
return $helper;
});
}
}
Now you can try in any view like this:
echo $this->basePath('Bar');
This is not a perfect solution but it should work.

Resources