Does Ardent for Laravel work with Repository Pattern? - laravel-4

Anyone use Ardent in Laravel with the repository pattern and have it "auto-hydrate" relations on save? If so, do the rules need to be in the repository or can they be in a separate Validator service?

The basic idea of Ardent is autovalidation done in the model itself. However if you want to make your app as robust as possible it's better to use validation services. In the end you can use the service (or even pass it's internal $rules) wherever you wish so it's totally DRY.
EDIT:
Suppose you have such a validation service
namespace App\Services\Validators;
class UserValidator extends Validator {
/**
* Validation rules
*/
public static $rules = array(
'username' => array('required'),
'email' => array('required','email'),
'password' => array('required','min:12','confirmed'),
'password_confirmation' => array('required','min:12'),
);
}
in a repository you can do
public function store()
{
$v = new App\Services\Validators\UserValidator;
if($v->passes())
{
$this->user->create($input);
return true
}
return Redirect::back()->withInput()
->withErrors($v->getErrors());
}
in an Ardent model you can just modify the rules directly
Ardent::$rules = UserValidator::$rules
Check out Ardent docs and you might find this article on validation interesting, the code above is based on that article.

Related

How to validate related objects request data from a parent controller in Laravel?

I'm using Laravel 8.x and have a one-to-one and one-to-many relationship with models as follows.
class ServiceProvider extends Model
{
use HasFactory;
protected $guarded = [];
public function contact() {
return $this->hasOne('App\Models\Contact');
}
public function services() {
return $this->hasMany('App\Models\Service');
}
}
I'm using a single form to get all the data. The problem is both Contact and Service has their own validation to be done. I can duplicate the validation in the ServiceProviderController. But it seems ugly and violate DRY. Is there a way to call the ContactController and ServiceController to do the validation and return the validation result to be accessed by the ServiceProviderController?
As I guess you are doing something like:
/**
* Store a new service provider.
*
* #param Request $request
* #return Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
return redirect('services/create')
->withErrors($validator)
->withInput();
}
// Store The Service Provider...
}
If Yes, I would recommend using a form request validator to validate the request. So if the request is get passed your controller will be called. Also, you can use the same Request Validation rule for both controllers. you can read how to create and use one more here.
You can set up enumerator classes that will return validation rules for you, or even declare static properties on your model which will hold an array of validation rules, and which can be called like: ServiceProvider::$rules or something similar.
This way you will keep all your rules at one place. You can't explicitly call controllers whenever, they respond to routes.
When you get the validation rules, just use $request->validate() method and send the rules you gathered to it.

Laravel 5.7 email verification expiration time

I would like to customize the time users have to verify their email address that happens through the built in Auth (since 5.7).
In config/auth there is:
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
],
],
But I haven't found anything similar for email verification. There is also no mention in the official documentation.
Whilst the question specifically addresses Laravel 5.7, I feel that it is worth mentioning that as of Laravel 5.8, it is possible to achieve this with a config variable. My search for customising the verification expiration time returned this question as the top result, hence my addition.
If we check out Illuminate\Auth\Notifications\VerifyEmail, the verificationUrl method now looks like this:
protected function verificationUrl($notifiable)
{
return URL::temporarySignedRoute(
'verification.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
['id' => $notifiable->getKey()]
);
}
As such, we can just add this block to config/auth.php to customise the time without needing to extend the classes or anything:
'verification' => [
'expire' => 525600, // One year - enter as many mintues as you would like here
],
UPDATE: I've written about the above approach, as well as another on customising the process by overiding the verificationUrl method to give you more flexibility, on my blog.
In deed the options is not there in Laravel, but since laravel makes use of the following:
a trait MustVerifyEmail (in Illuminate\Foundation\Auth\User class extended by the main User model)
Event and Notification
In the MustVerifyEmail trait, there's a method called sendEmailVerificationNotification. This is where the Notification VerifyEmail class referenced by #nakov's answer and its function verificationUrl is used:
/**
* Send the email verification notification.
*
* #return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new Notifications\VerifyEmail);
}
Since we know this, we can do the following:
Extend the Notifications\VerifyEmail to our custom VerifyEmail class
override the implementation of verificationUrl
override the implementation of the sendEmailVerificationNotification method in the User model to use our new VerifyEmail class.
Having done the above, our User model will have the following method:
/**
* Send the email verification notification.
*
* #return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new \App\Services\Verification\VerifyEmail);
}
Now we make use of our custom VerifyEmail class. Then our new VerifyEmail class would look like this:
namespace App\Services\Verification;
use Illuminate\Support\Carbon;
use \Illuminate\Support\Facades\URL;
class VerifyEmail extends \Illuminate\Auth\Notifications\VerifyEmail
{
protected function verificationUrl($notifiable)
{
return URL::temporarySignedRoute(
'verification.verify', Carbon::now()->addMinute(3), ['id' => $notifiable->getKey()]
); //we use 3 minutes expiry
}
}
Well, apart from the explanations, the process is quite straight forward. I hope it is easy to grasp. Cheers!
If you open the Illuminate\Auth\Notifications\VerifyEmail::class;
The method that generates the URL already uses expiration time which defaults to 1 hour. Unfortunately there is no option to modify that value.
/**
* Get the verification URL for the given notifiable.
*
* #param mixed $notifiable
* #return string
*/
protected function verificationUrl($notifiable)
{
return URL::temporarySignedRoute(
'verification.verify', Carbon::now()->addMinutes(60), ['id' => $notifiable->getKey()]
);
}

