Laravel 5.3 - Best way to implement Entrust role on signup? - laravel

I'm working with Laravel 5.3 and I'm trying to set a role when someone signs up, I've used the Zizaco Entrust library.
I'm unsure on the best way to achieve something like this.
I tried to do this inside RegisterController's create method like below:
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
$user = User::where('email', '=', $data['email'])->first();
// role attach alias
$user->attachRole($employee);
}
But obviously that's not right. So I'm a bit unsure on what the best practice is with this sort of thing.

If, as your comment on the OP suggests, you always want to assign the same role to a registered user, you can use a Model Observer for this - it's really simple.
// app/Observers/UserObserver.php
<?php namespace App\Observers;
use App\Models\User;
use App\Models\Role; // or the namespace to the Zizaco Role class
class UserObserver {
public function created( User $user ) {
$role = Role::find( 1 ); // or any other way of getting a role
$user->attachRole( $role );
}
Then you simply register the observer in your AppServiceProvider:
// app/Providers/AppServiceProvider.php
use App\Models\User;
use App\Observers\UserObserver;
class AppServiceProvider extends Provider {
public function boot() {
User::observe( new UserObserver );
// ...
}
// ...
}

This answer is mainly based off looking at your current solution, with a dash of original question.
Rather than filling out your model with methods like createNew, you'll probably find things easier to manage if you create a type of class specifically for interacting with models. You can call this a Repository or a Service or whatever takes your fancy, but we'll run with Service.
// app/Services/UserService.php
<?php namespace App\Services;
use App\Models\User; // or wherever your User model is
class UserService {
public function __construct( User $user ) {
$this->user = $user;
}
public function create( array $attributes, $role = null ) {
$user = $this->user->create( $attributes );
if ( $role ) {
$user->attachRole( $role );
}
return $user;
}
}
Now we need to deal with the fact that we've lost the hashing of passwords:
// app/Models/User.php
class User ... {
public function setPasswordAttribute( $password ) {
$this->attributes[ 'password' ] = bcrypt( $password );
}
}
And now we have the problem of sending out an activation email - that can be solved cleanly with events. Run this in the terminal:
php artisan make:event UserHasRegistered
and it should look something like this:
// app/Events/UserHasRegistered.php
<?php namespace App\Events;
use App\Models\User;
use Illuminate\Queue\SerializesModels;
class UserHasRegistered extends Event {
use SerializesModels;
public $user;
public function __construct( User $user ) {
$this->user = $user;
}
}
Now we need a listener for the event:
php artisan make:listener SendUserWelcomeEmail
And this can be as complex as you like, here's one I'm just copy/pasting from a project I have lying around:
// app/Listeners/SendUserWelcomeEmail.php
<?php namespace App\Listeners;
use App\Events\UserHasRegistered;
use App\Services\NotificationService;
class SendUserWelcomeEmail {
protected $notificationService;
public function __construct( NotificationService $notificationService ) {
$this->notify = $notificationService;
}
public function handle( UserHasRegistered $event ) {
$this->notify
->byEmail( $event->user->email, 'Welcome to the site', 'welcome-user' )
->send();
}
}
All that remains is to tell Laravel that the Event and Listener we've just created are related, then to fire the event.
// app/Providers/EventServiceProvider.php
use App\Events\UserHasRegistered;
use App\Listeners\SendUserWelcomeEmail;
class EventServiceProvider extends ServiceProvider {
// find this array near the top, and add this in
protected $listen = [
UserHasRegistered::class => [
SendUserWelcomeEmail::class,
],
];
// ...
}
Now we just need to raise the event - see my other post about Model Observers. First off you'll need to import Event and App\Events\UserHasRegistered, then in your created method, just call Event::fire( new UserHasRegistered( $user ) ).

What I ended up doing, since I do need to do more than just one operation on the user creation is having another function for user creations.
User model
/**
* Create a new user instance after a valid registration.
*
* #param array $attributes
* #param null $role
* #param bool $send_activation_email
*
* #return User $user
*
* #internal param array $args
*/
public function createNew(array $attributes, $role = null, $send_activation_email = true)
{
$this->name = $attributes['name'];
$this->company_id = $attributes['company_id'];
$this->email = $attributes['email'];
$this->password = bcrypt($attributes['password']);
$this->save();
if (isset($role)) {
// Assigning the role to the new user
$this->attachRole($role);
}
//If the activation email flag is ok, we send the email
if ($send_activation_email) {
$this->sendAccountActivationEmail();
}
return $this;
}
and calling it like:
User Controller
$user = new User();
$user->createNew($request->all(), $request->role);
It might not be the best solution, but it does the job, and it's future prof, so if the logic on the user creation grows can be implemented aswell.

Related

Gate Define not working for other users except whose role_id is 1

The Below code in middleware is working fine when user has role_id 1, When I did the dd on role->permissions I get then the response is array.
But on this middleware line
if (in_array($role->permissions, $permission)) {
I get this error for all other users whose role_id is different
in_array(): Argument #2 ($haystack) must be of type array, string given where array was passed
My Roles Model has
protected $casts = [
'permissions' => 'array',
];
My User Model has
protected function role()
{
return $this->hasOne(Roles::class, 'id', 'role_id');
}
My web Middlewaregroup has
\App\Http\Middleware\RolePermissionCheck::class,
My Meddlware has
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
class RolePermissionCheck
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (!empty(Auth::user()->role_id)) {
$role = Auth::user()->role;
Gate::before(
function () {
if (Auth::user()->role_id === 1) {
return true;
}
}
);
// dd($role->permissions);
foreach ($role->permissions as $permission) {
Gate::define(
$permission,
function ($role) use ($permission) {
if (in_array($role->permissions, $permission)) {
return true;
}
}
);
}
}
return $next($request);
}
}
Do not mark my answer as correct as user Autista_z told you the fix, I am going to share something to have a better code, really simple and "Laravel way" stuff to do.
As user Autista_z said: "the problem will be in foreach loop for Gate defining. In first iteration of loop (based on your sample array) you would have $action = 0 and $roles = 'admin_role_manage'. So the name of gate would be 0. So of course, then #can('admin_role_manage') is false".
So, You are setting up or getting a lot of stuff that is not needed, or worded other way, you can have a clearer code (at least for your Middleware class).
If you know (maybe you don't), you can cast models properties to a type you want, so instead of doing json_decode($user_role->permissions) you can simply do foreach ($user_role->permissions as $role), but before you do so you need to cast it.
So your model would have a property $casts as this:
protected $casts = [
'permissions' => 'array',
];
This will allow you to do $model->permissions = ['role_1', 'role_2', ...]; without the need of doing $model->permissions = json_encode(['role_1', 'role_2', ...]); (and also this is the Laravel way of handling this).
So your middleware would end like:
namespace App\Http\Middleware;
use App\Models\User;
use App\Models\Roles;
use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
class RolePermissionCheck
{
public function handle($request, Closure $next)
{
if ($role = Auth::user()->role)) {
Gate::before(
function (User $user) {
if ($user->role_id === '1') {
return true;
}
}
);
foreach ($role->permissions as $permission) {
Gate::define(
$permission,
function ($role) use ($permission) {
if (in_array($permission, $role->permissions)) {
return true;
}
}
);
}
}
return $next($request);
}
}
See that I have changed the wording of $role->permissions as $roles to $role->permissions as $permission, your role could be "Writer" and your permissions are newsletter_manage, brand_logos, quote_manage, and more. So this are not roles but permissions.
Also have in mind that I think you can even have better code for the foreach/define part.

