I've been studying the laravel 4 container to get more knowledge of the internals of laravel and to upgrade my own skills in writing better code.
However i'm failing to understand 3 similar pieces of code.
I'll use the smallest snippet to keep this question clean.
Similar questions can be found in links below. Although people have replied with correct answers, I'm not satisfied with simply 'Knowing how to use it, but not knowing how it all works inside'. So i really hope someone can give an explanation to all this.
Question 1
Question 2
<?php namespace Illuminate\Container; use Closure, ArrayAccess, ReflectionParameter;
class BindingResolutionException extends \Exception {}
class Container implements ArrayAccess {
/**
* Wrap a Closure such that it is shared.
*
* #param Closure $closure
* #return Closure
*/
public function share(Closure $closure)
{
return function($container) use ($closure)
{
// We'll simply declare a static variable within the Closures and if
// it has not been set we'll execute the given Closure to resolve
// the value and return it back to the consumers of the method.
static $object;
if (is_null($object))
{
$object = $closure($container);
}
return $object;
};
}
}
How does the share method know that the $container variable in that function is in fact an instance of Illuminate\Container? It isn't defined within the scope of that function.
Neither is it defined in the following example usecase (which wouldn't help anyway)
class AuthServiceProvider extends ServiceProvider{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app['auth'] = $this->app->share(function($app)
{
// Once the authentication service has actually been requested by the developer
// we will set a variable in the application indicating such. This helps us
// know that we need to set any queued cookies in the after event later.
$app['auth.loaded'] = true;
return new AuthManager($app);
});
}
}
I'd expect a different implementation, so here comes
class MyContainer{
public function share(Closure $closure)
{
$container = $this;
return function() use ($closure, $container)
{
static $object;
if(is_null($object))
{
$object = $closure($container);
}
return $object;
};
}
}
$closure = function($container)
{
var_dump($container);
};
$container = new MyContainer();
call_user_func($container->share($closure));
//dumps an instance of MyContainer -> which is the wanted behaviour
$container = new Illuminate\Container\Container();
call_user_func($container->share($closure));
//Throws a warning AND a notice
//Warning: Missing argument 1 for Illuminate\Container\Container::Illuminate\Container\{closure}() in /Users/thomas/Sites/Troll/vendor/illuminate/container/Illuminate/Container/Container.php on line 128
//NOTICE: Notice: Undefined variable: container in /Users/thomas/Sites/Troll/vendor/illuminate/container/Illuminate/Container/Container.php on line 137
//and even worse the output of the var_dump is NULL
I have the same problem in understanding the extend and the bind method, which both have the same implementation of passing a none-existing parameter as a closure argument, but i cannot grasp how it is resolved to the container instance itself?
The return value of Container::share() is a function that takes one argument: the container itself. In order to call it externally, you'd have to do this:
$closure = function ($container) {
var_dump($container);
};
$container = new Illuminate\Container\Container();
call_user_func($container->share($closure), $container);
The reason for this is due to how service definitions work. The intended use of share is to wrap around a service definition.
Like this:
$container = new Illuminate\Container\Container();
$container['foo'] = $container->share(function ($container) { return new Foo(); });
When you access a service, like this:
var_dump($container['foo']);
It checks if the value is callable, and if it is, it will try to call it as a function. If you leave off the share, you will get a new Foo instance every time. The share memoizes the instance and returns the same one every time.
To re-iterate, the $container argument in the function returned from share is there because that's how service creation works. The service definition ("factory" function that you "set" on the container) is just a function that takes a container and returns the instance of the service it is creating.
Since offsetGet() it is expecting the definition to take a $container argument, that's what share returns.
Related
I'm trying to refactor a class and having problems testing it when I place certain code in the __constructor method and it throws an error that table not found within the test but works outside of tests.
I know this means that in the testing environment the table has yet to be created and although I'm using RefreshDatabase within the test it appears that at the point the class I'm testing initialises and attempts to access the database it's not ready. So I'm either doing something in the constructor I shouldn't or I'm missing something in my test structure.
Here's the basics of the class I'm tryting to test:
class PlayerRounds
{
use EclecticPresenter;
private RoundRepository $roundRepository;
private CourseRepository $courseRepository;
private $courseHoles;
public function __construct(RoundRepository $roundRepository, CourseRepository $courseRepository)
{
$this->roundRepository = $roundRepository;
$this->courseRepository = $courseRepository;
$this->init();
}
private function init()
{
$this->courseHoles = $this->courseRepository->all();
}
/**
* generates eclecic rounds for each league the player is in
* #param Player $player
*/
public function getAllEclecticRounds(Player $player)
{
$allEclecticRounds = collect();
$leagues = $player->league()->where('league_type', 'eclectic')->get();
$leagues->each(function ($league) use ($player, $allEclecticRounds) {
$newRound = $this->getPlayerEclecticRound($player, $league);
$allEclecticRounds->put($league->id, $newRound);
});
return $allEclecticRounds;
}
The test fails at the init() method. The fetch $this->courseHoles = $this->courseRepository->all(); the the test fails with a table not found error if it's within the constructor, It works if I place this piece of code within each method that needs it but means I call it often rather than once.
Here's my test:
class PlayerRoundsTest extends TestCase
{
use RefreshDatabase;
use EclecticTestHelper;
use WithFaker;
private $playerRounds;
protected function setUp(): void
{
parent::setUp();
$this->seed(CourseTableSeeder::class);
$this->playerRounds = app()->make(PlayerRounds::class);
}
/**
* #test
* #covers PlayerRounds::getAllEclecticRounds
* #description:
*/
public function it_returns_an_eclectic_round_for_all_leagues()
{
$player = Player::factory()->has(League::factory()->count(3))->create();
foreach ($player->league()->get() as $league) {
for ($x = 0; $x <= 3; $x++) {
$this->createScores($league, $player);
}
}
$result = $this->playerRounds->getAllEclecticRounds($player);
$this->assertCount(3, $result);
$result->each(function($collection) {
$this->assertCount(1, $collection);
});
Are there any ideas how I can initiate the class correctly and get the test set up correct and ensure the database is ready for the test. I assumed using RefreshDatabase was the correct approach and I had things in the correct order.
Thank you
**update
If I change the constructor to this:
public function __construct(RoundRepository $roundRepository, CourseRepository $courseRepository)
{
$this->roundRepository = $roundRepository;
$this->courseRepository = $courseRepository;
}
and then place the code that calls on the database to the method used in the test back to this:
public function getPlayerEclecticRound(Player $player, League $league, $maxDate = null)
{
// FIXME: Initiate at start in constructor but fails in tests
$this->courseHoles = $this->courseRepository->all();
//rest of code removed for brevity
}
This then passes the test.
This class needs the data in $this->courseHoles for a number of methods to work so I'm aiming to just call this once at initialization rather than every time I access the method as it is now but can' get it to work in a testing environment.
note I'm using a mysql database on the server but a sqllite memory database in testing
###update
Ok, after a bit of playing around the error is being caused by the loading of a custom artisan command I created. That command class has a dependancy of another class which in turn calls on the class I'm tresting.
I removed the command from Kernel.php as follows:
protected $commands = [
Inspire::class,
FixtureReminder::class,
SmsFixtureReminder::class,
UpdateMigrationTable::class,
// EclecticUpdate::class,
// MatchplayUpdate::class,
CleanTemporaryFiles::class,
AuthPermissionCommand::class
];
So - am I right in assuming for test purposes this is going to be impossible to isolate without changing this each time? This all relates to dependencies and the order in which CreateApplication works but I don't know enough to work around this.
I am a bit confused about my folder structure for the scraping code. Using console/commands, not the controller. So, in the handle function I am writing the whole scraping code. But should I suppose to do that? Or... what is the best approach for this?
UPDATED
If I understand correctly the answer below. It should look like this right now.
calling services
class siteControl extends Command
{
protected $signature = 'bot:scrape {website_id}';
protected $description = 'target a portal site and scrape';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$website_id = $this->argument("website_id");
if ($website_id == 1) {
$portal = "App\Services\Site1";
}
$crawler = new $portal;
$crawler->run();
}
}
in handle method
class Site1 extends Utility
{
public function __construct()
{
parent::__construct();
}
public function run()
{
echo "method runs";
}
}
abstract:
use Goutte\Client;
abstract class Utility implements SiteInterfaces
{
protected $client;
public function __construct()
{
$this->client = new Client();
}
}
interfaces:
namespace App\Services;
interface SiteInterfaces
{
public function run();
}
and finally, I should write the whole scraping code inside the run() method? Please correct me If wrong about this... I am searching the best solution.
A best practice would be to call a separate service from your command handle() method. That way you could reuse that same service in a controller for instance.
The technical version:
Your application is given a specific thing to do (a command if you will). This command comes from outside of your application, which can be a anything from a web controller, to an API controller or a CLI application. In terms of hexagonal architecture this is called a port.
Once the application receives such a command it should not care which port it came from. By handling all similar commands in a single spot (a command handler) it does not have to worry about the origins of the command.
So to give you a short overview:
[Web request] [CLI command] <-- these are ports
\ /
\ /
\ /
[Command] <--- this is a method call to your service
|
|
|
[Command handler] <--- this is the service doing the actual work
Updated my answer
Based on the code you provided I implemented what I mentioned above like so:
app/Console/Command/BotScrapeCommand.php
This is the CLI command I mentioned above. All this class has to do is:
1. Gather input arguments; (website_id) in this case
2. Wrap those arguments in a command
3. Fire off the command using the command handler
namespace App\Console\Commands;
use App\Command\ScrapePortalSiteCommand;
use CommandHandler\ScrapePortalSiteCommandHandler;
class BotScrapeCommand extends Command
{
protected $signature = 'bot:scrape {website_id}';
protected $description = 'target a portal site and scrape';
public function handle(ScrapePortalSiteCommandHandler $handler)
{
$portalSiteId = $this->argument("website_id");
$command = new ScrapePortalSiteCommand($portalSiteId);
$handler->handle($command);
}
}
app/Command/ScapePortalSiteCommand.php
This is the Command I mentioned above. Its job is to wrap all input arguments in a class, which can be used by a command handler.
namespace App\Command;
class ScrapePortalSiteCommand
{
/**
* #var int
*/
private $portalSiteId;
public function __construct(int $portalSiteId)
{
$this->portalSiteId = $portalSiteId;
}
public function getPortalSiteId(): int
{
return $this->portalSiteId;
}
}
app/CommandHandler/ScrapePortalSiteCommandHandler.php
The command handler should implement logic based on its command. In this case that's figuring out which crawler to pick, then fire that one off.
namespace App\CommandHandler;
use App\Command\ScrapePortalSiteCommand;
use App\Crawler\PortalSite1Crawler;
use App\Crawler\PortalSiteCrawlerInterface;
use InvalidArgumentException;
class ScrapePortalSiteCommandHandler
{
public function handle(ScrapePortalSiteCommand $command): void
{
$crawler = $this->getCrawlerForPortalSite($command->getPortalSiteId());
$crawler->crawl();
}
private function getCrawlerForPortalSite(int $portalSiteId): PortalSiteCrawlerInterface {
switch ($portalSiteId) {
case 1:
return new PortalSite1Crawler();
default:
throw new InvalidArgumentException(
sprintf('No crawler configured for portal site with id "%s"', $portalSiteId)
);
}
}
}
app/Crawler/PortalSiteCrawlerInterface.php
This interface is there to make sure all crawlers can be called in similar fashion. Additionally it makes for nice type hinting.
namespace App\Crawler;
interface PortalSiteCrawlerInterface
{
public function crawl(): void;
}
app/Crawler/PortalSite1Crawler.php
This is where the implementation of the actual scraping goes.
namespace App\Crawler;
class PortalSite1Crawler implements PortalSiteCrawlerInterface
{
public function crawl(): void
{
// Crawl your site here
}
}
Another update
As you had some additional questions I've updated my answer once more.
:void
The use of : void in a method declaration means the method will not return anything. In a same way public function getPortalSiteId(): int means this method will always return an integer. The use of return typehints was added to PHP 7 and is not specific to Laravel. More information on return typehints can be found in the PHP documentation.
Commands and handlers
The use of commands and command handlers is a best practice which is part of the command bus pattern. This pattern describes an universal way of dealing with user input (a command). This post offers a nice explanation on commands and handlers. Additionally, this blog post describes in more details what a command bus is, how it's used and what the advantages are. Please note that in the code I've provided the bus implementation itself is skipped. In my opinion you do not need it per se, but in some cases it does add value.
In Laravel, how do I resolve 2 different singleton implementations of an instance using Laravel's Service Container (https://laravel.com/docs/5.7/container).
For example, the 2 implementations I have for a Foo class is:
$this->app->singleton(Foo::class, function ($app) {
return new Foo(config('services.foo.apiKey1'));
});
and
$this->app->singleton(Foo::class, function ($app) {
return new Foo(config('services.foo.apiKey2'));
});
I then have to also resolve it somehow:
$fooV1 = app(Foo::class); // ?
$fooV2 = app(Foo::class); // ?
What is the correct way of writing and resolving 2 different singleton implementations of an instance?
Update
One solution I tried is as follows:
$this->app->singleton(Foo::class, function ($app, $parameters) {
dump('Creating...'); // For testing only to see is actually a singleton
$apiKey = $parameters[0] ? config('services.foo.apiKey1') : config('services.foo.apiKey2');
return new Foo($apiKey);
});
and then resolve like so:
$fooV1 = app(Foo::class, [true]);
$fooV2 = app(Foo::class, [false]);
The above also correctly outputs:
Creating...
Creating...
As this is 2 different singletons.
This works for the most part. However, the singleton aspect is not respected. i.e. when creating the same foo twice:
$aV1 = app(Foo::class, [true]);
$bV1 = app(Foo::class, [true]);
Outputs:
Creating...
Creating...
It should have only outputted Created... once in this case, as a Foo with the same set of parameters was already created, thus not being a singleton.
Binding A Singleton
$this->app->singleton('foo1', function ($app) {
return new Foo(config('services.foo.apiKey1'));
});
$this->app->singleton('foo2', function ($app) {
return new Foo(config('services.foo.apiKey2'));
});
Instead of passing the Foo::class on the first parameter pass the name that you will be using to resolve that singleton you are creating
To resolve do the following
//a new instance of Foo is created
$foo1 = $this->app->make('foo1');
//the same instance created before is returned
$foo2 = $this->app->make('foo2');
Let me know if i helped
I'm trying to use zend expressive nested application, so I'm following this blog post :
https://framework.zend.com/blog/2017-03-15-nested-middleware-in-expressive.html
The problem seems to be in the Middleware factory:
class CreateBookMiddlewareFactory
{
public function __invoke(ContainerInterface $container)
{
$nested = new Application(
$container->get(RouterInterface::class),
$container
);
$nested->pipe(AuthenticationMiddleware::class);
$nested->pipe(ContentValidationMiddleware::class);
$nested->pipe(BodyParamsMiddleware::class);
$nested->pipe(BookValidationMiddleware::class);
$nested->pipe(CreateBookMiddleware::class);
return $nested;
}
}
I don't get how CreateBookMiddleware could be added to the pipe here as we are in its Factory. So piping it will call the factory, create a new nested application, which will call the factory, which will create another nested application...
( ! ) Fatal error: Maximum function nesting level of '256' reached, aborting! in /var/www/project/vendor/zendframework/zend-stratigility/src/Next.php on line
158
Is there something I'm not getting right from this blog post?
You named the factory CreateBookMiddlewareFactory. And then inside __invoke you have $nested->pipe(CreateBookMiddleware::class);. It depends on your config, but usually CreateBookMiddlewareFactory would be the factory for CreateBookMiddleware. So it's stuck in a loop because it keeps creating itself.
As you have the exact same code as in the blogpost, I'm guessing it's an error in that blog post. I think it should have been like in the last delegator factory example: without the last $nested->pipe(CreateBookMiddleware::class);.
I've notified the author of the blog post.
Edit: The blog post is updated with this fix:
namespace Acme\Api;
use Acme\AuthenticationMiddleware;
use Acme\ContentNegotiationMiddleware;
use Psr\Container\ContainerInterface;
use Zend\Expressive\Application;
use Zend\Expressive\Helper\BodyParams\BodyParamsMiddleware;
use Zend\Expressive\Router\RouterInterface;
class CreateBookMiddlewareFactory
{
public function __invoke(ContainerInterface $container)
{
$nested = new Application(
$container->get(RouterInterface::class),
$container
);
$nested->pipe(AuthenticationMiddleware::class);
$nested->pipe(ContentValidationMiddleware::class);
$nested->pipe(BodyParamsMiddleware::class);
$nested->pipe(BookValidationMiddleware::class);
// If dependencies are needed, pull them from the container and pass
// them to the constructor:
$nested->pipe(new CreateBookMiddleware());
return $nested;
}
}
I accepted #xtreamwayz answer for the clarification. But here's how I made it work:
class CreateBookMiddlewareFactory
{
public function __invoke(ContainerInterface $container)
{
$nested = new Application(
$container->get(RouterInterface::class),
$container
);
$nested->pipe($container->get(AuthenticationMiddleware::class));
$nested->pipe($container->get(ContentValidationMiddleware::class));
$nested->pipe($container->get(BodyParamsMiddleware::class));
$nested->pipe($container->get(BookValidationMiddleware::class));
// instanciate the new class, so it will not call the factory again
$nested->pipe(new CreateBookMiddleware());
return $nested;
}
}
I have a class which is used to generate navigation from a variety of interconnected bundles. I have a Navigation service to accomplish this.
In order to connect this service with the other bits of Navigation, I want to allow the other bundles to define their own services which then listen to the event listener and add their navigation items at the proper time.
The problem is, I can't figure out how to have a service listen to an event without first calling that service manually in order to create it.
Any ideas?
To give a more concrete idea, I have something like this:
// Set up as a service in the bundle.
class Navigation {
// ...
protected $dispatcher; // event dispatcher passed in to service
// ...
public function generateNavigation() {
$items = array();
// add some items
$event = new NavigationEvent($items); // custom event
$this->eventDispatcher->dispatchEvent('navigation_event', $event);
}
}
// Set up as a service in some secondary bundle.
class NavigationWorker {
/**
* #param $dispatcher Same instance as Navigation
*/
public function __construct(EventDispatcher $dispatcher) {
$dispatcher->addListener('navigation_event', array($this, 'doSomething'));
}
}
With this set up, it should work if the NavigationWorker is called at some point and is constructed, but I can't always call them directly, so it is never constructed and the listener is never added.
The way I currently do it is to pass all of the NavigationWorkers to Navigation and have it add their listener, but this is very ugly.
See the Event Listener Documentation. Make NavigationWorker and event listener and it won't need to be explicitly constructed.
I'm changing the answer to this because while that set me on the right path, it wasn't the complete answer. That article really only allows you to hook in to pre-defined kernel events. I however needed my own, so I started working back from there.
In the end, I ended up creating my own tags, a compiler pass to process those tasks. I also added my own extension of EventDispatcher, though that wasn't super-necessary (you could just use the normal one).
Here is what the file solution looked like.
Configuration:
parameters:
my_bundle.navigation.event.class: My\Bundle\DependencyInjection\NavigationEvent
my_bundle.event_dispatcher.class: My\Bundle\DependencyInjection\EventDispatcher
my_bundle.navigation.class: My\Bundle\DependencyInjection\NavigationGenerator
my_bundle.navigation_listener1.class: My\Bundle\DependencyInjection\NavigationListener
my_bundle.navigation_listener2.class: My\Bundle\DependencyInjection\NavigationListener
services:
my_bundle.event_dispatcher:
class: %my_bundle.event_dispatcher.class%
my_bundle.navigation:
class: %my_bundle.navigation.class%
arguments:
- #my_bundle.event_dispatcher
my_bundle.navigation_listener1.class:
class: %my_bundle.navigation_listener1.class%
tags:
- { name: my_bundle.event_listener, event: my_bundle.navigation.generate, method: onGenerateNavigation }
my_bundle.navigation_listener2.class:
class: %my_bundle.navigation_listener2.class%
tags:
- { name: my_bundle.event_listener, event: my_bundle.navigation.generate, method: onGenerateNavigation }
CompilerPass:
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class EventListenerCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('my_bundle.event_dispatcher')) {
return;
}
$definition = $container->getDefinition(
'my_bundle.event_dispatcher'
);
$taggedServices = $container->findTaggedServiceIds(
'my_bundle.event_listener'
);
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
$definition->addMethodCall(
'addListener',
array($this->getEventString($attributes['event'], $container), array(new Reference($id), $attributes['method']))
);
}
}
}
protected function getEventString($str, ContainerBuilder $container)
{
preg_match('/(.*)\.([^.]*)$/', $str, $matches);
$parameterName = $matches[1];
$constName = strtoupper($matches[2]);
$eventClass = $container->getParameter($parameterName . '.event.class');
if (!$eventClass) {
throw new Exception('Unable to find parameter: ' . $eventClass . '.event.class');
}
// Return the value of the constant.
return constant($eventClass . '::' . $constName);
}
Add a function like this to your compiler class (something like MyBundleBundle).
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new EventListenerCompilerPass());
}
Now the EventListener will have added listeners for each of those events. You than just implement everything else exactly as you would expect (Navigation dispatches events which it listens too). You can than hook in new event listeners from any bundle, and they don't even need to share a common class/interface.
This also works for any custom event, as long as the object which has the constant for the event is registered in the parameters with ".event.class" at the end (so my_bundle.navigation.generate looks for the parameter my_bundle.navigation.event.class, uses that class and the constant GENERATE).
Hopefully that'll help anyone else looking to do something similar.