Laravel package custom migration command - php

I would like to make a package which uses a custom stub for creating migrations needed for my package. More precisely, running the command should make pivot tables for models which have a specific trait present.
If I make a "normal" command, I can register it within my service provider:
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
MakeContainerMigration::class,
]);
}
}
However in this case I wanted to reuse Laravel's coding and save myself the trouble to reinvent the wheel. So my command looks like this:
class MakeContainerMigration extends MigrateMakeCommand
{
protected $name = 'custom:make-container';
protected $description = '...';
}
Since MigrateMakeCommand doesn't have stubs defined, but rather it's dependency MigrationCreator, I needed to find a way to provide custom stub path to it without disrupting "regular" migration stubs.
I tried doing something like this but failed:
public function register()
{
$this->registerCreator();
$this->registerMigrateMakeCommand();
if ($this->app->runningInConsole()) {
$this->commands([
//MakeContainerMigration::class,
'custom.command.migrate.make'
]);
}
}
protected function registerCreator()
{
$this->app->singleton('custom.migration.creator', function ($app) {
return new MigrationCreator($app['files'], __DIR__ . '/stubs');
});
}
protected function registerMigrateMakeCommand()
{
$this->app->singleton('custom.command.migrate.make', function ($app) {
$creator = $app['custom.migration.creator'];
$composer = $app['composer'];
return new MakeContainerMigration($creator, $composer);
});
}
I'm aware that registering the command shouldn't function like this as I am simply registering singletons to Laravel app instance, but I have no idea how to register it through the class while ensuring that the right version of MigrationCreator will be injected. I'm kinda stuck here, is there a way to do it?

Turns out everything is working, I just needed to replace
protected $name = 'custom:make-container';
with
protected $signature = 'custom:make-container';

Related

How to get Symfony 4 dependency injection working with two different use case scenarios?

We're trying to find the best way to implement dependency injection in a Symfony project with a quite specific problematic.
At user level, our application rely on an "Account" doctrine entity which is loaded with the help of the HTTP_HOST global against a domain property (multi-domain application). Going on the domain example.domain.tld will load the matching entity and settings.
At the devops level, we also need to do batch work with CLI scripts on many accounts at the same time.
The question we are facing is how to write services that will be compatible with both needs?
Let's illustrate this with a simplified example. For the user level we have this and everything works great:
Controller/FileController.php
public function new(Request $request, FileManager $fileManager): Response
{
...
$fileManager->addFile($file);
...
}
Service/FileManager.php
public function __construct(AccountFactory $account)
{
$this->account = $account;
}
Service/AccountFactory.php
public function __construct(RequestStack $requestStack, AccountRepository $accountRepository)
{
$this->requestStack = $requestStack;
$this->accountRepository = $accountRepository;
}
public function createAccount()
{
$httpHost = $this->requestStack->getCurrentRequest()->server->get('HTTP_HOST');
$account = $this->accountRepository->findOneBy(['domain' => $httpHost]);
if (!$account) {
throw $this->createNotFoundException(sprintf('No matching account for given host %s', $httpHost));
}
return $account;
}
Now if we wanted to write the following console command, it would fail because the FileManager is only accepting an AccountFactory and not the Account Entity.
$accounts = $accountRepository->findAll();
foreach ($accounts as $account) {
$fileManager = new FileManager($account);
$fileManager->addFile($file);
}
We could tweak in the AccountFactory but this would feel wrong...
In reality this is even worse because the Account dependency is deeper in services.
Does anyone have an idea how to make this properly ?
As a good practice, you should create an interface for the FileManager and set this FileManagerInterface as your dependency injection (instead of FileManager).
Then, you can have different classes that follow the same interface rules but just have a different constructor.
With this approach you can implement something like:
Service/FileManager.php
interface FileManagerInterface
{
// declare the methods that must be implemented
public function FileManagerFunctionA();
public function FileManagerFunctionB(ParamType $paramX):ReturnType;
}
FileManagerInterface.php
class FileManagerBase implements FileManagerInterface
{
// implement the methods defined on the interface
public function FileManagerFunctionA()
{
//... code
}
public function FileManagerFunctionB(ParamType $paramX):ReturnType
{
//... code
}
}
FileManagerForFactory.php
class FileManagerForFactory implements FileManagerInterface
{
// implement the specific constructor for this implementation
public function __construct(AccountFactory $account)
{
// your code here using the account factory object
}
// additional code that is needed for this implementation and that is not on the base class
}
FileManagerAnother.php
class FileManagerForFactory implements FileManagerInterface
{
// implement the specific constructor for this implementation
public function __construct(AccountInterface $account)
{
// your code here using the account object
}
// additional code that is needed for this implementation and that is not on the base class
}
Ans last but not least:
Controller/FileController.php
public function new(Request $request, FileManagerInterface $fileManager): Response
{
// ... code using the file manager interface
}
Another approach that also looks correct is, assuming that FileManager depends on an AccountInstance to work, changes could be made to your FileManager dependency to have the AccountInstance as a dependency instead of the Factory. Just Because in fact, the FileManager does not need the factory, it needs the result that the factory generates, so, automatically it is not FileManager's responsibility to carry the entire Factory.
With this approach you will only have to change your declarations like:
Service/FileManager.php
public function __construct(AccountInterface $account)
{
$this->account = $account;
}
Service/AccountFactory.php
public function createAccount():AccountInterface
{
// ... your code
}

