I would like to override the default View::make() method in Laravel, which can be used to return a view-response to the user.
(I think) I've already figured out that this method is stored inside Illuminate\View\Factory.php, and I've been reading about IoC container, while trying to make it work using some similar tutorials, but it just won't work.
I've already created a file App\Lib\MyProject\Extensions\View\Factory.php, containing the following code:
<?php namespace MyProject\Extensions\View;
use Illuminate\View\Factory as OldFactory;
class Factory extends OldFactory {
public static function make() {
// My own implementation which should override the default
}
}
where the MyProject folder is autoloaded with Composer. But I don't know how to use this 'modified' version of the Factory class whenever a static method of View (in particular View::make()) is being called. Some help would be great!
Thanks!
The call to View::make is, in Laravel speak, a call to the View facade. Facades provide global static access to a service in the service container. Step 1 is lookup the actual class View points to
#File: app/config/app.php
'aliases' => array(
'View' => 'Illuminate\Support\Facades\View',
)
This means View is aliased to the class Illuminate\Support\Facades\View. If you look at the source of Illuminate\Support\Facades\View
#File: vendor/laravel/framework/src/Illuminate/Support/Facades/View.php
class View extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'view'; }
}
You can see the facade service accessor is view. This means a call to
View::make...
is (more or less) equivalent to a call to
$app = app();
$app['view']->make(...
That is, the facade provides access to the view service in the service container. To swap out a different class, all you need to do is bind a different object in as the view service. Laravel provides an extend method for doing this.
App::extend('view', function(){
$app = 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 \MyProject\Extensions\View($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;
});
Notice this is more than just instantiating an object. We need to instantiate it the same way as the original view service was instantiated and bound (usually with either bind or bindShared). You can find that code here
#File: vendor\laravel\framework\src\Illuminate\View\ViewServiceProvider.php
public function registerFactory()
{
$this->app->bindShared('view', 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 Factory($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;
});
}
You can test if your binding worked with code like this
var_dump(get_class(app()['view']));
You should see your class name. Once your sure the binding has "taken", you're free to redefine whatever methods you want.
Related
I'm using Symfony DI, Http, Kernel, Routing in my project following Create your own PHP Framework (https://symfony.com/doc/current/create_framework/index.html). It's fine and ok with is_leap_year App demo purpose.
I'm trying to figure out how I can inject services and container, into a custom controller defined in routes using only Symfony components, not framework bundle.
container.php
// add demo service into the service container
$containerBuilder->register('demo.service', '\Demo\DemoService');
// add dependent service into the controller container
$containerBuilder->register('dependent.controller', '\Demo\DemoController')
->setArguments([new Reference('demo.service')]);
// fetch service from the service container
// Echoing Works fine! But no service in controller
//echo $containerBuilder->get('dependent.controller')->helloWorld();
App.php
// nok with dependency
$routes->add('hello', new Routing\Route('/hello', [
'_controller' => 'Demo\DemoController::helloWorld',
]));
// ok with no dependency
$routes->add('hello2', new Routing\Route('/hello2', [
'_controller' => 'Demo\DemoService::helloWorld',
]));
And DemoController.php
<?php declare(strict_types=1);
namespace Demo;
class DemoController
{
private $demo_service;
public function __construct(\Demo\DemoService $demoService)
{
$this->demo_service = $demoService;
}
public function helloWorld()
{
return $this->demo_service->helloWorld();
}
}
Returns
Fatal error: Uncaught ArgumentCountError: Too few arguments to function Demo\DemoController::__construct(), 0 passed
How can I get this working? Or, how can i inject container into the Controller Constructor ?
Example here https://github.com/Monnomcjo/symfony-simplex
You actually got pretty close so well done there. I think you mixed ContainerAwareInterface with the ContainerControllerResolver suggestion. Two different concepts really. Looks like you also tried making your own ControllerResolver class but you did not update container.php to use it. In any event, there is no need for a custom resolver at this point.
I also accidentally misled you with the suggestion that there was a service called 'container'. It is actually called 'service_container'.
# container.php
$containerBuilder->register('container_controller_resolver', HttpKernel\Controller\ContainerControllerResolver::class)
->setArguments([new Reference('service_container')]);
$containerBuilder->register('framework', Framework::class)
->setArguments([
new Reference('dispatcher'),
new Reference('container_controller_resolver'), // Changed
new Reference('request_stack'),
new Reference('argument_resolver'),
//new Reference('demo.service'), // No need for these
//new Reference('dependent.controller'),
])
;
Also, by convention, some of the framework services use id's like 'framework' or what not. But most of the time your application level stuff should just use the class name as the service id. In particular your need to use the controller class name to allow the ContainerControllerResolver to find it. It will also be useful when you venture into using the automatic wiring capabilities of the container.
// add demo service into the service container
$containerBuilder->register(\Demo\DemoService::class, \Demo\DemoService::class);
// add dependent service into the controller container
$containerBuilder->register(\Demo\DemoController::class,\Demo\DemoController::class)
->setArguments([new Reference(\Demo\DemoService::class)]);
And that should do it. Enjoy.
Service containers / providers are probably much simpler concepts than I imagine, but after several hours of reading I still don't get it, entirely.
I have created a simple DateFormat class within app/Library. After creating an alias for it inside \config\app.php I can use it right away in any controllers or blade templates.
<?php namespace App\Library;
class DateFormat {
public static function getDate($timestamp){
// processing the timestamp
}
}
Did I just create a Service Container? If yes, do I need to create a Service Provider as well? Where come bindings into the picture?
I would really appreciate some light on the subject.
Thanks
No. What you created is simply an alias to your class. Services Providers are a way of binding a specific class, and often are used in conjuction with a Facade.
An alias is simply a convenient way to use a class without having to import the entire namespaced class every time.
For example, if you have a class \My\Very\Long\Class\Adapter, you could alias this in config/app.php:
// config/app.php
<?php
'aliases' => [
// a bunch of aliases
'MyAdapter' => My\Very\Long\Class\Adapter::class,
]
And now you can just do:
<?php
new MyAdapter();
...
instead of:
<?php
use My\Very\Long\Class\Adapter;
...
new Adapter();
...
A Service Provider is often used when you want to resolve a dependency, most commonly through injection. This can be helpful when the class you want to resolve requires parameters to passed into the constructor or has a common setup every time. You can perform all that setup in the Provider.
Here's a scenario:
You have an API that you want to interact with. We'll call it SuperApi. The docs for SuperAPI say that to create an instance of the SuperApi class, you have to do something like:
<?php
// Some method (a controller or something)
public function index()
{
$superApi = new \SuperApi\Connector($key, $secret);
return $superApi->getCustomers();
}
Now, every time you want to create an instance of this, you'll have to do the same setup (or abstract it to some class, but the fact remains that you need to pass a $key and $secret to the constructor).
If you were to create an alias for this Connector class, maybe it would be:
// config/app.php
<?php
'aliases' => [
// a bunch of aliases
'SuperApi' => SuperApi\Connector::class,
]
So with that alias, you can now do this:
<?php
// Some method (a controller or something)
public function index()
{
$superApi = new SuperApi($key, $secret);
return $superApi->getCustomers();
}
But you see, that even with the alias, you still need to pass the $key and $secret.
This is where a Service Provider can help.
// app/Providers/SuperApiProvider.php
<?php
namespace App\Providers;
use SuperApi\Connector;
use Illuminate\Support\ServiceProvider;
class SuperApiProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind('superApiConnector', function ($app) {
return new ApiConnector($app['config']->get('super-api.key'), $app['config']->get('super-api.secret'));
});
}
}
// app/Providers/SuperApi.php (the Facade)
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Facade;
class SuperApi extends Facade
{
protected static function getFacadeAccessor()
{
return 'superApiConnector';
}
}
// config/super-api.config
<?php
return [
'key' => env('SUPER_API_KEY'),
'secret' => env('SUPER_API_SECRET'),
];
// config/app.php
<?php
'providers' => [
// a bunch of providers
App\Providers\SuperApiProvider::class,
]
See that the string you bind to in the provider ('superApiConnector') is the same as what you return from the facade and the class name of the facade is how you'll actually call the binded class, in this case SuperApi.
Now, when you want to user the SuperApi\Connector class, you can do this:
<?php
// Some method (a controller or something)
public function index()
{
return SuperApi::getCustomers();
}
And as I said above, where a provider really comes in handy is when you want to inject it and have Laravel's IoC Container automatically resolve the injected class:
<?php
// Some method (a controller or something)
public function index(SuperApi $api)
{
return $api->getCustomers();
}
To be clear, you do NOT need a Service Provider to take advantage of dependency injection. As long as the class can be resolved by the application it can be injected. That means whatever arguments the constructor of the class you're injecting takes need to also be auto-resolvable.
As for the title I've googled about two hours searching for a efficient answer and read repeatedly the official documentation, but without any step further, considering I'm relatively new to the framework. The doubt arise while searching for a correct way to share some code between controllers and i stumbled in service providers, so:
I've created say a MyCustomServiceProvider;
I've added it to the providers and aliases arrays within the app.php file;
finally I've created a custom helpers class and registered it like:
class MyCustomServiceProvider extends ServiceProvider
{
public function boot()
{
//
}
public function register()
{
$this->app->bind('App\Helpers\Commander', function(){
return new Commander();
});
}
}
So far, however, if I use that custom class within a controller I necessarily need to add the path to it through the use statement:
use App\Helpers\Commander;
otherwise I get a nice class not found exception and obviously my controller does not his job.
I suspect there's something which escapes to me on service providers! :-)
So far, however, if I use that custom class within a controller I
necessarily need to add the path to it through the use statement:
`use App\Helpers\Commander;`
otherwise I get a nice class not found
exception and obviously my controller does not his job.
Yes, that's how it works. If you don't want to use the full name, you can use a Facade instead.
Create the Facade class like this:
class Commander extends Facade
{
protected static function getFacadeAccessor() { return 'commander'; }
}
register the service:
$this->app->singleton('commander', function ($app) {
return new Commander();
});
add the alias to your config/app.php:
'aliases' => [
//...
'Commander' => Path\To\Facades\Commander::class,
//...
],
and use it like a Facade:
\Commander::doStuff();
On why your code still works, even when you remove the bind:
When you type-hint a parameter to a function, and Laravel does not know about the type you want (through binding), Laravel will do its best to create that class for you, if it is possible. So even though you didn't bind the class, Laravel will happily create a instance of that class for you. Where you actually need the binding is when you use interfaces. Usually, you'd not type-hint specific classes but a interface. But Laravel can not create a instance of an interface and pass it to you, so Laravel needs to know how it can construct a class which implements the interface you need. In this case, you'd bind the class (or the closure which creates the class) to the interface.
I have a question! I have a library, whenever I need to call, I'll include it into & new Class() like the link below.
Now, I want to include it to use with Lumen framework & call usually inton Controller, then how to register service, class in Lumen to make it comfortable so that when needing, just call new FileMaker();
http://laravel.io/bin/E3d9x
Thank you so much!
What you're looking for is a Service Provider. Instead of including files in your Controllers, and then new'ing up instances of a class it is better to register the class within a service provider and then resolve the object out of the IoC container.
An example of how you can register a provider:
public function register()
{
$this->app->singleton('Full\Vendor\Namespace\FileMaker', function($app) {
return new FileMaker('someparameters');
});
}
Doing it this way means that you can inject dependencies into your Controllers and Laravel, or Lumen in this case will automatically resolve the object without you needing to instantiate the object.
For example, in your controller:
public function someControllerMethod(FileMaker $filemaker)
{
// The $filemaker instance is available to use
}
I've kind of looking at what's going on Laravel 4 facades under the hood.
Let's take this Facade as an example:
File::get(someArgs);
If i'm not mistaken, the step by step (oversimplified) invocation would be:
//static method invocation which are all extended from Facade class
File::__callStatic(get, someArgs)
//returns an instance of FileSystem
File::resolveFacedeInstance('files')
FileSystem->get(someArgs)
What I'am confused about is in the commented line below of the method File::resolveFacadeInstance() below:
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) return $name;
if (isset(static::$resolvedInstance[$name]))
{
return static::$resolvedInstance[$name];
}
/**
* The line that i'm confused about
*/
return static::$resolvedInstance[$name] = static::$app[$name];
}
My questions are:
How is File::$app even initialized or assigned a value inside the Facade class
If File::get() is the invoked Facade
static::$app[$name] would resolve to i think Application['files']
or Application->files which in turn calls Application->__get('files') since there's no files property inside Application class.
How would FileSystem Class be return if this is only the content of this method?
public function __get($key)
{
return $this[$key];
}
I'll try to describe in short :
So, you already know that resolveFacadeInstance method is called via __callStatic method of Facade class and component's Facade (i.e. File extends Facade) extends this Facade class.
During the boot-up process of the framework, from public/index.php following line starts the execution of bootstrap/start.php file
$app = require_once __DIR__.'/../bootstrap/start.php';
So, in this (bootstrap/start.php) file you can see some code like
// the first line, initiate the application
$app = new Illuminate\Foundation\Application;
// ...
// ...
// notice this line
require $framework.'/Illuminate/Foundation/start.php';
// ...
// last line
return $app;
In this code snippet, require $framework.'/Illuminate/Foundation/start.php'; line starts the execution of Foundation/start.php file and in this file you may see something like this
// ...
Facade::clearResolvedInstances();
// Notice this line
Facade::setFacadeApplication($app);
This (given above) line sets application instanse to $app property in the Facade class
// support/Facades/Facade.php
public static function setFacadeApplication($app)
{
static::$app = $app;
}
Then in the Foundation/start.php file at the bottom, you can see something like this
/*
|--------------------------------------------------------------------------
| Register The Core Service Providers
|--------------------------------------------------------------------------
|
| The Illuminate core service providers register all of the core pieces
| of the Illuminate framework including session, caching, encryption
| and more. It's simply a convenient wrapper for the registration.
|
*/
$providers = $config['providers'];
$app->getProviderRepository()->load($app, $providers);
$app->boot();
In this code snippet given above, all the core components registered by the framework and as you know that, every component has a service provider class (i.e. FilesystemServiceProvider) and in every service provider class there is a method register which is (for FilesystemServiceProvider)
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app['files'] = $this->app->share(function() { return new Filesystem; });
}
Well, in this case $this->app['files'] setting (return new Filesystem) an anonymous function, which returns the filesystem when gets executed
$this->app['files'] = $this->app->share(function() { return new Filesystem; });
to $app['files'] so, when you call the File::get(), it finally calls the anonymous function and in this case, the following line
return static::$resolvedInstance[$name] = static::$app[$name];
Calls the function for static::$app['file']; and this function returns the instance but before returning, it stores the instance in the $resolvedInstance variable, so, next time it can return the instance from the variable without calling the anonymous function again.
So, it looks like that, static::$resolvedInstance[$name] = static::$app[$name]; calls the anonymous function which returns the instance and this function was registered earlier, when the app was started through boot up process.
Important :
Application extends Container and Container extends ArrayAccess class and that's why, a property of the $app object could be (accessed) set/get using array notation.
I've tried to give you an idea but you have to look in to the code, step by step, you won't get it only reading/tracking the code once.