Laravel custom validation to all model - php

I am using laravel latest version and i have a common field in all model called slug.I would to check whether slug is unique or not. i have slug field in all tables
so i have extended Valdiator class
class CustomValidator extends Validator{
protected function validateIsUniqueSlug($attribute, $value, $parameters)
{
$isSlugExist= User::where('slug', $value)->exists();
if ($isSlugExist) {
return false;
}
return true;
}
}
this works but problem here is i need to repeat this for models but i dont want to do this .is there any better approach so i can handle it in one method
i know laravel has sluggable package but for some reason i cant use that package

If you using create cutom rules try this code
php artisan make rule command for create rule go to App\Rules dir
u can see passes function condition here
and use any model
'slug'=>[new SlugDomain], in validator
Rules file
public function passes($attribute, $value)
{
$isSlugExist= User::where('slug', $value)->exists();
if ($isSlugExist) {
return false;
}
return true;
}

Related

Spatie laravel/permission - Modifying table and methods

I'm working with Laravel 5.6 and Spatie laravel-permission and I want to modify the model_has_roles so it can have an extra field named code.
Now, i did that thing by modifying the migrations the library provides via the next command:
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"
This library provides a useful command:
$user->assignRole($role);
That lets me assign a role to an user within a single line of code. My problem is that, using this command will throw an error because i can't leave the code field empty. Either way, i can't do something like this:
$user->assignRole($role, ['code' => '0001']);
Because it will say that 0001 is not a role. Basically, it asumes that i'm trying to assign TWO different roles to the user: $role and 0001 while actually i'm trying to assign a single role: $role with an extra parameter for the table model_has_roles.
Is there a way to archieve such a thing? I thought that maybe i could modify the logic behind the assignRole method but i don't know how to extend/edit the library methods.
I see you have already added the column in the migration so now you need to make the changes in the assignRole method.
Spatie permission package works by adding the HasRoles trait in the User model class.
This is the full path of the trait-
use Spatie\Permission\Traits\HasRoles;
To achieve what you desire with the assignRole method, you can simply copy the content from HasRoles trait and then create your own trait from this code. You can see the method in this trait-
public function assignRole(...$roles)
{
$roles = collect($roles)
->flatten()
->map(function ($role) {
if (empty($role)) {
return false;
}
return $this->getStoredRole($role);
})
->filter(function ($role) {
return $role instanceof Role;
})
->each(function ($role) {
$this->ensureModelSharesGuard($role);
})
->map->id
->all();
$model = $this->getModel();
if ($model->exists) {
$this->roles()->sync($roles, false);
$model->load('roles');
} else {
$class = \get_class($model);
$class::saved(
function ($object) use ($roles, $model) {
static $modelLastFiredOn;
if ($modelLastFiredOn !== null && $modelLastFiredOn === $model) {
return;
}
$object->roles()->sync($roles, false);
$object->load('roles');
$modelLastFiredOn = $object;
});
}
$this->forgetCachedPermissions();
return $this;
}
Make your desired changes in this method and use your custom trait rather than using the spatie's HasRole trait.
Alternatively, you can also override the method from HasRoles trait in your class which is an easier and preferred way.

How to prevent save from a behavior in Yii2

I want to add a check before save in several of my Yii2 models.
In Yii1 this was simply a case of adding a behavior that had a beforeSave method, that returned false.
This doesn't work in Yii2. I can register a behavior that is called before save, but returning false from it doesn't prevent that save.
Anyone know how to achieve this without having to duplicate a beforeSave method with identical code in all my models?
namespace app\components\behaviors;
use yii\base\Behavior;
use yii\db\ActiveRecord;
class PreventSaveBehavior extends Behavior
{
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
];
}
public function beforeSave($insert)
{
if (SomeClass::shouldWePreventSave()) {
return false;
}
return parent::beforeSave($insert);
}
}
In Yii2 in behaviors you need to use isValid property of ModelEvent.
public function beforeSave($event)
{
if (SomeClass::shouldWePreventSave()) {
$event->isValid = false;
}
}
This is explained in event documentation.

