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.
Related
I have the following definition:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\SomeClass;
class SomeProvider extends ServiceProvider
{
protected $defer = true;
public function register()
{
$this->app->bind(SomeClass::class, function ($app)
{
return new SomeClass();
});
}
public function provides()
{
die("This never gets called");
return [SomeClass::class];
}
}
And it returns an instance of SomeClass as expected, except that according to the documentation, if $defer is true then the provides() method should be called. No matter what I set $defer to, and no matter if I actually ask for an instance of SomeClass or not, provides() is never called.
The way I'm asking for an instance of the class is as follows:
App::make('SomeClass');
Short answer:
Your compiled manifest file is already compiled by framework.
On the first time when Laravel build the application (and resolves all of services providers in IoC container)
it writes to cached file named services.php (that is, the manifest file, placed in: bootstrap/cache/services.php).
So, if you clear the compiled via php artisan clear-compiled command it should force framework to rebuild the manifest file and you could to note that provides method is called.
On the next calls/requests provides method is not called anymore.
The sequence of framework boot is nearly like this:
//public/index.php
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
\Illuminate\Foundation\Http\Kernel::__construct();
\Illuminate\Foundation\Http\Kernel::handle();
\Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter();
\Illuminate\Foundation\Http\Kernel::bootstrap();
\Illuminate\Foundation\Application::bootstrapWith();
# where $bootstrapper is a item from \Illuminate\Foundation\Http\Kernel::$bootstrappers
# and $this is instance of \Illuminate\Foundation\Application
\Illuminate\Foundation\Application::make($bootstrapper)->bootstrap($this);
One of bootstrappers is Illuminate\Foundation\Bootstrap\RegisterProviders which
invokes \Illuminate\Foundation\Application::registerConfiguredProviders() and then invokes
\Illuminate\Foundation\ProviderRepository::__construct() and finally:
\Illuminate\Foundation\ProviderRepository::load()
When \Illuminate\Foundation\ProviderRepository::load() is called all services providers is registered and
\Illuminate\Support\ServiceProvider::provides() are called also well.
And here is the snippet you should know (from \Illuminate\Foundation\ProviderRepository::load):
/**
* Register the application service providers.
*
* #param array $providers
* #return void
*/
public function load(array $providers)
{
$manifest = $this->loadManifest();
// First we will load the service manifest, which contains information on all
// service providers registered with the application and which services it
// provides. This is used to know which services are "deferred" loaders.
if ($this->shouldRecompile($manifest, $providers)) {
$manifest = $this->compileManifest($providers);
}
// Next, we will register events to load the providers for each of the events
// that it has requested. This allows the service provider to defer itself
// while still getting automatically loaded when a certain event occurs.
foreach ($manifest['when'] as $provider => $events) {
$this->registerLoadEvents($provider, $events);
}
// We will go ahead and register all of the eagerly loaded providers with the
// application so their services can be registered with the application as
// a provided service. Then we will set the deferred service list on it.
foreach ($manifest['eager'] as $provider) {
$this->app->register($this->createProvider($provider));
}
$this->app->addDeferredServices($manifest['deferred']);
}
\Illuminate\Foundation\ProviderRepository::compileManifest() is the place where your provides() method is performed.
After doing my own testing, it would seem this is an issue (maybe "issue" is a strong word in this case).
If you register something in a service provider which has the name of a class, Laravel is just going to return that class and disregard whatever is in the service provider. I started doing the same thing as you did....
protected $defer = true;
public function register()
{
$this->app->bind(SomeClass::class, function ($app)
{
return new SomeClass();
});
}
public function provides()
{
dd('testerino');
}
$test = \App::make('App\SomeClass');
And $test is an instance of SomeClass. However if I make the following change...
$this->app->bind('test', function ($app) { ... }
And use
$test = \App::make('test');
Then it hits the deffered function and outputs the text testerino.
I think the issue here is that Laravel knows you are just trying to grab a class. In this instance, there is no reason to register what you are trying to register with the container, you aren't doing anything except telling Laravel to make an instance of App\SomeClass when it should make an instance of App\SomeClass.
However, if you tell Laravel you want an instance of App\SomeClass when you call App::make('test'), then it actually needs to bind that class to test so then I think it starts to pay attention to the service provider.
I want to swap out my client call or better i try to make a wrapper around this package, so i dont have to write this everytime, so i made a new ServiceProvider which should call
// Create a new client,
// so i dont have to type this in every Method
$client = new ShopwareClient('url', 'user', 'api_key');
on every request i make.
// Later after the Client is called i can make a Request
return $client->getArticleQuery()->findAll();
SwapiServiceProvider
<?php
namespace Chris\Swapi;
use Illuminate\Support\ServiceProvider;
use LeadCommerce\Shopware\SDK\ShopwareClient;
class SwapiServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* #return void
*/
public function boot()
{
}
/**
* Register any package services.
*
* #return void
*/
public function register()
{
$this->app->singleton(ShopwareClient::class, function () {
return new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
);
});
}
}
My Class
...
use LeadCommerce\Shopware\SDK\ShopwareClient as Shopware;
class Swapi
{
public function fetchAllArticles(Shopware $shopware)
{
return $shopware->getArticleQuery()->findAll();
}
}
Testing
I just call it in my routes.php for testing
use Chris\Swapi\Swapi;
Route::get('swapi', function () {
// Since this is a package i also made the Facade
return Swapi::fetchAllArticles();
});
But i get everytime the error
FatalThrowableError in Swapi.php line 18: Type error: Argument 1
passed to Chris\Swapi\Swapi::fetchAllArticles() must be an instance of
LeadCommerce\Shopware\SDK\ShopwareClient, none given, called in
/Users/chris/Desktop/code/swapi/app/Http/routes.php on line 7
So i am asking why this
return new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
);
is not called everytime i call a method e.g $shopware->getArticleQuery()->findAll();
Does anyone know why?
I think there might be some confusion here about Laravel's IoC. When you use return Swapi::fetchAllArticles();, Laravel doesn't know what you are doing because you haven't used the container to build out the Swapi class (even though you have registered one with the container) nor do you have a facade built to access it in that manner. Otherwise PHP is going to complain because your function isn't static.
I just wrote this code and verified that it works as far as Laravel putting it all together.
In my service provider, my register function was this...
public function register()
{
$this->app->singleton('swapi', function($app) {
return new SwapiRepository(
new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
)
);
});
}
Keep in mind, swapi is really just a key the container will use to find the actual class. There's no need to pass in the entire qualified class name when you can keep it simple and easy.
My SwapiRepository which is really the wrapper for the Shopware SDK.
use LeadCommerce\Shopware\SDK\ShopwareClient;
class SwapiRepository
{
protected $client;
public function __construct(ShopwareClient $client)
{
$this->client = $client;
}
public function fetchAllArticles()
{
return $this->client->getArticleQuery()->findAll();
}
}
At this point, you are basically done. Just add App\Providers\SwapiServiceProvider::class, in the providers array (which you probably have done already) in app/config.php and use your wrapper like so...
$swapi = app('swapi');
$swapi->fetchAllArticles();
Or you can have Laravel inject it into other classes as long as Laravel is building said class.
If you want to build out a facade for this to save yourself a line of code each time you want to use this or for snytactical sugar...
use Illuminate\Support\Facades\Facade;
class Swapi extends Facade
{
protected static function getFacadeAccessor() { return 'swapi'; }
}
Make sure to update your aliases array in app/config.php so that it contains 'Swapi' => App\Repositories\Swapi::class,
And finally you should be able to use it like so...
Swapi::fetchAllArticles();
Please note your namespaces are different than mine so you may need to replace mine with yours. You should also now be able to easily inject Swapi into other classes and even method injected into your controllers where needed.
Just remember if you do that though, make sure you are grabbing instances of those classes from Laravel's service container using the app() function. If you try to build them out yourself using new SomeClass, then you have the responsibility of injecting any dependencies yourself.
So I figured I'd try to actually use this fancy IoC container in Laravel. I'm starting with Guzzle but I cannot get it to work. Perhaps there is a gap in my understanding. I really appreciate any help here.
so I've got a class for connecting to a RESTful Api. Here is a sample from it:
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Client;
use GuzzleHttp\Subscriber\Oauth\Oauth1;
class EtApi {
//you can pass in the model if you wanna
//protected $model;
//client Id
protected $clientId;
//client secret
protected $clientSecret;
//base_uri
protected $getTokenUri;
protected $client;
//build
function __construct(Client $client)
{
$this->client = $client;
$this->clientId = 's0m3R4nd0mStr1nG';
$this->clientSecret = 's0m3R4nd0mStr1nG';
$this->getTokenUri = 'https://rest.api/requestToken';
$this->accessToken = $this->getToken($this->clientId, $this->clientSecret, $this->getTokenUri);
}
}
I've successfully installed and used Guzzle by manually newing it up inside of methods like $client = new Client(); but that's not very DRY and it's not the right way of doing things. So I created a ServiceProvider at app\Providers\GuzzleProvider.php. I made sure this was registered in app/config/app.php under $providers = ['App\Providers\GuzzleProvider']. Here is the Provider Code:
<?php namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use GuzzleHttp\Client;
use GuzzleHttp\Subscriber\Oauth\Oauth1;
class GuzzleProvider extends ServiceProvider {
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
$this->app->bind('Client', function () {
return new Client;
});
}
}
So when I try to access my EtApi methods that load fails during the instantiation (__construct) with the following error.
ErrorException in EtApi.php line 23:
Argument 1 passed to App\EtApi::__construct() must be an instance of GuzzleHttp\Client, none given, called in /home/vagrant/webdocs/et_restful_test/app/Http/Controllers/EtConnectController.php on line 23 and defined
Do any of you Laravel Masters have any idea why I can't bind Guzzle using this code and Laravel's magic will just inject the obj into the constructor? The [docs1 say I should be able to do this. I must be missing something. Thank You!
It's a little hard to say for certain based on the information in your question, but based on this
Argument 1 passed to App\EtApi::__construct() must be an instance of GuzzleHttp\Client, none given, called in /home/vagrant/webdocs/et_restful_test/app/Http/Controllers/EtConnectController.php on line 23 and defined
It sounds like you're directly instantiating your App\Eti class on line 23 of EtConnectController.php with code that looks something like this
$api = new App\EtApi;
If that's the case, there's a key piece of Laravel's dependency injection you're missing. Laravel can't change the behavior of standard PHP -- i.e. if you create a new class with PHP's built-in new keyword, then Laravel never has the change to inject any dependencies in __construct.
If you want to take advantage of dependency injection, you also need to instantiate your object via Laravel's app container. There's many different way to do that -- here's two them
//$api = new App\EtApi;
\App::make('App\EtApi'); //probably "the right" way
$api = app()['App\EtApi']
If you do that, Laravel will read the type hints in __construct and try to inject dependencies for your object.
Just change your register function to
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
$this->app->bind('GuzzleHttp\Client\Client', function () {
return new Client;
});
}
That should do the trick => the IOC resolves the fqcn and not the short one, so exposing it in your container you'll need to bind it to the fqcn too!
Hope it helps!
I have something like the following set up in Laravel:
In /app/controllers/MyController.php:
class MyController extends BaseController {
const MAX_FILE_SIZE = 10000;
// ....
}
In /app/tests/MyControllerTest.php:
class MyControllerTest extends TestCase {
public function myDataProvider() {
return [
[ MyController::MAX_FILE_SIZE ]
];
}
/**
* #dataProvider myDataProvider
*/
public function testMyController($a) {
// Just an example
$this->assertTrue(1 == 1);
}
}
However, when I run vendor/bin/phpunit I get the following error:
PHP Fatal error: Class 'Controller' not found in /home/me/my-app/app/controllers/BaseController.php on line 3
Fatal error: Class 'Controller' not found in /home/me/my-app/app/controllers/BaseController.php on line 3
If I remove the reference to the MyController class in myDataProvider() and replace it with a literal constant then the test completes successfully.
In addition, I can place references to MyController::MAX_FILE_SIZE inside the actual testMyController() method, and the test also completes successfully.
It appears that the autoloading setup for Laravel framework classes isn't being set up until after the data provider method is being called, but before the actual test methods are called. Is there any way around this so that I can access Laravel framework classes from within a PHPUnit data provider?
NOTE: I'm calling PHPUnit directly from the command line and not from within an IDE (such as NetBeans). I know some people have had issues with that, but I don't think that applies to my problem.
As implied in this answer, this appears to be related to the order that PHPUnit will call any data providers and the setUp() method in any test cases.
PHPUnit will call the data provider methods before running any tests. Before each test it will also call the setUp() method in the test case. Laravel hooks into the setUp() method to call $this->createApplication() which will add the controller classes to the 'include path' so that they can be autoloaded correctly.
Since the data provider methods are run before this happens then any references to controller classes inside a data provider fail. It's possible work around this by modifying the test class to something like this:
class MyControllerTest extends TestCase {
public function __construct($name = null, array $data = array(), $dataName = '') {
parent::__construct($name, $data, $dataName);
$this->createApplication();
}
public function myDataProvider() {
return [
[ MyController::MAX_FILE_SIZE ]
];
}
/**
* #dataProvider myDataProvider
*/
public function testMyController($a) {
// Just an example
$this->assertTrue(1 == 1);
}
}
This will call createApplication() before the data provider methods are run, and so there is a valid application instance that will allow the appropriate classes to be autoloaded correctly.
This seems to work, but I'm not sure if it's the best solution, or if it is likely to cause any issues (although I can't think of any reasons why it should).
The test will initialize much faster if you create the application right within the dataProvider method, especially if you have large set of items to test.
public function myDataProvider() {
$this->createApplication();
return [
[ MyController::MAX_FILE_SIZE ]
];
}
Performance warning for the other solutions (especially if you plan to use factories inside your dataProviders):
As this article explains:
The test runner builds a test suite by scanning all of your test
directories […] When a
#dataProvider annotation is found, the referenced data provider is
EXECUTED, then a TestCase is created and added to the TestSuite for
each dataset in the provider.
[…]
if you use factory methods in your data providers, these
factories will run once for each test utilizing this data provider
BEFORE your first test even runs. So a data provider […] that is used by ten tests
will run ten times before your first
test even runs. This could drastically slow down the time until your
first test executes. Even […] using phpunit --filter,
every data provider will still run multiple times. Filtering occurs after the test
suite has been generated and therefore after any
data providers have been executed.
The above article proposes to return a closure from the dataProvider and execute that in your test:
/**
* #test
* #dataProvider paymentProcessorProvider
*/
public function user_can_charge_an_amount($paymentProcessorProvider)
{
$paymentProcessorProvider();
$paymentProcessor = $this->app->make(PaymentProviderContract::class);
$paymentProcessor->charge(2000);
$this->assertEquals(2000, $paymentProcessor->totalCharges());
}
public function paymentProcessorProvider()
{
return [
'Braintree processor' => [function () {
$container = Container::getInstance();
$container->bind(PaymentProviderContract::class, BraintreeProvider::class);
}],
...
];
}
You can adjust this behaviour of PHPUnit by adding your custom bootstrapper to your projects phpunit.xml like this (look at 3rd line):
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
bootstrap="tests/bootstrap.php" ← ← ← THIS
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
...
</phpunit>
Then create a bootstrap.php file in your tests folder (i.e. the path you denoted above), and paste this:
<?php
use Illuminate\Contracts\Console\Kernel;
require __DIR__ . '/../vendor/autoload.php';
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
You can now use Laravel functionality in your data providers, just keep in mind they still run after your setUp methods.
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.