Laravel deferred service provider `provides` not being called - php

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.

Related

Laravel 5.4: Change DB according to sub domain in laravel

I am creating a SAAS application with a single laravel installation and multi tenant database.
So i have a main DB that is connected to laravel by default. the client can access the application through their sub-domain like: client1.example.com and client2.example.com
I am saving all info fo the client like domain and db name in the main db.
How can change the DB according to the subdomain
I tried with the following Code
in routes/web.php
Route::group(['domain' => '{account}'], function() {
Route::get('/', function($account) {
// This will return {account}, which you can pass along to what you'd like
return $account;
});
});
in app/Providers\RouteServiceProvieder.php
public function boot(Router $router)
{
parent::boot($router);
$router->bind('domain', function ($value) {
//$hst=$request->getHost();
$domain= Customer::where('sub_domain',$value)->first();
if ($domain) {
return $domain;
}
throw new Exception('error message');
});
}
Then i have created a ServiceProvider ConfigServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Request;
class ConfigServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register(Request $request)
{
$customer=$request->route('domain');
//$customer = Request::route('domain'); // there are multiple ways of doing this
//$customer = $request->route()->domain;
config([
'database.mysql.database' => $customer->customer_database, //this is because I have same username and password for all the databases. If you have different username and passwords set them too.
]);
DB::reconnect(); // this will help in reconnecting the database if the connection is already established. and import the DB facade.
}
}
But the code is not working?
The Error i am getting is
FatalThrowableError in ConfigServiceProvider.php line 25:
Type error: Too few arguments to function App\Providers\ConfigServiceProvider::register(), 0 passed in C:\xampp\htdocs\main\vendor\laravel\framework\src\Illuminate\Foundation\Application.php on line 568 and exactly 1 expected
I am using Laravel 5.4. Can any one guide me how to do it correctly?
well there are some issues here:
First the register() method of a service provider should not accept a Request instance as argument. Its better to write it like that:
public function register()
Second, dont use config() in the register() method, from the laravel docs:
within the register method, you should only bind things into the
service container. You should never attempt to register any event
listeners, routes, or any other piece of functionality within the
register method. Otherwise, you may accidentally use a service that is
provided by a service provider which has not loaded yet.
IMHO I would use a middleware to change the database configuration like you tried in a service provider, i.e:
public function handle($request, Closure $next)
{
$customer=$request->route('domain');
if($customer)
{
config(['database.mysql.database' => $customer->customer_database]);
DB::reconnect();
}
return $next($request);
}
If you take care of loading the middleware in the right place (look in app/Http/Kernel.php) you will have database changed before any query.
What I would suggest is to create 2 .env files, one for each subdomain e.g. .env.domain1 and .env.domain2
Then in your app service provider:
public function boot() { //Use boot for this
$fn = ".env.".$this->app->request->route('domain');
$dotenv = new \Dotenv\Dotenv(self::basePath(), $fn);
try {
$dotenv->overload();
} catch (\Dotenv\Exception\InvalidPathException $e) {
//Nothing to overload with
}
}
This makes your sub-domains behave like different environments which does look like what you're trying to do.
Change
use Illuminate\Support\Facades\Request;
to
use Illuminate\Http\Request;
You can not use Facades for dependency/method injection. Hope it helps

Laravel Own ServiceProvider Client Call Type error: Argument 1 passed to ... must be an instance of

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.

How does Laravel defer multiple bindings all listed in a single service provider?

I want all my repositories to be listed in a single service provider, but I don't want them all loaded at once...
Consider the service provider below:
class RepositoryServiceProvider extends ServiceProvider {
protected $defer = true;
public function register()
{
$this->app->bind(
'App\Repositories\Contracts\FooRepository',
'App\Repositories\SQL\FooSQLRepository');
$this->app->bind(
'App\Repositories\Contracts\BarRepository',
'App\Repositories\SQL\BarSQLRepository');
// and more to be added later...
}
public function provides()
{
// Will it defer and load all these at once? Or only the one(s) needed?
return ['App\Repositories\Contracts\FooRepository',
'App\Repositories\Contracts\BarRepository'];
}
}
According to the Laravel docs, I can defer the registration of bindings until needed. But does this work for when I've added multiple bindings in a single service provider? Specifically I mean, will it defer and then load all or load only the one needed?
Laravel will register all bindings, even if only one is needed. The deferred feature actually works pretty simple. First, a map of the entries in provides() and the actual provider is created:
Illuminate\Foundation\ProviderRepository#compileManifest
if ($instance->isDeferred())
{
foreach ($instance->provides() as $service)
{
$manifest['deferred'][$service] = $provider;
}
$manifest['when'][$provider] = $instance->when();
}
Then when make() is called in Illuminate\Foundation\Application...
if (isset($this->deferredServices[$abstract]))
{
$this->loadDeferredProvider($abstract);
}
...and the binding matches one of a deferred provider it will end up here:
Illuminate\Foundation\Application#registerDeferredProvider
$this->register($instance = new $provider($this));
if ( ! $this->booted)
{
$this->booting(function() use ($instance)
{
$this->bootProvider($instance);
});
}
As you might are able to tell, now the provider is registered as usual which means register() and boot() is called. If you think about it, it's not even possible to load one binding from a service provider and not include the other ones, because it's all done in one method.

How to override View::make() in Laravel 4?

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.

Laravel 4 : How are Facades resolved?

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.

Categories