Laravel: Overriding a Bundle's Service Providers - php

I have a project that uses the Sentinel bundle for Laravel.
A while ago, I asked a question about extending a model provided in the bundle. The answer I accepted worked, but it required editing the bundle's code in the vendor folder.
Since then, I've run a composer update command and the change I applied was overwritten (no surprise there).
I know a bit more about how laravel works now so I was able to trace back the end point where the bundle's services are referenced by my app. In my config/app.php file I have the service provider reference:
'Sentinel\SentinelServiceProvider',
The SentinelServiceProvider service has a register method that registers the RepoServiceProvider which is the class I need to override.
My plan is to create two new files: ExtendedSentinelServiceProvider.php and ExtendedRepoServiceProvider.php.
In the ExtendedRepoServiceProvider.php file, I could replace the class being used for the User Repository binding with my custom User class:
// Bind the User Repository
$app->bind('Sentinel\Repo\User\UserInterface', function($app)
{
return new ExtendedSentryUser( // This is my custom model that I want the ExtendedRepoServiceProvider to use
$app['sentry']
);
});
In the ExtendedSentinelServiceProvider.php file, I would replace the existing RepoServiceProvider class reference with my newly modified ExtendedRepoServiceProvider class:
public function register()
{
$loader = \Illuminate\Foundation\AliasLoader::getInstance();
$loader->alias('Sentry', 'Cartalyst\Sentry\Facades\Laravel\Sentry');
$repoProvider = new ExtendedRepoServiceProvider($this->app); // This would be my new ExtendedRepoServiceProvider class
$repoProvider->register();
$formServiceProvider = new FormServiceProvider($this->app);
$formServiceProvider->register();
}
And then I would replace the service provider reference in my config/app.php file with the new ExtendedSentinelServiceProvider class:
'providers' => array(
...
'Sentinel\ExtendedSentinelServiceProvider',
...
),
My question is, would this make sense as a way of being able to store my modifications in my app directory rather than modifying the vendor code? Is this going to be a problem when it comes to name spacing? Is there a better way of doing this??
Any help would be greatly appreciated.

You shouldn't run into name spacing issues. However another option - especially if you're adding a feature that might be generally useful for others - would be to fork the project on github and then include a reference to your git project in the "repositories" attribute of your composer.json. For example you could add:
"repositories": [
{
"type": "git",
"url": "git#github.com:{your-user}/Sentinel.git"
}
],
And then composer would look to your fork when ever it does an install/update.

Related

Laravel Service Provider Still Registering In If Statement