Laravel Target is not instantiable while building

I created an Artisan command which worked and where I injected a Kafka client service as the first parameter and a concrete class BudgetsTransformer, as the second parameter.
class ConsumeBudgetsCommand extends Command {
public function __construct(FKafka $kafkaClient, BudgetsTransformer $transformer)
{
$this->kafkaClient = $kafkaClient;
$this->transformer = $transformer;
parent::__construct();
}
}
AppServiceProvider class looked like:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('kafka.client', function ($app) {
return new \Weq\FKafka\FKafka();
});
$this->app->bind('budget.transformer', function ($app) {
return new BudgetsTransformer();
});
}
public function boot()
{
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], $app['budget.transformer']);
});
$this->commands('consume:budgets');
}
}
So far all is working properly. Then I decided to create a TransformerInterface which BudgedTransformer implements (and other future Transformers will implement it):
class BudgetsTransformer implements TransformerInterface
{
// ...
}
and I changed the signature in the command to inject the interface instead of the concrete class:
class ConsumeBudgetsCommand extends Command {
public function __construct(FKafka $kafkaClient, TransformerInterface $transformer)
{
$this->kafkaClient = $kafkaClient;
$this->transformer = $transformer;
parent::__construct();
}
}
But I get the following issue when I try to run some artisan command
In Container.php line 933:
Target [App\Transformers\TransformerInterface] is not instantiable while building [App\Console\Commands\ConsumeBudgetsCommand].
I run previously the issue the following artisan command just in case. cache:clear, clear-compiled, optimize and so on but no luck.
What I'm doing wrong? Should I bind the BudgetTransformer in a different way I'm doing now for passing and Interface instead of a concrete class?
I added:
$this->app->bind(TransformerInterface::class, BudgetsTransformer::class);
in AppServiceProvider::register() and I removed
$this->app->bind('budget.transformer', function ($app) {
return new BudgetsTransformer();
});
there, then I update in AppServiceProvider::boot() the command binding:
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], $app[TransformerInterface::class]);
});
But still not working, anyway this approach (even working) will not resolve the issue since when I want to add another different transformer implementation, let's say CostTransformer which implements TransformerInterface is gonna always inject BudgetTransformer. So reading the documentation in the link, I found that Contextual Binding could be the solution, so I substituted by:
$this->app
->when(ConsumeBudgetsCommand::class)
->needs(TransformerInterface::class)
->give(function ($app) {
return new BudgetsTransformer();
});
So in that way, I will be able to inject different implementations of transformers to different commands by injecting the interface. But still not working.
Can someone tell me how exactly declare the command binding
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], ???);
});
to use that Contextual binding?
For binding interfaces must be use this structure https://laravel.com/docs/5.5/container#binding-interfaces-to-implementations
$this->app->bind(TransformerInterface::class, BudgetsTransformer::class);
And
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], $app->make(TransformerInterface::class));
});

