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';
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
}
I want to make some entries of Analysis publicly available. I tried to implement it with Policies but failed. I think it's because the AuthServiceProvider fails with AccessDeniedHttpException every time I try to access without an authorized user.
AuthServiceProvider
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
Analysis::class => AnalysisPolicy::class
];
public function boot()
{
$this->registerPolicies();
}
}
AnalysisPolicy
public function view(User $user, Analysis $analysis)
{
if($analysis->demo === true){
return true;
}
return $user->id === $analysis->user_id;
}
AnalysisController
public function show(int $analysis)
{
$ana = Analysis::find($analysis);
$this->authorize('view', $ana);
...
}
I tried to just create a new Service Provider, but that didn't work either as I cannot call the registerPolicies function without extending from AuthServiceProvider.
Basically, all I want is to now check for anything if the demo Attribute is true.
Edit:
My Quick-Fix form now is just checking in the controller if it's a demo. But that's not a great solution in my opinion as I think the goal with Policies should be that I don't have Access Management in the Controller. So I'd love to find a better solution.
if(!$ana->demo){
$this->authorize('view', $ana);
}
Ok so it turns out you should always read the release notes of the latest Laravel version before asking a question.
As of Laravel 5.7 there is a proper solution for this:
public function update(?User $user, Post $post)
{
return $user->id === $post->user_id;
}
(https://laravel.com/docs/5.7/authorization#writing-policies)
By declaring $user optional, it's null for guest users and can be handled in the policy.
I've been reading the documentation up and down now, still not sure what I'm doing wrong. In my opinion the documentation is very difficult to understand for a beginner.
Anyway, I'm trying to make something akin to the Auth::user() method, where it returns additional data about a logged in user that I will be needing for this application.
I have this helper class here:
namespace App\Helpers;
use Auth;
use Illuminate\Http\Request;
use App\Models\Grouping\User;
use App\Models\Grouping\Client;
use App\Models\Grouping\Rank;
class ClientUser {
public function __construct($request) {
$this->request = $request;
}
public function client() {
return Client::find($this->request->session()->get('client_id'));
}
public function auth() {
if (Auth::check()) {
// Get the client
$client = $this->client();
// Get the client's user
$user = $client->users()->find(Auth::user()['id']);
// Get the rank of the logged in user
$rank = Rank::find($user->pivot->rank_id);
return [
'user' => $user,
'rank' => $rank,
'client' => $client
];
}
return null;
}
}
This is responsible for doing what I described, returning additional data that I can't get through Auth::user(). Now I'm trying to register this class in the AuthServiceProvider
public function register()
{
// Register client auth
$request = $this->app->request;
$this->app->singleton(ClientUser::class, function ($app) {
return new ClientUser($request);
});
}
Now what I don't understand is how I'm supposed to make this globally accessible throughout my app like Auth::user() is.
The problem with just making "importing" it is that it needs the request object, which is why I'm passing it through the service container.
Now here's where I'm stuck. I'm not able to access app in my controller or anywhere, and I can't define a Facade because a Facade expects you to return a string of the bound service that it should "alias?"
Change your service provider like this :
$this->app->bind('client.user', function ($app) {
return new ClientUser($app->request);
});
Create another class extended from Illuminate\Support\Facades\Facade.
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class ClientUserFacade extends Facade {
public static function getFacadeAccessor(){
return "client.user";
}
}
Add 'ClientUser => ClientUserFacade::class in alias key of app.php
I have a table in my database with users. Their password are generated with my own custom hashing function.
How do i override the Authentication methods in laravel 4 to use my own hash class?
This is what I have been trying to do:
class CustomUserProvider implements Illuminate\Auth\UserProviderInterface {
public function retrieveByID($identifier)
{
return $this->createModel()->newQuery()->find($identifier);
}
public function retrieveByCredentials(array $credentials)
{
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->createModel()->newQuery();
foreach ($credentials as $key => $value)
{
if ( ! str_contains($key, 'password')) $query->where($key, $value);
}
return $query->first();
}
public function validateCredentials(Illuminate\Auth\UserInterface $user, array $credentials)
{
$plain = $credentials['password'];
return $this->hasher->check($plain, $user->getAuthPassword());
}
}
class CodeIgniter extends Illuminate\Auth\Guard {
}
App::bind('Illuminate\Auth\UserProviderInterface', 'CustomUserProvider');
Auth::extend('codeigniter', function()
{
return new CodeIgniter( App::make('CustomUserProvider'), App::make('session'));
});
When I run the Auth::attempt method I get this error:
ErrorException: Warning: Illegal offset type in isset or empty in G:\Dropbox\Workspaces\www\video\vendor\laravel\framework\src\Illuminate\Foundation\Application.php line 352
This is how ended up solving the problem:
libraries\CustomHasherServiceProvider.php
use Illuminate\Support\ServiceProvider;
class CustomHasherServiceProvider extends ServiceProvider {
public function register()
{
$this->app->bind('hash', function()
{
return new CustomHasher;
});
}
}
libraries\CustomHasher.php
class CustomHasher implements Illuminate\Hashing\HasherInterface {
private $NUMBER_OF_ROUNDS = '$5$rounds=7331$';
public function make($value, array $options = array())
{
$salt = uniqid();
$hash = crypt($password, $this->NUMBER_OF_ROUNDS . $salt);
return substr($hash, 15);
}
public function check($value, $hashedValue, array $options = array())
{
return $this->NUMBER_OF_ROUNDS . $hashedValue === crypt($value, $this->NUMBER_OF_ROUNDS . $hashedValue);
}
}
And then I replaced 'Illuminate\Hashing\HashServiceProvider' with 'CustomHasherServiceProvider' in the providers array in app/config/app.php
and added "app/libraries" to autoload classmap in composer.json
#vFragosop was on the right path with extending Auth.
There are a couple of ways to skin the cat and here is how I would do that without replacing the default Hasher class:
Include in your app/routes.php or wherever:
use Illuminate\Auth\Guard;
Auth::extend("eloquent", function() {
return new Guard(
new \Illuminate\Auth\EloquentUserProvider(new CustomHasher(), "User"),
App::make('session.store')
);
});
Create and autoload a CustomHasher class (i.e., app/libraries/CustomHasher.php):
class CustomHasher extends Illuminate\Hashing\BcryptHasher {
public function make($value, array $options = array())
{
...
}
public function check($value, $hashedValue, array $options = array())
{
...
}
}
That's it.
Warning: I can't ensure this is works out of the box and there may be a few gotchas here and there. Keep in mind Laravel 4 is still on development. Wish I could provide a more precise answer, but codebase is still going through many changes and not everything is properly documented. Anyway, you are looking for something like this:
// on config/auth.php
'driver' => 'custom'
// on start/global.php
Auth::extend('custom', function() {
// CustomUserProvider is your custom driver and should
// implement Illuminate\Auth\UserProviderInterface;
return new Guard(new CustomUserProvider, App::make('session'));
});
If this doesn't give you enough information to start, you should be able to figure it out by taking a look at those classes below:
EloquentUserProvider and DatabaseUserProvider
These classes are the currently supported authentication drivers. They should guide you on how to create your CustomUserProvider (or any name you like really).
Manager
This is the base class for anything that accepts custom drivers (including the AuthManager). It provides the methods for registering them like you do in Laravel 3.
This was the top result on Google, but these answers are insufficient for anyone on Laravel 5. Even the documentation doesn't suffice.
I've successfully replaced the Hasher for only the UserProvider. The rest of my application continues to use the very nice BcryptHasher, while user authentication uses a custom hasher. To do this, I had to study these answers, the documentation, and Laravel's source code itself. Here's what I found. Hopefully I can save someone else's full head of hair. Feel free to crosspost this to a question about Laravel 5.
First, create your custom hasher, if you haven't already. Place it wherever you'd like.
class MyCustomHasher implements Hasher {
public function make($value, array $options = []) {
return md5( $value ); // PLEASE DON'T USE MD5!
}
public function check($value, $hashedValue, array $options = []) {
if (strlen($hashedValue) === 0) {
return false;
}
return $hashedValue === $this->make($value);
}
public function needsRehash($hashedValue, array $options = []) {
return false;
}
}
Edit any registered ServiceProvider as follows...
class AppServiceProvider extends ServiceProvider {
public function boot() {
Auth::provider('eloquentCustom', function ($app, $config) {
return new EloquentUserProvider(new MyCustomHasher(), $config['model']);
});
}
}
You can replace 'eloquentCustom' with whatever you'd prefer.
Finally, edit your config/auth.php to use your custom provider. Here are the relevant parts...
return [
// ...
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
// ...
],
// ...
'providers' => [
'users' => [
'driver' => 'eloquentCustom', // <--- This is the only change
'model' => App\User::class,
],
// ...
],
// ...
];
Here's a little explanation, because I can't believe how obscure this was.
As you may expect, authentication is configured with config/auth.php. There are two key parts: Guards and Providers. I haven't yet bothered to learn exactly what guards do, but they seem to enforce authentication requirements. Providers are responsible for providing the necessary information to the guards. Therefore, a Guard requires a Provider. You can see that, in the default configuration, guards.web.provider is mapped to providers.users.
Laravel provides two implementations of UserProvider by default: EloquentUserProvider and DatabaseUserProvider. These correspond to the two possible values for providers.users.driver: eloquent and database, respectively. Normally, the eloquent option is chosen. EloquentUserProvider needs a Hasher, so Laravel gives it whatever the standard implementation is (ie. BcryptHasher). We override this behavior by creating our own "driver" for instantiating the Provider.
Auth is our friendly neighborhood facade. It is backed by the AuthManager. The often suggested Auth::extend() method expects a Guard (contrary to what the documentation might suggest). We have no need to mess with the Guard. Instead, we can use Auth::provider() which basically does the same thing as extend(), except it expects a Provider. So we provide a function to create our own instance of a EloquentUserProvider, giving it our custom Hasher (eg. MyCustomHasher). We also include a driver "name" that can be used in the config file.
Now back to the config file. That driver name that we just created is now a valid value for providers.users.driver. Set it there and you're good to go!
I hope this all makes sense and is useful for someone!