Laravel: dependency injection in commands - php

Is dependency injection of a custom class in a command possible?
I'm trying this:
<?php
namespace vendor\package\Commands;
use Illuminate\Console\Command;
use vendor\package\Models\Log;
use vendor\package\Updates\UpdateStatistics;
class UpdatePublishmentStats extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'vendorname:updatePublishmentStats';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Updates Twitter followers & Facebook page likes';
/**
* Contact implementation
* #var vendor\package\Update\UpdateStatistics
*/
protected $stats;
/**
* Create a new command instance.
*
* #return void
*/
public function __construct(
Log $log,
UpdateStatistics $stats
) {
parent::__construct();
$this->log = $log;
$this->stats = $stats;
}
But when I try to do this:
public function handle()
{
$this->stats->updateFbStats();
}
I suddenly get Segmentation fault: 11
When I delete the use vendor\package\Updates\UpdateStatistics; part, I don't get that error.
So what am I doing wrong here? Is it not possible to use dependency injection in a command?

You can inject any service in the handle method:
Note that we are able to inject any dependencies we need into the command's handle method.
Source: https://laravel.com/docs/5.8/artisan#command-structure

According to the Command Structure section of 5.2 documentation (https://laravel.com/docs/5.2/artisan#writing-commands):
"Note that we are able to inject any dependencies we need into the command's constructor. The Laravel service container will automatically inject all dependencies type-hinted in the constructor."
So I think you're good there, as far as the capability being present and available.
As for getting it to work, for me the segfault points to something wrong with the UpdateStats class, how it's referenced in the service container, or how its being resolved from the service container.
I don't have a definitive answer, but what I would do is try another class and see if I could localize the issue to this particular class, or if the problem happens with others, and then try and debug from there.
Also, if you just can't get that to work, the app() function will resolve items from the service container when you want (although looking through the 5.2 docs I don't see it anymore, so it may be deprecated - I do see $this->app->make() however).
This may work for you if nothing else does:
public function __construct(
Log $log,
) {
parent::__construct();
$this->log = $log;
$this->stats = app(UpdateStatistics::class);
}
My guess is, however, that you will get a segfault with this as well, as it should try resolving the same class the same way. If you do, then at least the error is a little clearer, and unrelated to auto-injecting feature.
Hope that at least helps a little.
Update on the app() function
So the app() function does not appear to be documented, but I have 5.2 installed right now and the helpers.php file in Illuminate/Foundation definitely has the function:
if (! function_exists('app')) {
/**
* Get the available container instance.
*
* #param string $make
* #param array $parameters
* #return mixed|\Illuminate\Foundation\Application
*/
function app($make = null, $parameters = [])
{
if (is_null($make)) {
return Container::getInstance();
}
return Container::getInstance()->make($make, $parameters);
}
}
Unfortunately the API documentation doesn't include any of the helper functions, but the current master, 5.2, and 5.3 versions of the file on Github all have the function:
https://github.com/laravel/framework/blob/master/src/Illuminate/Foundation/helpers.php#L91
https://github.com/laravel/framework/blob/5.3/src/Illuminate/Foundation/helpers.php#L91
https://github.com/laravel/framework/blob/5.2/src/Illuminate/Foundation/helpers.php#L91

Related

Weird issue with parameter validation on usage of interface implementation

So basically I'm not sure if this is a PhpStorm issue parsing my code or if its a weird quirk of PHP and interfaces but basically I have the following interface
<?php
namespace App\Contracts;
/**
* Interface IFileSource
* #package App\Contracts
*/
interface IFileSource
{
public function getFilesByPattern(string $filePattern) : array;
}
with the following implementation
<?php
namespace App\Sources;
use App\Contracts\IFileService;
use App\Services\File\FileService;
/**
* Class FileSource
* #package App\Sources
*/
class FileSource implements IFileSource
{
/**
* #var FileService
*/
private $fileService;
public function __construct (IFileService $fileService)
{
$this->fileService = $fileService;
}
/**
* #param string $filePattern
* #return File[]
* NOTE THIS ASSUMES FILESYSTEM
*/
public function getFilesByPattern (string $filePattern) : array
{
$filesDetails = $this->fileService->getFilesByPattern($filePattern);
return [];
}
}
and the usage
<?php
namespace App\Console\Commands;
use App\Contracts\IFileSource;
use App\Sources\FileSource;
class ImportXML extends Command
{
/**
* #var FileSource
*/
protected $fileSource;
public function __construct (IFileSource $fileSource)
{
parent::__construct();
$this->fileSource = $fileSource;
}
public function handle () : void
{
$filePattern = 'APATTERN';
$files = $this->fileSource->getFilesByPattern($filePattern)
}
}
My question relates to the usage of this implementation.
So the following is a valid usage:
$filePattern = 'APATTERN';
$this->fileSource->getFilesByPattern(filePattern)
But for some reason the following is also seen as a valid usage?
$filePattern = 'APATTERN';
$this->fileSource->getFilesByPattern(filePattern,filePattern,filePattern,filePattern,filePattern,filePattern,filePattern)
Why does it not care that i am not conforming to my implementation?
Why does it not care that i am not conforming to my implementation
That's the whole point of interfaces - they don't care about implementations. They only care about how the method is defined and if the signature conforms to the interface.
However, I think the real question being asked here is why the PHP interpreter doesn't throw an exception when multiple arguments are passed to the function. The answer is because this is how PHP implements overloading. They allow a variable number of arguments to be passed which you can access with functions such as func_get_args.
You should definitely read https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list and also look into the new(ish) splat operator ....
Similar QAs
How to pass variable number of arguments to a PHP function
https://softwareengineering.stackexchange.com/questions/165467/why-php-doesnt-support-function-overloading
So incase someone else stumbles across this,
thanks to LazyOne for the help explaining what i was doing wrong
it was due to the fact i am enforcing the implementation in the PHPdoc rather than relying on the interface to enforce the type hinting (or adding the interface as the type hint instead of the implementation), once i changed this it began complaining as expected.
Why enforce an implementation when the point of the interface is the enforce such things.
Doh