Laravel singleton not working across controller/ViewComposer

In Laravel, I have a class that I would like to make available to the service controller, make some changes to in the controller action, and then render out with a ViewComposer.
I have done this several times before without issue, but for some reason this time my usual approach is not working - clearly I'm doing something different, and I'm beginning to suspect I've fundamentally misunderstood an aspect of what I am doing.
I have a ServiceProvider with this register() method:
public function register()
{
$this->app->singleton(HelperTest::class, function ($app) {
$pb = new HelperTest();
$pb->test = "jokes on you batman";
return $pb;
});
}
Then in my controller I'm doing the following:
private $helper;
public function __construct(HelperTest $pb)
{
$this->helper = $pb;
$this->helper->test = "hahah";
}
And then I have a viewcomposer doing the following:
private $helper;
public function __construct(HelperTest $pb)
{
$this->helper = $pb;
}
public function compose(View $view)
{
$view->with('output', $this->helper->test);
}
When I call {{ $output }} in the blade view, I expect to see hahah, but instead I get jokes on you batman.
My debugging has shown that all three of these methods are definitely being called. It looks to me like the ViewComposer is for some reason instantiating its own, fresh instance of the class. What am I doing wrong?
Thanks!
Execute php artisan optimize on your console, this will generate an optimized class loader for your application, then check if you can find your class HelperTest registered in services.php inside boostrap/cache. Until HelperTest is not registered there, Laravel IoC can't resolve your class.

How to create Laravel 5.1 Custom Authentication driver?

