I am just starting to get the hang of Service Providers and the IoC container, however one thing is confusing me. I have a SpamServiceProvider that requires two other classes to function. However one of those classes, InvalidKeywords, has a array $blacklist parameter which needs to be passed to its constructor.
If I register that class in the AppServiceProvider and pass in the $blacklist array, everything works fine. However, if I try to bind the class in the SpamServiceProvider instead it will not inject the $blacklist into InvalidKeywords constructor.
So I guess my question is why is this? And is there a way to keep bindings like this together in a single container or do I simply have to bind InvalidKeywords inside the AppServiceProvider?
This works
class SpamServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* #var bool
*/
protected $defer = true;
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->app->bind(SpamManager::class, function ($app) {
return new SpamManager(new InvalidKeywords, new RepeatedCharacters);
});
}
}
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(InvalidKeywords::class, function ($app) {
return new InvalidKeywords(config('spam.blacklist'));
});
}
}
This does not work
class SpamServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* #var bool
*/
protected $defer = true;
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->app->bind(InvalidKeywords::class, function ($app) {
return new InvalidKeywords(config('spam.blacklist'));
});
$this->app->bind(SpamManager::class, function ($app) {
return new SpamManager(new InvalidKeywords, new RepeatedCharacters);
});
}
}
In the second case you're not resolving the InvalidKeywords class from the container, simply creating a new instance. Instead, try using app or resolve helpers when creating the SpamManager:
$this->app->bind(SpamManager::class, function ($app) {
return new SpamManager(resolve(InvalidKeywords::class), resolve(RepeatedCharacters::class));
});
// or
$this->app->bind(SpamManager::class, function ($app) {
return new SpamManager(app(InvalidKeywords::class), app(RepeatedCharacters::class));
});
I would create a singleton with InvalidKeywords as well:
$this->app->singleton(InvalidKeywords::class, function ($app) {
return new InvalidKeywords(config('spam.blacklist'));
});
Related
I have tried for days to get this to work.
I am using this framework: https://github.com/DennisSkoko/discord-bot to create a bot. But I wan't to add the DIC container http://container.thephpleague.com/ to this project.
What I want to do is in the Main class is to register service providers (modules/packages/bundles) or whatever you want to call them, just like Laravel etc. does.
So I created a module/ dir and added a serviceprovider class and some other classes as the docs state: http://container.thephpleague.com/service-providers/
First I edited the start.php to inject the container:
use League\Container\Container;
require "vendor/autoload.php";
require "bot.php";
$container = new Container;
$main = new DS\Main(new Example($container));
$main->run();
I changed the Example bot to include a getContainer() function so I could read it from the Main class, like this:
use DS\Bot;
use Discord\Discord;
use DS\Service;
class Example extends Bot
{
/**
* Example constructor
*/
public function __construct($container)
{
$this->config = [
"token" => "My token"
];
$this->container = $container;
}
/**
* Will be executed when the WebSocket is ready and before the services are implemented.
*
* #param Discord $discord
*
* #return void
*/
public function setup(Discord $discord)
{
echo "Example is ready to start!";
}
public function getContainer()
{
return $this->container;
}
/**
* Will return an array of Service that the bot uses.
*
* #return Service[]
*/
public function getServices()
{
return [];
}
}
I then changed the Main class to to register my service provider.
namespace DS;
use Discord\Discord;
/**
* A class that bring bots script into life.
*/
class Main
{
/**
* #var Discord
*/
private $discord;
/**
* #var Bot
*/
private $bot;
/**
* Main constructor.
*
* #param Bot $bot
*/
public function __construct(Bot $bot)
{
$this->bot = $bot;
$this->discord = new Discord($this->bot->getConfig());
$this->discord->on("ready", function ($discord) {
$this->bot->setup($discord);
$this->setServices($this->bot->getServices());
});
$this->container = $this->bot->getContainer();
$this->container->addServiceProvider('Mynamespace\HelloWorld\ServiceProvider');
}
/**
* Will run the bot.
*
* #return void
*/
public function run()
{
$this->discord->run();
}
/**
* Will add the services for the WebSocket.
*
* #param Service[] $services
*
* #return void
*/
private function setServices($services)
{
foreach ($services as $service) {
$this->discord->on($service->getEvent(), $service->getListener());
}
}
}
Here is the problem:
PHP Fatal error: Uncaught exception 'InvalidArgumentException' with message 'A service provider must be a fully qualified class name or instance of (\League\Container\ServiceProvider\ServiceProviderInterface)'
Which is weird because I did it just like in the documentation and extended League\Container\ServiceProvider\AbstractServiceProvider
This I have double checked 20 times.
So I have no idea what to do about this. How can I use addServiceProvider() in the Main class and register things in the DIC?
I do not really want to do it in the Example class if possible. Because that should be extended by the user and the Main class will bootstrap the bot.
I also tried $this->bot->addServiceProvider() but then I get the following error:
PHP Fatal error: Call to undefined method Example::addServiceProvider()
Any help appreciated.
I am using Laravel 5.2 and wrote my own Service Provider. I want to inject a Request object into the register method.
The base problem is that I want to call different service container depending on a special request param - all the service container implementing the same interface/contract of course.
The error message I am getting is:
ReflectionException in Container.php line 559:
Function registerService() does not exist
My service provider looks like that:
<?php
namespace App\Providers;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;
use App\Contracts\Extractor;
class ExtractorServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* #var bool
*/
protected $defer = true;
/**
* Available services for channels
*
* #var array
*/
protected $availableServices = ['AExtractor', 'ZExtractor'];
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->call('registerService');
}
/**
* #param Request $request
* #return void
*/
protected function registerService(Request $request)
{
$tag = DB::table('channels')->where('id', $request->channel)->value('tag')->first()->tag;
$selectedExtractor = $tag . 'Extractor';
$extractor = 'AExtractor';
if(in_array($selectedExtractor, $this->availableServices)) {
$extractor = $selectedExtractor;
}
$this->app->bind('App\Contracts\Extractor', "App\\Helpers\\{$extractor}");
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides()
{
return [Extractor::class];
}
}
How can I use $this->app->call('registerService'); to call my registerService function and inject the Request object?
The problem is you're calling App:call in a wrong way: you have to specify the object on which you want to call the method and the method, like this :
$this->app->call( [ $this, 'registerService' ] );
I'm trying my hand at building a custom Service Provider package, however I'm running into the following error. Does anyone have experience with this?
Unresolvable dependency resolving [Parameter #0 [ <required> $app ]] in class Illuminate\Support\ServiceProvider
Package Folder Structure:
[root]
....packages/
........mbarwick83/
............previewr/
................src/
....................PreviewrServiceProvider.php
....................Previewr.php
................composer.json
config/app.php:
Mbarwick83\Previewr\PreviewrServiceProvider::class
Service Provider:
<?php
namespace Mbarwick83\Previewr;
use Illuminate\Support\ServiceProvider;
class PreviewrServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* #var bool
*/
protected $defer = false;
/**
* Perform post-registration booting of services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register any package services.
*
* #return void
*/
public function register()
{
$this->app->bind('Mbarwick83\Previewr\Previewr',function($app){
return new Previewr($app);
});
}
}
Previewr.php (class):
<?php
namespace Mbarwick83\Previewr;
class Previewr
{
/**
* Create a new Previewr Instance
*/
public function __construct()
{
//
}
/**
* Friendly welcome
*
* #param string $phrase Phrase to return
*
* #return string Returns the phrase passed in
*/
public function something($phrase)
{
return $phrase;
}
}
Controller/view:
use Mbarwick83\Previewr\PreviewrServiceProvider as Previewr;
public function index(Previewr $previewr)
{
echo $previewr->something('Hello, League!');
}
composer.json:
"autoload": {
"psr-4": {
"Mbarwick83\\Previewr\\": "packages/Mbarwick83/Previewr/src"
}
},
In your controller, are you sure you want to inject your service provider?
use Mbarwick83\Previewr\PreviewrServiceProvider as Previewr;
Chances are you want to use this instead:
use Mbarwick83\Previewr\Previewr;
Maybe someone can tell me how to use something like embedded controllers in symfony2, to call\render controller action in Laravel4,5?
Found the best way for me, for L5:
CartServerProvider
use Illuminate\Support\ServiceProvider;
class CartServiceProvider extends ServiceProvider {
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app->make('view')->composer('layouts.master', 'Vendor\Cart\Http\ViewComposers\CartComposer');
}
}
The CartComposer class looks like this:
use Illuminate\Contracts\View\View;
class CartComposer {
/**
* Cart manager instance.
*
* #var \Vendor\Cart\StoreInterface
*/
protected $cart;
/**
* Create a new CartComposer instance.
*/
public function __construct()
{
$this->cart = app()->make('cart.store');
}
/**
* Compose the view.
*
* #return void
*/
public function compose(View $view)
{
$view->with('cart', $this->cart);
}
}
cart.store is a custom cart implementation I injected into the container, but the above should be enough to show you how to register a simple view composer.
I want to override a method (isValidatable) in the Illuminate\Validation\Validator class. I have done this by creating a class (outside Illuminate) that extends the Validator and only overrides the isValidatable method.
I think this will work, except I'm not sure how to create the service provider for the Validator class (or actually CustomLaravelValidator class). I have created service providers before, but there seems to be going on a lot inside the Validator serviceprovider (Illuminate\Validation\ValidationServiceProvider). Therefore I don't have a clue on how my custom service provider for this class should look like.
This is my CustomLaravelValidator class:
<?php namespace API\Extensions\Core;
use Illuminate\Validation\Validator;
class CustomLaravelValidator extends Validator {
/**
* Determine if the attribute is validatable.
*
* #param string $rule
* #param string $attribute
* #param mixed $value
* #return bool
*/
protected function isValidatable($rule, $attribute, $value)
{
// Validate integers on empty strings as well
if($rule == 'IntStrict')
{
return true;
}
return $this->presentOrRuleIsImplicit($rule, $attribute, $value) &&
$this->passesOptionalCheck($attribute);
}
}
This is the default ValidationServiceProvider from Laravel:
<?php namespace Illuminate\Validation;
use Illuminate\Support\ServiceProvider;
class ValidationServiceProvider extends ServiceProvider {
/**
* Indicates if loading of the provider is deferred.
*
* #var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->registerPresenceVerifier();
$this->app->bindShared('validator', function($app)
{
$validator = new Factory($app['translator'], $app);
// The validation presence verifier is responsible for determining the existence
// of values in a given data collection, typically a relational database or
// other persistent data stores. And it is used to check for uniqueness.
if (isset($app['validation.presence']))
{
$validator->setPresenceVerifier($app['validation.presence']);
}
return $validator;
});
}
/**
* Register the database presence verifier.
*
* #return void
*/
protected function registerPresenceVerifier()
{
$this->app->bindShared('validation.presence', function($app)
{
return new DatabasePresenceVerifier($app['db']);
});
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides()
{
return array('validator', 'validation.presence');
}
}
Can anyone tell me how my custom serviceprovider have to look like?
Your service provider doesn't need to mimic the native Validator service provider. You just need to register your custom validator using the resolver method on the validator factory.
use API\Extensions\Core\CustomLaravelValidator;
class CustomValidationServiceProvider extends ServiceProvider {
public function boot()
{
$this->app['validator']
->resolver(function($translator, $data, $rules, $messages)
{
return new CustomLaravelValidator(
$translator,
$data,
$rules,
$messages
);
});
}
}
That's it...