I'm having a few issues with registering a Laravel service provider. In the sense that the service provider is always gets registered, even when it shouldn't.
I'm using composer require laravel/telescope --dev and looking at their docs. You can do an if statement within AppServiceProvider.php to allow this to register on occasion, in this sense I want only to register if the app has debug enabled.
I created the method.
protected function hasDebug()
{
return config('app.debug');
}
This returned whether or not debug is set within the env. I then have the following if statement within my register method.
public function register()
{
if ($this->hasDebug()) {
$this->app->register(TelescopeServiceProvider::class);
}
}
However, even when I have not enabled debugging (I can see this by doing dd($this-hasDebug()) it will return false. Everything to do with Telescope is being provided. I can still access routes, whereas realistically I do not want to be able to access anything to do with Telescope.
Can someone point out where I'm going wrong?
For anyone else who is having this issue, this is how to correct it.
The issue was to do with auto discovery within composer. It was always picking up Telescope therefore always registering it.
Although all of the logic was correct within my AppServiceProvider because it was registering it was basically ignored.
Inside of composer.json, I added this...
"extra": {
"laravel": {
"dont-discover": [
"laravel/telescope"
]
}
},
Change "app.debug" to APP_DEBUG. The config helper pulls the mappings directly from your .env file.

No hint path defined for [xxx]

I'm trying to link in my package to a view also in the same package.
This is the file structure:
/report/src
/report/src/ReportServiceProvider.php
/report/src/views/test.blade.php
/report/src/SomeClass.php
In my ReportServiceProvider.php I specify the directory where the views should be loading from (like specified here):
public function boot()
{
$this->loadViewsFrom(__DIR__.'/views', 'reports');
}
With the 'hint' reports, so I should be able to access them with view('reports::test')
Off course I add my ServiceProvider to /config/app.php's providers array like so:
....
Vendor\Report\ReportServiceProvider::class,
....
I load my package in composer as follows:
"autoload": {
....
"psr-4": {
"App\\": "app/",
"Vendor\\Report\\": "packages/vendor/report/src"
}
...
}
But when I use the view('reports::test') in SomeClass.php i get the following error:
No hint path defined for [reports]
So somehow it cannot find the reports hint....
What am I missing here?
I had a similar issue for Laravel's default forgot-password functionality. Error No hint path defined for [emails]
Running php artisan optimize:clear fixed it for me.
I think report is your package name,
Step 1: You must specify the package name inside the service provider
$this->loadViewsFrom(__DIR__.'/views', 'report');
Step 2: If you want to load the view
return view('packageName::Email.testmail'); //packageName is report, the actual path to my view is package/report/src/views/Email/testmail.blade.php
You need to add the package' service provider in cofing/app.php
I solved the error No hint path defined for [view] by putting the following code snippet on my service provider boot method of my package:
$this->loadViewsFrom(__DIR__.'/views', 'home');
Where home is my view file home.blade.php. As I am Laravel beginner, maybe in package builded type of coding in need to give the path of view files inside service provider.
if you have your blades views in ...views/xxx, that is how you can specify it:
app('xxx')->addNamespace('mail', resource_path('views') . '/xxx');
If you're using infyomlabs/adminlte-templates package just add service provider in config/app.php as shown below:
'providers' => [
// ...
InfyOm\AdminLTETemplates\AdminLTETemplatesServiceProvider::class,
// ...
],
#include('cookie-consent::index') this should be used the blade file
either in the body tag or footer tag
<body>
#include('cookie-consent::index')
</body>
<footer>
#include('cookie-consent::index')
</footer>
#include('cookieConsent::index') /**this might not work */

Adding a view to Slim Views Framework with composer

I am writing a PHP template system for Slim, I have it working fine, but it is necessary to install the view file Ets.php in the correct existing location:
vendor/slim/views/Slim/Views/Ets.php
Whilst I can do it manually of course this defeats the object of composer. I was wondering if I can do it with https://getcomposer.org/doc/articles/custom-installers.md but I am having trouble following the guide as it and others only really talk about installing outside of the vendor directory.
Why do you want the views to get installed in the same place?
Have a look into http://docs.slimframework.com/#Custom-Views
You just need to extend the Slim\View. An example taken from the docs
class CustomView extends \Slim\View
{
public function render($template)
{
return 'The final rendered template';
}
}
and integrate in to slim like
$app = new \Slim\Slim(array(
'view' => new CustomView()
));
NB : Don't forget to do the autoload the classes required.

Caching View Output in Laravel 4

I know that Blade already caches the compiled PHP for all blade views, but I would like to take this a step further. A website that I'm working on is modularized into component views and then pieced together in the default controller. Each of the "widgets" has its own view, which rarely changes content (with the exception of a few frequently updating ones). So, I'd like to cache the HTML output of these rarely-changing views to prevent them from being evaluated on every page load.
In Laravel 3 we could do something like so (credit Laravel forums):
Event::listen(View::loader, function($bundle, $view)
{
return Cache::get($bundle.'::'.$view, View::file($bundle, $view,
Bundle::path($bundle).'view'));
});
Unfortunately, View::loader has entirely disappeared in Laravel 4. When digging through \Illuminate\View\View and \Illuminate\View\Environment, I discovered that each view dispatches an event named "composing: {view_name}". Listening for this event provides the view name and data being passed to it on each view render, however returning from the callback does not have the same effect as it did in Laravel 3:
Event::listen('composing: *', function($view) {
if(!in_array($view->getName(), Config::get('view.alwaysFresh'))) {
// Hacky way of removing data that we didn't pass in
// that have nasty cyclic references (like __env, app, and errors)
$passedData = array_diff_key($view->getData(), $view->getEnvironment()
->getShared());
return Cache::forever($view->getName() . json_encode($passedData), function() {
return 'test view data -- this should appear in the browser';
});
}, 99);
The above does not circumvent the normal view including and rendering process.
So how can you circumvent normal view rendering and return cached content from this composing event? Is it possible currently in Laravel without some ugly hackery?
Quick and Dirty
Well, one option, as I'm sure you know, is to cache the items inside of controllers as the View is being rendered. I suspect you don't want to do that, as it's less maintainable in the long-run.
More maintainable(?) method
However, if the View loader/renderer doesn't fire an event where you want, you can create one. Because every package/library in Laravel 4 is set in the App container, you can actually replace the View library with your own.
The steps I would take is:
Create a library/package. The goal is to create a class which extends Laravel's view logic. After taking a look, you might want to extend this one - This is the View facade
If you extended the View facade with your own (aka if my assumption on the file in step 1 is correct), you'll then just need to replace the alias for View in app/config/app.php with your own.
Edit- I played with this a bit. Although I don't necessarily agree with caching a View result, vs caching sql queries or the "heavier lifts", here is how I'd go about doing this in Laravel 4:
The View rendering in Laravel 4 doesn't fire an event that let's us cache the result of a view. Here's how I've added in that functionality to cache a view's result.
You may want to consider the ramifications of caching a view's result. For instance, this doesn't get around doing the hard work of talking to a datbase to get the data needed for the view. In any case, this gives a good overview on extending or replacing core items.
First, create a package and set up its autoloading. I'll use the namespace Fideloper\View. It's autoloading in composer.json will looks like this:
"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/database/migrations",
"app/database/seeds",
"app/tests/TestCase.php"
],
"psr-0": {
"Fideloper": "app/"
}
},
Next, create a class to replace the View facade. In our case, that means we'll be extending Illuminate\View\Environment.
In this class, we'll take the result of the View being rendered and add some logic to cache (or not cache) it. Here is Fideloper/View/Environment.php:
<?php namespace Fideloper\View;
use Illuminate\View\Environment as BaseEnvironment;
use Illuminate\View\View;
class Environment extends BaseEnvironment {
/**
* Get a evaluated view contents for the given view.
*
* #param string $view
* #param array $data
* #param array $mergeData
* #return \Illuminate\View\View
*/
public function make($view, $data = array(), $mergeData = array())
{
$path = $this->finder->find($view);
$data = array_merge($mergeData, $this->parseData($data));
$newView = new View($this, $this->getEngineFromPath($path), $view, $path, $data);
// Cache Logic Here
return $newView;
}
}
So, that's where the bulk of your work will be - filling out that // Cache Logic Here. However, we have some plumbing left to do.
Next, we need to set up our new Environment class to work as a Facade. I have a blog post about creating Laravel facades. Here's how to accomplish that in this case:
Create the facade for our new Environment. We'll name it fideloper.view in code.
<?php namespace Fideloper\View;
use Illuminate\Support\Facades\Facade;
class ViewFacade extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'fideloper.view'; }
}
Then, create the Service Provider which will tell Laravel what to create when fideloper.view is called. Note that this needs to mimic functionality of the Illuminate\View\ViewServiceProvider for creating the extended Environment class.
<?php namespace Fideloper\View;
use Illuminate\Support\ServiceProvider;
class ViewServiceProvider extends ServiceProvider {
public function register()
{
$this->app['fideloper.view'] = $this->app->share(function($app)
{
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
$env = new Environment($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
}
}
Lastly, we need to hook this all together and tell Laravel to load our Service Provider and replace Illuminate's View facade with our own. Edit app/config/app.php:
Add the Service Provider:
'providers' => array(
// Other providers
'Fideloper\View\ViewServiceProvider',
),
Replace the View facade with our own:
'aliases' => array(
// Other Aliases
//'View' => 'Illuminate\Support\Facades\View',
'View' => 'Fideloper\View\ViewFacade',
),
You'll then be able to use whatever logic you wish in the View::make() method!
Finally
It's worth noting that there are some patterns to load in multiple "requests" per web request. Symfony, for instance, let's you define controllers as servers. Zend has (had?) a concept of Action Stacks, which let you
... effectively help you create a queue of [controller] actions to execute during the request.
Perhaps you'd like to explore that possibility within Laravel, and cache the results of those "actions" (vs caching a view directly).
Just a thought, not a recommendation.
There is a library for caching views/parts in Laravel(and not only) - Flatten.
It is a powerful cache system for caching pages at runtime. What it does is quite simple : you tell him which page are to be cached, when the cache is to be flushed, and from there Flatten handles it all. It will quietly flatten your pages to plain HTML and store them. That whay if an user visit a page that has already been flattened, all the PHP is highjacked to instead display a simple HTML page. This will provide an essential boost to your application's speed, as your page's cache only gets refreshed when a change is made to the data it displays.
To cache all authorized pages in your application via the artisan flatten:build command. It will crawl your application and go from page to page, caching all the pages you allowed him to.
Flushing
Sometimes you may want to flush a specific page or pattern. If per example you cache your users's profiles, you may want to flush those when the user edit its informations. You can do so via the following methods :
// Manual flushing
Flatten::flushAll();
Flatten::flushPattern('users/.+');
Flatten::flushUrl('http://localhost/users/taylorotwell');
// Flushing via an UrlGenerator
Flatten::flushRoute('user', 'taylorotwell');
Flatten::flushAction('UsersController#user', 'taylorotwell');
// Flushing template sections (see below)
Flatten::flushSection('articles');
link to - https://github.com/Anahkiasen/flatten

Where do I put event listeners and handlers?

I am wondering where to put the Laravel Event Listeners and Handlers. Somebody told me that I can put them anywhere. This is what I have tried so far.
# listeners/log.php
<?php
Event::listen('log.create', 'LogHandler#create');
# handlers/LogHandler.php
<?php
class LogHandler {
public function create(){
$character = new Character;
$character->name = "test";
$character->save();
}
}
# controllers/MainController.php
public function test(){
Event::fire('log.create');
return "fired";
}
# start/global.php
ClassLoader::addDirectories(array(
app_path().'/commands',
app_path().'/controllers',
app_path().'/models',
app_path().'/database/seeds',
app_path().'/libraries',
app_path().'/listeners',
app_path().'/handlers',
));
I'm going to assume that you're asking this because they're not working, rather than for confirmation of something that you've got working.
Whilst it is correct that you can put event listeners anywhere, you need to make sure they'll actually get included - Laravel doesn't search through your source code looking for them.
My favourite place to include such files is in start/global.php. If you look at the bottom of the file you can see where the filters are included, you can do the same to include your listeners. It would be cleanest to keep them all in one listeners file, like all of your routes are in one routes file...
# start/global.php
require app_path().'/filters.php';
My personal opinion is that it's bad practice in general to lump event listeners in a single place. Sure, today you only need 2 or 3, but scope can be added to any project at any time, possible adding a lot more.
Instead, I generally create a directory underneath the app directory (e.g. app/CompanyName) and put all of my application specific code in there. To tell Laravel how to find your files, you can then update your composer.json llike this:
"autoload": {
"classmap": [
// ...
],
"psr-4": {
"CompanyName\\" : "app/"
},
}
After that, be sure to run composer dump-autoload.
Now, you can create namespace directories inside of your custom application directory, like app/CompanyName/Events/, and be able to segregate out your event listeners into groups that make sense, and put them inside of a service provider, for example:
<?php namespace CompanyName/Events;
// File: app/CompanyName/Events/LogEventsProvider.php
use Illuminate\Support\ServiceProvider;
class LogEventsProvider extends ServiceProvider
{
public function register()
{
Event::listen('log.create', 'CompanyName/Events/LogEventsProvider#create');
}
public function create()
{
// ...
}
}
Now you can add this service provider to your app/config/app.php and be good to go, and have all of your related event listeners in a single file, and ALL of your event listeners in a single directory, yet separate so that if something goes wrong with one of them you don't have to search through ALL of them to find where the error is happening.
NB: I did not come up with this as a practice, but found it somewhere along the way. I however cannot remember where it was.

Categories