I am working in Laravel authentication login using socialite. Now I can able to save data of user from socialite. But now I am facing problem how to authenticate user from gmail, github.
After some research I understood that I need to create custom authentication. I googled but all are Laravel 4.1 topics. If any one work on this please provide your answers.
I already read following topics but I didn't got how to do it?
http://laravel.com/docs/5.1/authentication#social-authentication
http://laravel.com/docs/5.1/providers
http://laravel-recipes.com/recipes/115/using-your-own-authentication-driver
http://laravel.io/forum/11-04-2014-laravel-5-how-do-i-create-a-custom-auth-in-laravel-5
Update
public function handleProviderCallback() {
$user = Socialite::with('github')->user();
$email=$user->email;
$user_id=$user->id;
//$authUser = User::where('user_id',$user_id)->where('email', $email)->first();
$authUser = $this->findOrCreateUser($user);
if(Auth::login($authUser, true)) {
return Redirect::to('user/UserDashboard');
}
}
private function findOrCreateUser($user) {
if ($authUser = User::where('user_id',$user->id)->first()) {
return $authUser;
}
return User::create([
'user_id' => $user->id,
'name' => $user->nickname,
'email' => $user->email,
'avatar' => $user->avatar
]);
}
This answer is most suited for Laravel 5.1. Please take care if you
are in some other version. Also keep in mind that IMHO this is a rather advanced level in Laravel, and hence if you don't fully understand what you are doing, you may end up crashing your application. The solution is not end to end correct. This is just a general guideline of what you need to do in order for this to work.
Adding Custom Authentication Drivers In Laravel 5.1
Hint: Laravel documentation for this topic is here.
Hint2: The last link you mentioned is quite useful in my opinion. I learned all of this after reading that link.
http://laravel.io/forum/11-04-2014-laravel-5-how-do-i-create-a-custom-auth-in-laravel-5
Before we start, I would first like to describe the login flow which will help you understand the process. Laravel uses a driver to connect to the database to fetch your records. Two drivers come pre-bundled with laravel - eloquent & database. We want to create a third so that we can customize it to our needs.
Illuminate\Auth\Guard inside your vendor folder is the main file which has code for the user to log in and log out. And this file mainly uses two Contracts (or interfaces) that we need to override in order for our driver to work. From Laravel's own documentation read this:
The Illuminate\Contracts\Auth\UserProvider implementations are only
responsible for fetching a Illuminate\Contracts\Auth\Authenticatable
implementation out of a persistent storage system, such as MySQL,
Riak, etc. These two interfaces allow the Laravel authentication
mechanisms to continue functioning regardless of how the user data is
stored or what type of class is used to represent it.
So the idea is that for our driver to work we need to implement Illuminate\Contracts\Auth\UserProvider and Illuminate\Contracts\Auth\Authenticatable and tell Laravel to use these implementations instead of the defaults.
So let's begin.
Step 1:
Choose a name for your driver. I name mine socialite. Then in your config/auth.php, change the driver name to socialite. By doing this we just told laravel to use this driver for authentication instead of eloquent which is default.
Step 2:
In your app/Provider/AuthServiceProvider in the boot() method add the following lines:
Auth::extend('socialite', function($app) {
$provider = new SocialiteUserProvider();
return new AuthService($provider, App::make('session.store'));
});
What we did here is:
We first used Auth facade to define the socialite driver.
SocialiteUserProvider is an implementation of UserProvider.
AuthService is my extension of Guard class. The second parameter this class's constructor takes is the session which laravel uses to get and set sessions.
So we basically told Laravel to use our own implementation of Guard class instead of the default one.
Step 3:
Create SocialiteUserProvider. If you read the Laravel's documentation, you will understand what each of these methods should return. I have created the first method as a sample. As you can see, I use my UserService class to fetch results. You can fetch your own results however you want to fetch them. Then I created an User object out of it. This User class implements the Illuminate\Contracts\Auth\Authenticatable contract.
<?php
namespace App\Extensions;
use App\User;
use App\Services\UserService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
class SocialiteUserProvider implements UserProvider
{
private $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function retrieveById($identifier)
{
$result = $this->userService->getUserByEmail($identifier);
if(count($result) === 0)
{
$user = null;
}
else
{
$user = new User($result[0]);
}
return $user;
}
public function retrieveByToken($identifier, $token)
{
// Implement your own.
}
public function updateRememberToken(Authenticatable $user, $token)
{
// Implement your own.
}
public function retrieveByCredentials(array $credentials)
{
// Implement your own.
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
// Implement your own.
}
}
Step 4:
Create User class which implements the Authenticatable. This class has to implement this interface because the Guard class will use this class to get values.
<?php
namespace App;
use Illuminate\Contracts\Auth\Authenticatable;
class User implements Authenticatable
{
protected $primaryKey = 'userEmail';
protected $attributes = [];
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
public function getUserAttributes()
{
return $this->attributes;
}
public function getAuthIdentifier()
{
return $this->attributes[$this->primaryKey];
}
public function getAuthPassword()
{
// Implement your own.
}
public function getRememberToken()
{
// Implement your own.
}
public function setRememberToken($value)
{
// Implement your own.
}
public function getRememberTokenName()
{
// Implement your own.
}
}
Step 5:
Finally create the AuthService class that will call the Guard methods. This is my own implementation. You can write your own as per your needs. What we have done here is extended the Guard class to implement two new functions which are self explanatory.
<?php
namespace App\Services;
use Illuminate\Auth\Guard;
class AuthService extends Guard
{
public function signin($email)
{
$credentials = array('email' => $email);
$this->fireAttemptEvent($credentials, false, true);
$this->lastAttempted = $user = $this->provider->retrieveById($email);
if($user !== null)
{
$this->login($user, false);
return true;
}
else
{
return false;
}
}
public function signout()
{
$this->clearUserDataFromStorage();
if(isset($this->events))
{
$this->events->fire('auth.logout', [$this->user()]);
}
$this->user = null;
$this->loggedOut = true;
}
}
Step 6: Bonus Step
Just to complete my answer, I will also explain the structure that UserService class expects. First lets understand what this class does. In our above steps we created everything to let laravel know how to use our authentication driver, instead of theirs. But we still haven't told laravel that how should it get the data. All we told laravel that if you call the userService->getUserByEmail($email) method, you will get your data. So now we simply have to implement this function.
E.g.1 You are using Eloquent.
public function getUserByEmail($email)
{
return UserModel::where('email', $email)->get();
}
E.g.2 You are using Fluent.
public function getUserByEmail($email)
{
return DB::table('myusertable')->where('email', '=', $email)->get();
}
Update: 19 Jun 2016
Thank you #skittles for pointing out that I have not clearly shown where the files should be placed. All the files are to be placed as per the namespace given. E.g. if the namespace is App\Extensions and the class name is SocialiteUserProvider then location of file is App\Extensions\SocialiteUserProvider.php. The App directory in laravel is the app folder.
Good tutorial for setting up laravel socialite here: https://mattstauffer.co/blog/using-github-authentication-for-login-with-laravel-socialite
Auth::login doesn't return a boolean value you can use attempt to do a Auth::attempt
if(Auth::login($authUser, true)) {
return Redirect::to('user/UserDashboard');
}
Follow the tutorial and do this, and just have middleware configured on the home route
$authUser = $this->findOrCreateUser($user);
Auth::login($authUser, true);
return Redirect::to('home');