PhpStorm show usages of __invoke methods

I have problems detecting the use of a __invoke method in PhpStorm.
Example of class that is used with the __invoke php method:
class InitNewsletterSubscribedCustomerUseCase
{
/**
* #param CustomerId $id
* #throws CustomerIsValidatedException
*/
public function __invoke(CustomerId $id)
{
...
And I would like, as in all php methods, to know where it is used in the project wiht PhpStorm.
The variable knows the type, but PhpStorm does not know it knows that it executes that magic method "__invoke".
/** #var InitNewsletterSubscribedCustomerUseCase $useCase */
$useCase = $this->useCase;
try{
$useCase($customerId);
}
catch (CustomerIsNewsletterSubscribedException $ex)
Is there any special phpdoc or note for this?
PD: I use the 2018.3.3 version of PhpStorm.
Accordingly to WI-34223 ticket, it should be fixed in 2019.1 only (currently in EAP stage).
Try EAP build from https://www.jetbrains.com/phpstorm/eap/ page.

Laravel 5 dynamically run migrations

so I have created my own blog package in a structure of Packages/Sitemanager/Blog I have a service provider that looks like the following:
namespace Sitemanager\Blog;
use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
class BlogServiceProvider extends LaravelServiceProvider {
/**
* Indicates if loading of the provider is deferred.
*
* #var bool
*/
protected $defer = false;
/**
* Bootstrap the application events.
*
* #return void
*/
public function boot() {
$this->handleConfigs();
$this->handleMigrations();
$this->handleViews();
$this->handleRoutes();
}
/**
* Register the service provider.
*
* #return void
*/
public function register() {
// Bind any implementations.
$this->app->make('Sitemanager\Blog\Controllers\BlogController');
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides() {
return [];
}
private function handleConfigs() {
$configPath = __DIR__ . '/config/blog.php';
$this->publishes([$configPath => config_path('blog.php')]);
$this->mergeConfigFrom($configPath, 'blog');
}
private function handleTranslations() {
$this->loadTranslationsFrom(__DIR__.'/lang', 'blog');
}
private function handleViews() {
$this->loadViewsFrom(__DIR__.'/views', 'blog');
$this->publishes([__DIR__.'/views' => base_path('resources/views/vendor/blog')]);
}
private function handleMigrations() {
$this->publishes([__DIR__ . '/migrations' => base_path('database/migrations')]);
}
private function handleRoutes() {
include __DIR__.'/routes.php';
}
}
Now, what i would like to do is run the migrations dynamically if they have never been run before or within an installation process i suppose. I've seen in older documentation you could so something like this:
Artisan::call('migrate', array('--path' => 'app/migrations'));
However, this is invalid in laravel 5, how can I approach this?
Artisan::call('migrate', array('--path' => 'app/migrations'));
will work in Laravel 5, but you'll likely need to make a couple tweaks.
First, you need a use Artisan; line at the top of your file (where use Illuminate\Support\ServiceProvider... is), because of Laravel 5's namespacing. (You can alternatively do \Artisan::call - the \ is important).
You likely also need to do this:
Artisan::call('migrate', array('--path' => 'app/migrations', '--force' => true));
The --force is necessary because Laravel will, by default, prompt you for a yes/no in production, as it's a potentially destructive command. Without --force, your code will just sit there spinning its wheels (Laravel's waiting for a response from the CLI, but you're not in the CLI).
I'd encourage you to do this stuff somewhere other than the boot method of a service provider. These can be heavy calls (relying on both filesystem and database calls you don't want to make on every pageview). Consider an explicit installation console command or route instead.
After publishing the package:
php artisan vendor:publish --provider="Packages\Namespace\ServiceProvider"
You can execute the migration using:
php artisan migrate
Laravel automatically keeps track of which migrations have been executed and runs new ones accordingly.
If you want to execute the migration from outside of the CLI, for example in a route, you can do so using the Artisan facade:
Artisan::call('migrate')
You can pass optional parameters such as force and path as an array to the second argument in Artisan::call.
Further reading:
https://laravel.com/docs/5.1/artisan
https://laravel.com/docs/5.2/migrations#running-migrations
For the Laravel 7(and probably 6):
use Illuminate\Support\Facades\Artisan;
Artisan::call('migrate');
will greatly work.

Dependency Injecting the Laravel Cache System

Is there anyway to inject the laravel cache system and have access to the increment method.
\Cache::increment('key');
So far I have the following, but no increase method.
/**
* #var Cache\Repository
*/
protected $cache;
/**
* RateLimiter constructor.
*
* #param Cache\Repository $cache
*/
public function __construct(Cache\Repository $cache)
{
$this->cache = $cache;
}
I've tried injecting Cache\Store but that is not instantiable
EDIT:
use Illuminate\Contracts\Cache; is not show in above extract, but it's there.
Any suggestions???
According to the official docs the underline class of the Cache facade is Illuminate\Cache\Repository, not Cache\Repository

Attempting to bind Guzzle Curl Client to Laravel's Service Container -- then Type Hint the Client Fails when attempting to __construct()

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!

Categories