Yii2. Adding attribute and rule dynamically to model

I am writing a widget and I want to avoid user adding code to their model (I know it would be easier but using it to learn something new).
Do you know if it is possible to add an attribute (which is not in your database, so it will be virtual) to a model and add a rule for that attribute?. You have no access to change that model code.
I know rules is an array. In the past I have merged rules from parent class using array_merge. Can it be done externally? Does Yii2 has a method for that?
An idea is to extend model provided by the user with a "model" inside my widget an there use:
public function init() {
/*Since it is extended this not even would be necessary,
I could declare the attribute as usual*/
$attribute = "categories";
$this->{$attribute} = null; //To create attribute on the fly
parent::init();
}
public function rules() {
$rules = [...];
//Then here merge parent rules with mine.
return array_merge(parent::rules, $rules);
}
But If I extend it, when I use that model in an ActiveForm in example for a checkbox, it will use my "CustomModel", so I want to avoid that. Any other ideas? How to do it without extending their model?
Add Dynamic Attributes to a existing Model
When you want to add dynamic attributes during runtime to a existing model. Then you need some custom code, you need: A Model-Class, and a extended class, which will do the dynamic part and which have array to hold the dynamic information. These array will merged in the needed function with the return arrays of the Model-Class.
Here is a kind of mockup, it's not fully working. But maybe you get an idea what you need to do:
class MyDynamicModel extends MyNoneDynamicModel
{
private $dynamicFields = [];
private $dynamicRules = [];
public function setDynamicFields($aryDynamics) {
$this->dynamicFields = $aryDynamics;
}
public function setDynamicRules($aryDynamics) {
$this->dynamicRules = $aryDynamics;
}
public function __get($name)
{
if (isset($this->dynamicFields[$name])) {
return $this->dynamicFields[$name];
}
return parent::__get($name);
}
public function __set($name, $value)
{
if (isset($this->dynamicFields[$name])) {
return $this->dynamicFields[$name] = $value;
}
return parent::__set($name, $value);
}
public function rules() {
return array_merge(parent::rules, $this->dynamicRules);
}
}
Full Dynamic Attributes
When all attributes are dynamic and you don't need a database. Then use the new DynamicModel of Yii2. The doc states also:
DynamicModel is a model class primarily used to support ad hoc data validation.
Here is a full example with form integration from the Yii2-Wiki, so i don't make a example here.
Virtual Attributes
When you want to add a attribute to the model, which is not in the database. Then just declare a public variable in the model:
public $myVirtualAttribute;
Then you can just use it in the rules like the other (database-)attributes.
To do Massive Assignment don't forget to add a safe rule to the model rules:
public function rules()
{
return [
...,
[['myVirtualAttribute'], 'safe'],
...
];
}
The reason for this is very well explained here:
Yii2 non-DB (or virtual) attribute isn't populated during massive assignment?

How to define an accessor for an existing field in a model in Laravel 5?

I have a model called Book for a table called books, that has a field called cover_image.
However, I want to define an accessor for the field cover_image instead of just retrieving the default value.
This is how I attempted to do this:
class Book extends Model {
public function getCoverImageAttribute() {
if ($this->cover_image === null) { // Not sure how to check current value?
return "a.jpg"
}
return $this->cover_image;
}
}
However the above of course does not work because calling $this->cover_image again would cause recursion
How can I fix this?
You must check for the attribute instead:
class Book extends Model {
public function getCoverImageAttribute() {
return $this->attributes['cover_image'] ?? "a.jpg";
}
}
This will allow you to normally use $book->cover_image as well.
I believe the following example will also work:
class Book extends Model {
public function getCoverImageAttribute($value) {
return is_null($value) ? 'a.jpg' : $value;
}
}

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');

Categories