Laravel: Contract file is not instantiable while building Controller

I am trying to add a shopping cart function to my Laravel application. I installed darryldecode/laravelshoppingcart package from GitHub and have been following instructions in these two websites.
TECHPOOL-Create a Shopping Cart with Laravel 6
LARASHOUT-Laravel E-Commerce Application Development – Checkout
I was able to create most of the shopping cart function with the first website but it didn't cover checkouts and placing orders so I found the second website.
The problem is that the contract file is not working. Here is the error I got.
Illuminate\Contracts\Container\BindingResolutionException
Target [App\Contracts\OrderContract] is not instantiable while building [App\Http\Controllers\CheckoutController].
http://localhost:8000/checkout
Where I use the contract file is in the checkout process witch is explained in the second website. I made few changes in the codes so that it will be consistent with the first website but mostly I followed what the website says.
Here are the codes that are mentioned in the error.
OrderContract.php
<?php
namespace App\Contracts;
interface OrderContract
{
public function storeOrderDetails($params);
}
CheckoutController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Contracts\OrderContract;
use App\Http\Controllers\Controller;
class CheckoutController extends Controller
{
protected $orderRepository;
public function __construct(OrderContract $orderRepository)
{
$this->orderRepository = $orderRepository;
}
public function getCheckout()
{
return view('checkout');
}
public function placeOrder(Request $request)
{
// Before storing the order we should implement the
// request validation which I leave it to you
$order = $this->orderRepository->storeOrderDetails($request->all());
dd($order);
}
}
OrderRepository.php
<?php
namespace App\Repositories;
use Cart;
use App\Models\Order;
use App\Product;
use App\Models\OrderItem;
use App\Contracts\OrderContract;
class OrderRepository extends BaseRepository implements OrderContract
{
public function __construct(Order $model)
{
parent::__construct($model);
$this->model = $model;
}
public function storeOrderDetails($params)
{
$order = Order::create([
'order_number' => 'ORD-' . strtoupper(uniqid()),
'status' => 'pending',
'grand_total' => Cart::getSubTotal(),
'item_count' => Cart::getTotalQuantity(),
'table_number' => $params['table_number'],
'name' => $params['name'],
'notes' => $params['notes']
]);
if ($order) {
$items = Cart::getContent();
foreach ($items as $item) {
// A better way will be to bring the product id with the cart items
// you can explore the package documentation to send product id with the cart
$product = Product::where('name', $item->name)->first();
$orderItem = new OrderItem([
'product_id' => $product->id,
'quantity' => $item->quantity,
'price' => $item->getPriceSum()
]);
$order->items()->save($orderItem);
}
}
return $order;
}
}
RepositoryServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Contracts\OrderContract;
use App\Repositories\OrderRepository;
class RepositoryServiceProvider extends ServiceProvider
{
protected $repositories = [
OrderContract::class => OrderRepository::class,
];
/**
* Register services.
*
* #return void
*/
public function register()
{
foreach ($this->repositories as $interface => $implementation) {
$this->app->bind($interface, $implementation);
}
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
}
I'm not really familiar with the contract concept since I only started learning Laravel recently and I'm completely lost here. Maybe the problem is that I haven't created another file that is necessary or maybe something else.
Any help would be appreciated as I have tried multiple methods with no success.
Thank you in advance.
Yes, this is expected...your contract should point to a Solid class else it's going to fail while trying to resolve it out of the container. So this is what you should do:
Create a class that implements that trait.
Go to your AppServiceProvider and bind it to that contract like this:
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
$this->app->bind(\App\Contracts\OrderContract::class, App\Repositories\ClassImplementingOrderContract::class);
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
}
this should fix your problem.
This error could caused even if you have forget to add "RepositoryServiceProvider" into "config/app.php" 's serviceProviders array.