proengsoft/laravel-jsvalidation How to use Custom Rule Messages?

I use proengsoft/laravel-jsvalidation in combination with a custom FormRequest and custom validation rules that I defined via Validator::extend(...) in a service provider. This works well.
However, when I port my custom rules to the new(ish) custom Rule class in Laravel 5.5+, JsValidator fails at getting my custom Rule messages.
I have this custom rule:
use Illuminate\Contracts\Validation\Rule;
class MyRule implements Rule
{
public function passes($attribute, $value) {
return $value > 10;
}
public function message() {
return 'Your :attribute is pretty small, dawg!';
}
}
My form request uses this rule:
use Illuminate\Foundation\Http\FormRequest;
use App\Rules\MyRule;
class MyRequest extends FormRequest
{
public function authorize() {
return true;
}
public function rules() {
$rules = [
'foo' => 'required|numeric',
'bar' => ['required', new MyRule()],
];
return $rules;
}
}
This should work, but I get thrown an Exception on
{!! JsValidator::formRequest('\App\Http\Requests\MyRequest') !!}
An Exception is thrown from a call to Str::snake(Object(App\Rules\MyRule)) made by Proengsoft\JsValidation\Javascript\MessageParser.php.
JsValidation does not look at the $rule object type before calling Validator->getMessage($attribute, $rule)
where instead it should be calling $rule->messages();
Can I work around this bug somehow, and use laravel-jsvalidation together with my custom Rule and FormRequest -- or does it necessarily require i make a pull request and hope it will be fixed... someday? I'd like to make this work now-ish.
This can be archived by passing JsValidator instance based on rules and message arrays instead of passing the form request. In controller Pass this instance to blade. Read here for more details.
JsValidator::make($rules, $messages, $customAttributes, $selector)
In controller,
$validator = JsValidator::make(
[
'name' => 'required',
],
[
'name.required' => 'Name field is a required field',
]
)
return View::make("Your view", compact($validator));
In blade,
{!! $validator->selector('.wizard_frm') !!}
<form class='wizard_frm'></form>
And in this case we can create object of request class and pass rules function returned array to JsValidator::make if needed.

Laravel policy not working