How can I extend class Confide in Laravel 4?

I want to extend/overwrite the method logAttempt in class Confide (Confide on GitHub) in order to execute some extra code whenever someone logs in successfully. This would be cleaner than copying the same code to all controllers where logAttempt is called.
I read through the Laravel documentation and several answers here on stackoverflow, but I just can't get it working.
I created a new folder app/extensions with a file named Confide.php:
<?php
namespace Extensions;
class Confide extends \Zizaco\Confide\Confide {
public function __construct(ConfideRepository $repo) {
die('no way!');
$this->repo = $repo;
$this->app = app();
}
public function logAttempt($credentials, $confirmed_only = false, $identity_columns = array()) {
die('yeah man!');
}
}
I added the directory to my app/start/global.php:
ClassLoader::addDirectories(array(
// ...
app_path().'/extensions',
));
I also added it to composer.json and ran composer dump-autoload:
"autoload": {
"classmap": [
...,
"app/extensions"
]
},
My own Confide class seems not to be loaded at all, because Confide works as normal – without ever die()-ing.
And if I use \Extensions\Confide::logAttempt($input, true); in my controller including the namespace, I get this ErrorException:
Non-static method Extensions\Confide::logAttempt() should not be called statically, assuming $this from incompatible context
Do I really need my own ConfideServiceProvider class as well? I tried that, too, but I'm not sure at all what to put in there to make Confide use my extended class.
Is there no simple way to extend a tiny bit of a class? There must be, I'm just missing something here.
If you are looking to execute some code when a user logs in, you should just listen for that event. In this case, I believe Confide uses the Auth class to login, so you should be able to listen for that event.
Event::listen('auth.login', function($user)
{
$user->last_login = new DateTime;
$user->save();
});
I find this much easier and cleaner than worrying about extending classes.
EDIT: Made a mistake
I think you need to call the method like this:
\Extensions\Confide->logAttempt($input, true);
because you are using:
\Extensions\Confide::logAttempt($input, true);
Which is how you call static methods.
I think I finally figured it out.
I had to extend ConfideServiceProvider as well like so:
<?php
namespace Extensions;
class ConfideServiceProvider extends \Zizaco\Confide\ConfideServiceProvider {
/**
* Bootstrap the service provider.
*
* #return void
*/
public function boot() {
$this->package('extensions/confide');
}
/**
* Register the application bindings.
*
* #return void
*/
protected function registerConfide() {
$this->app->bind('confide', function($app) {
return new Confide($app->make('confide.repository'));
});
}
}
The code above goes into app/extensions/ConfideServiceProvider.php. Note: In boot() I replaced "zizaco" with "extensions" and in registerConfide() I made no changes at all, but if this method is not present in the extended class, the original class will be used. I've got no idea why.
Then in app/config/app.php I replaced Zizaco\Confide\ConfideServiceProvider with Extensions\ConfideServiceProvider.
My own extended Confide class looks like this now:
<?php
namespace Extensions;
class Confide extends \Zizaco\Confide\Confide {
public function logAttempt($credentials, $confirmed_only = false, $identity_columns = array()) {
$result = parent::logAttempt($credentials, $confirmed_only, $identity_columns);
if ($result) {
// Login successful. Do some additional stuff.
\Log::info('User ' . \Auth::user()->username . ' logged in.');
}
return $result;
}
}
Note: If you want to use any other standard Laravel class like Log, Session etc., prefix it with one backslash as shown in the example above, or add a use operator for each class you use (e.g. use \Log;).

Categories