Use Auth in AppServiceProvider

I need the ID of the user who is logged in to get a photo in the profile table, here I am trying to use View but only in the index function that gets $profile, I want all files in the view to have $profile
public function index(){
$profil = Profil_user::where('user_id',$auth)->first();
View::share('profil', $profil);
return view('user.index');
}
I have also tried AppServiceProvider but I get an error in the form of a null value if I don't log in, is there a solution to my problem?
public function boot(){
$auth = Auth::user();
dd($auth);
}
exist several way to pass a variable to all views. I explain some ways.
1. use middleware for all routes that you need to pass variable to those:
create middleware (I named it RootMiddleware)
php artisan make:middleware RootMiddleware
go to app/Http/Middleware/RootMiddleware.php and do following example code:
public function handle($request, Closure $next) {
if(auth()->check()) {
$authUser = auth()->user();
$profil = Profil_user::where('user_id',$authUser->id)->first();
view()->share([
'profil', $profil
]);
}
return $next($request);
}
then must register this middleware in app/Http/Kernel.php and put this line 'root' => RootMiddleware::class, to protected $routeMiddleware array.
then use this middleware of routes or routes group, for example:
Route::group(['middleware' => 'root'], function (){
// your routes that need to $profil, of course it can be used for all routers(because in handle function in RootMiddleware you set if
});
or set for single root:
Route::get('/profile', 'ProfileController#profile')->name('profile')->middleware('RootMiddleware');
2. other way that you pass variable to all views with view composer
go to app/Http and create Composers folder and inside it create ProfileComposer.php, inside ProfileComposer.php like this:
<?php
namespace App\Http\View\Composers;
use Illuminate\View\View;
class ProfileComposer
{
public function __construct()
{
}
public function compose(View $view)
{
$profil = Profil_user::where('user_id', auth()->id)->first();
$view->with([
'profil' => $profil
]);
}
}
now it's time create your service provider class, I named it ComposerServiceProvider
write this command in terminal : php artisan make:provider ComposerServiceProvider
after get Provider created successfully. message go to config/app.php and register your provider with put this \App\Providers\ComposerServiceProvider::class to providers array.
now go to app/Providers/ComposerServiceProvider.php and do like following:
namespace App\Providers;
use App\Http\View\Composers\ProfileComposer;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
View::composer(
'*' , ProfileComposer::class // is better in your case use write your views that want to send $profil variable to those
);
/* for certain some view */
//View::composer(
// ['profile', 'dashboard'] , ProfileComposer::class
//);
/* for single view */
//View::composer(
// 'app.user.profile' , ProfileComposer::class
//);
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
}
}
3. is possible that without create a service provider share your variable in AppServiceProvider, go to app/Provider/AppServiceProvider.php and do as follows:
// Using class based composers...
View::composer(
'profile', 'App\Http\View\Composers\ProfileComposer'
);
// Using Closure based composers...
View::composer('dashboard', function ($view) {
//
});
I hope be useful
you can use this
view()->composer('*', function($view)
{
if (Auth::check()) {
$view->with('currentUser', Auth::user());
}else {
$view->with('currentUser', null);
}
});