In my Laravel application, i have several policies working, but one will not work.
Controller
public function store(Project $project, CreateActionRequest $request)
{
$this->authorize('store', $project);
Action::create([
'name' => $request->name,
]);
return redirect()->route('projects.show', $project->id)->withSuccess('Massnahme erfolgreich gespeichert');
}
Policy
namespace App\Policies\Project;
use App\Models\Project\Project;
use App\Models\User;
use App\Models\Project\Action;
use Illuminate\Auth\Access\HandlesAuthorization;
class ActionPolicy
{
use HandlesAuthorization;
public function store(User $user, Project $project)
{
return $user->company_id === $project->company_id;
}
}
AuthServiceProvider
protected $policies = [
'App\Models\User' => 'App\Policies\CompanyAdmin\UserPolicy',
'App\Models\Company' => 'App\Policies\CompanyAdmin\CompanyPolicy',
'App\Models\Team' => 'App\Policies\CompanyAdmin\TeamPolicy',
'App\Models\Department' => 'App\Policies\CompanyAdmin\DepartmentPolicy',
'App\Models\Location' => 'App\Policies\CompanyAdmin\LocationPolicy',
'App\Models\Division' => 'App\Policies\CompanyAdmin\DivisionPolicy',
'App\Models\Costcenter' => 'App\Policies\CompanyAdmin\CostcenterPolicy',
'App\Models\Workplace' => 'App\Policies\CompanyAdmin\WorkplacePolicy',
'App\Models\Product' => 'App\Policies\CompanyAdmin\ProductPolicy',
'App\Models\Project\Action' => 'App\Policies\Project\ActionPolicy',
'App\Models\Project\Project' => 'App\Policies\Project\ProjectPolicy',
];
CreateActionRequest
namespace App\Http\Requests\Project;
use Illuminate\Foundation\Http\FormRequest;
class CreateActionRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|min:3',
];
}
}
All policies are working except ActionPolicy and ProjectPolicy.
I added in the policy a __construct() method to check if the policy is called. But ActionPolicy and ProjectPolicy are not working.
How can i search the error? I tried with dd() but i got only allways the message: This action is unauthorized
Since you are injecting CreateActionRequest instead of Request that means you are defining your own set of rules to authorize the FormRequest which comes inside of your method. Further it means that you gotta define a few rules which the "FormRequest" has to pass in order to EVEN reach your controller, this is a nice concept that I like about Laravel since the code is not centralized, but rather spread and every layer has it's own responsibility. Now, you don't have to call any method from your CreateActionRequest nor you have to write any code regarding that class in your controller, because Laravel runs authorize method by default before allowing the Request to reach your controller, before running authorizemethod in your CreateActionRequest it runs rules method which verifies that all the given fields pass the expressions you assigned them, so the execution is something like this CreateActionRequest => rules => authorize => IF(authorized) Controller ELSE Not authorized, hope that makes sense. In order to fix your code:
1.) Remove $this->authorize('store', $project);
This will allow you to pass not authorized error in case your name passes the truth test inside of rules method inside of your CreateActionRequest. If you wish to utilize your Action Policy you will need to hook up your custom Request(CreateActionRequest) with it and this is how:
public function authorize()
{
$store = $this->route('project');
//The above line will return Project object if your mapping is correct
//If it's not it will return the value you passed to your route for {project}
return $this->user() && $this->user()->can('store', $store);
}
EDIT:
Here is the link where you can see how to properly authorize and connect policy with CreateActionRequest
Do you have all your controller methods defined with the Request object last?
public function store(Project $project, CreateActionRequest $request)
The Request object should be the first parameter in the methods signature:
public function store(CreateActionRequest $request, Project $project)
Dependency Injection & Route Parameters
If your controller method is also expecting input from a route parameter you should list your route parameters after your other dependencies.
Most Laravel authorization mechanisms have identical method signatures allowing them to work across varying classes.

Updating and Validating email field

Im creating a update page, where the user can change his email, but it needs a password confirmation for that. But before this, it needs some kind of validation, first to check if the current email is correct and also if the new email is available to be saved, and after the password is correct than be updated.
But im having some trouble in making the request, validation, can someone tell me if this is correct? (dont mind the dd i putted, is just for testing).
$user = Auth::user();
$this->validate($request, array(
'current_email' => 'required|email|unique:users,email,'. $user->id,
'email' => 'required|email|unique:users',
'verify_password' => 'required|min:6'
));
//Verify information user before updating
if($user->email != $request->current_email){
dd("Current Email is not the same");
}
if($user->password != bcrypt($request->verify_password)){
dd("Password incorrect, will not update");
}
dd("update, is ok now");
First write this in your console.
php artisan make:provider ValidationServiceProvider
Then replace your app\Providers\ValidationServiceProvider with
namespace App\Providers;
use Validator;
use Illuminate\Support\ServiceProvider;
class ValidationServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot() {
Validator::extend('old_password', function($attribute, $value, $parameters, $validator) {
return auth()->validate([
'email' => auth()->user()->email,
'password' => $value
]);
});
}
/**
* Register the service provider.
*
* #return void
*/
public function register() {
//
}
}
Now add it to providers in config\app.php, like
App\Providers\ValidationServiceProvider::class,
Now replace your method definition with
$user = auth()->user();
$validator = Validator::make($request, array(
'current_email' => 'required|email|exists:users,email,id,'. $user->id,
'email' => 'required|email|unique:users',
'verify_password' => 'required|min:6|old_password'
));
if($validator->fails()) {
return redirect()->back()->withErrors($validator)->withInput();
}
dd("Good to go!!!");
First of all I have replaced your current_email unique validation with exists. Why? Have a look here
The method I have used here for validation is called Custom Validation. More details here
Let me know if you face any issues :)
What you have will work, but there are a couple of things that I would recommend.
First, you already have the $request, so you should get the user from that. While Auth::user() and $request->user() do return the same thing, the later will not require the use of a facade and therefore is a little quicker.
Second, I would validate the before you validate the request body. It doesn't make sense to spend the resources validating the $request if the password is not correct.
Third, you can put your $user->email == $request->current_email check in the validation using the exists rule. It would be something like "exists:users,email,id,$user->id".
How you display the errors will be up to how the request is being done. Take a look at the Displaying Validation Errors section for submitting a form and the AJAX Requests and Validation for AJAX requests.

Resources