Is it a good practice to add custom method on Laravel Model class to insert record in another table?

I am following a tutorial to create a referal system in Laravel. In the tutorial it was not shown how to implement the addCredit() method of the user model class. I am a bit confuse. Assuming I have another table to keep the record of credits like :
user_credits
------------
user_id
credits
Is it good practice to write the code on user model's addCredits method to update the user_credits table? What will be the best in this case?
class User extends Authenticatable
{
/**
* Add bonus to the user
*/
public function addCredits($credit) {
//
}
}
The listener class to handle addition of the bonus for both the users.
namespace App\Listeners;
use App\Events\UserReferred;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class RewardUser
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param UserReferred $event
* #return void
*/
public function handle(UserReferred $event)
{
$referral = \App\ReferralLink::find($event->referralId);
if (!is_null($referral)) {
\App\ReferralRelationship::create(['referral_link_id' => $referral->id, 'user_id' => $event->user->id]);
if ($referral->program->name === 'Sign-up Bonus') {
// User who was sharing link
$provider = $referral->user;
// add credits to provider
$provider->addCredits(15);
// User who used the link
$user = $event->user;
$user->addCredits(20);
}
}
}
}
I'm not pretty sure, is it good practice or not, but i prefer abstract such things into a standalone service.
In your case it would be something like that:
CreditService
namespace App\Services;
use App\User;
class CreditService
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function addCredits($credits)
{
$this->user->credits += $credits;
$this->user->save();
}
}
Then in controller/listener you can work with this service
use App\Services\CreditService;
...
public function handle(UserReferred $event)
{
$referral = \App\ReferralLink::find($event->referralId);
if ( !is_null($referral) ) {
\App\ReferralRelationship::create([
'referral_link_id' => $referral->id,
'user_id' => $event->user->id,
]);
if ( $referral->program->name === 'Sign-up Bonus' ) {
(new CreditService($referral->user))->addCredits(15);
(new CreditService($event->user))->addCredits(20);
}
}
}
The way how you make and then use service might be different. So, if you don't want work via constructors, you can write static class and pass User into method directly.
I often put some additional actions into services. For example, fire events when i need to do it. Or log some things.

Argument 1 passed to Illuminate\Auth\Guard::login() must implement interface Illuminate\Auth\UserInterface, null given open:

I have facebook login which uses socialite library. The error in the question occurs when the callback occurs.
Here is my "USER" model
<?php
namespace App;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
class User extends Model implements Authenticatable
{
//use Illuminate\Contracts\Auth\Authenticatable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
use \Illuminate\Auth\Authenticatable;
public function posts()
{
return $this->hasMany('App\Post');
}
public function likes()
{
return $this->hasMany('App\Like');
}
}
The Socialite logins are handled by SocialAuthController and what i understood from the error is , auth()->login($user); , null is passed to the login("NULL"). Here is the code of SocialAuthController. What's the mistake i have made here and how to fix this. thanks in advance
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Socialite;
use App\SocialAccountService;
class SocialAuthController extends Controller
{
public function redirect($provider)
{
return Socialite::driver($provider)->redirect();
}
use \Illuminate\Auth\Authenticatable;
public function callback(SocialAccountService $service , $provider)
{
$user = $service->createOrGetUser(Socialite::driver($provider));
auth()->login($user);
return redirect()->to('/home');
}
}
The below is the handling service that will try to register user or log in if account already exists.
Here is the code of SocialAccountService.php
<?php
namespace App;
use Laravel\Socialite\Contracts\Provider;
class SocialAccountService
{
public function createOrGetUser(Provider $provider)
{
$providerUser = $provider->user();
$providerName = class_basename($provider);
$account = SocialAccount::whereProvider($providerName)
->whereProviderUserId($providerUser->getId())
->first();
if ($account) {
return $account->user;
} else {
$account = new SocialAccount([
'provider_user_id' => $providerUser->getId(),
'provider' => $providerName
]);
$user = User::whereEmail($providerUser->getEmail())->first();
if (!$user) {
$user = User::create([
'email' => $providerUser->getEmail(),
'name' => $providerUser->getName(),
]);
}
$account->user()->associate($user);
$account->save();
return $user;
}
}
}
This will try to find provider's account in the system and if it is not present it will create new user. This method will also try to associate social account with the email address in case that user already has an account.
My wild guess is that createOrGetUser() returns NULL because the SocialAccount does not have a user. So what could do is change the if condition in that method to check if the $account has a user:
public function createOrGetUser(Provider $provider)
{
...
if ( $account && property_exists($account, 'user') && $account->user ) {
return $account->user;
} else {
...

Resources