CakePHP - Where to put reusable custom validation rules? - validation

I want to add a custom validation rule that can be reused across the application when necessary. Where is the best place to put it?
I know I can put them in AppModel but it's messy and pollutes/bloats the AppModel with methods that are only used occasionally for validation, and I find I need to prefix them all with validateUsername, validateCustom, etc. to keep them clearly organised from the other methods in the AppModel. I feel like there is a better way of doing this in the OOP structure.
Is there a way to specify custom rules as static methods of a Lib class, for example?
e.g.
app/Lib/Validate.php
class Validate {
public function username($value) {
$value = array_shift($value);
return preg_match('/^[A-Z0-9_-]+$/i', $value);
}
}
And then use the rules only in the models when you need to:
app/Model/MyModel.php
App::uses('Validate', 'Lib');
class MyModel extends AppModel {
public $validate = array(
'username' => array(
'rule' => 'Validate::username',
'message' => 'Username contains invalid characters'
)
);
}

You could do this with a Behavior. http://book.cakephp.org/2.0/en/models/behaviors.html There is a setup callback you could use to attach validation rules or create your own custom functions. You can then attach the Behaviors as needed to models.

Related

Simplify API boilerplate in Laravel's controllers?

When I write APIs with Laravel, I often use the same method as GitHub API v3. I add URLs to ease the navigation during development and also for the users that will develop using that API.
In this example, I manually add the URLs on every field then add a count for the pagers on the frontend. I sometime have more complicated stuff if I want to add the necessary to filter the results (if used with Vuetify or Kendo Grid).
class UserController extends Controller
{
function index() {
$users = User::all()->each(function ($item, $key) {
$item['activities'] = url("/api/users/{$item['id']}/activities");
$item['classrooms'] = url("/api/users/{$item['id']}/classrooms");
});
return [
'count' => count($users),
'courses' => $users,
];
}
}
Is there a way to make this code less boilerplate? Is there a package that does everything out of the box?
I'm a big fan of fractal especially spaties fractal package. This enables you to transform objects into responses.
There are two concepts in fractal serializers and transformers. Serializers is about the whole request, meta information data etc. Transformer is how you transform each model or object. Normally you would not make custom serializers, but in your case in can solve most of your trouble.
use League\Fractal\Serializer\ArraySerializer;
class YourSerializer extends ArraySerializer {
public function collection($resourceKey, array $data)
{
return [
$resourceKey => $data,
'count' => count($data),
];
}
}
Create your transformer for your user. The other big thing you gain for using this, is you have one plays to be responsible for transformation. Instead of each controller having to have the logic, which will be spread out and you have to remember to include it.
use League\Fractal\TransformerAbstract;
class AccountBalanceTransformer extends TransformerAbstract
{
public function transform(User $user)
{
return [
'id' => $user->id,
'name' => $user->id,
// other user fields
'activities' => url("/api/users/{$user->id}/activities"),
'classrooms' => url("/api/users/{$user->id}/classrooms"),
];
}
}
You have to assign the serializer in your fractal.php config.
'default_serializer' => YourSerializer::class,
Now you should be able to transform you responses like so.
return fractal($users, new UserTransformer())
->withResourceName('courses')
->respond(Response::HTTP_OK);
For making it easier to use and avoid repeating your self, i usually do a method on a parent controller like so. While setting the transformer on the object.
public function respond($data, $resourceKey, $status = Response::HTTP_OK) {
return fractal($data, $this->transformer)
->withResourceName($resourceKey)
->respond($status);
}
This will produce a response similar to what was specified in the question.

Map controller functions into database records in Laravel

I try to implement a chain of approval methodology in my Laravel app.
Lets say that I have a standard CRUD controller with standard REST routes.
[URL]/products
In the controller I have 3 functions (Index, Store, Update)
I want that for Store and Update a certain condition will happen before, something like that (pseudo):
if (fucntion requires chain of approval) {
if (auth()->user !== one of the approvers){
email all approvers;
return 201 "pending approval";
}
}
// the logged in user is allowed to execute the function
rest of the code...
I'm struggling with a few things here:
The only thing I can think of something that might be similar to this inside Laravel is the $this->authorize() function but I don't think that it was meant to be used like this, it is meant to authorize or not to authorize, not for 201 codes, and using it means aborting with 201 and it doesn't sit right
I want to allow the admins to control which functions need approval and because I don't want to maintain my controller functions together with a seeder that will contain a list of all the functions I'm thinking about creating an artisan command to be run before production and mapping all the functions into a database table that will be used as a model and all the process will use a proper many to many relations between the functions and the approvers, but I don't know how to map the functions with artisan command and I don't know if this is even the right way to go
I want to avoid from writing a certain code in all the functions that might require approval, and don't know how and if it is even possible
The return of the functions should return a JsonResource of the specific model for example ProductResource, What should I return when I need approval? Just to mock a proper response with status pending?
Thanks for everybody who is willing to give me some guidance.
You can do something similar to below... just have a single class that defines how each user type (or even permission) is handled.
public function index(IndexRequest $request) // verify that use is authorized to do this action
{
$response = (new MyJobDirector)->handle(Auth::user());
.. handle response
}
MyJobDirector Class
class MyJobDirector
{
const STRATEGY = [
'user' => 'userHandler',
'manager' => 'managerHandler',
];
public function handle(User $user): array
{
return $this->{static::STRATEGY[$user->role]}();
}
protected function userHandler(): array
{
event(EmailApprovers::class);
return [
'code' => 201,
'response' => 'pending approval',
];
}
}
You can make it even more complicated by defining a separate class for each handler and specifying constants for status and a response that each class should return
class User extends BaseJobHandle
{
const CODE = 201;
const RESPONSE = 'pending approval';
}
abstract class BaseJobHandler
{
const CODE;
const RESPONSE;
public function handle(): array
{
$this->additionalProcesses();
return [
'code' => static::CODE,
'response' => static::RESPONSE,
];
}
protected function additionalProcesses(): void {}
}
class MyJobDirector
{
const STRATEGY = [
'user' => User::class,
'manager' => Manager::class,
];
public function handle(User $user): array
{
$class = static::STRATEGY[$user->role];
return (new $class)->handle();
}
}
Regarding The return of the functions should return a JsonResource of the specific model for example ProductResource, What should I return when I need approval? Just to mock a proper response with status pending?
You can have a ProductResouce class, in which you decide which specific resource to return depending on a use case. It's all depends on what data you need to return.

Laravel FormRequest with variables

How do I put my validation logic within a FormRequest, knowing that my validation rules need variables set by the controller?
public function store()
{
$commentable = Comment::getCommentable(request('input1'), request('input1'));
// I need this $commentable above in my validator below!
$this->validate(request(),[
'commentable_type' => 'required|string|alpha', // Post
'commentable_id' => 'required|uuid|exists:' . plural_from_model($commentable) . ',' . $commentable->getKeyName(),
'body' => 'required|string|min:1',
]);
// ...
}
Here is my actual code: https://i.imgur.com/3bb8rgI.png
I want to tidy up my controller's store() method, moving the validate() in a FormRequest. However, as you can see, it needs the $commentable variable, which is retrieved by the controller.
I guess I could make it so that the FormRequest could retrieve that variable itself as well, but that would be an ugly duplicate (since it would also probe the database twice...). So that is not a good solution at all.
Any idea? Cheers.
Your FormRequest class can do pre-validation steps (including adding/modifying the input data, as shown below) via the prepareForValidation hook:
protected function prepareForValidation()
{
$this->commentable = Comment::getCommentable($this->input('input1'), $this->input('input1'));
$this->merge([
'commentable_id' => $this->commentable->id,
'commentable_type' => $this->commentable->type
]);
}
You'll be able to use $this->commentable in your rules() function, as well.

non-static method basemodel::validate() should not be called statically,assuming $this from compatible context

Was tryin to validate my registration form calling the validation method from the basemodel in my controller
The method
public function postSIgnup ()
{
$validation = User::validate(Input::all());
}
Routes
Route::post('register', array('before=>'csrf', 'uses'=>'UsersController#postSignup'));
Help mi solve this problem
You can't just say 'validate my whole form'.
The reason this error occurs is because you are trying to use the validation method from Laravel.
Basic Form Validation in Laravel
First you want to grab all your form data/content.
$input = Input::all();
Secondly you can setup some rules. Those rules can be set in an array which Laravel will use.
Make sure the names are correctly spelled. (Those are the ones u used in your form-template.)
$rules = array(
'real_name' => 'Required|Min:3|Max:80|Alpha',
'email' => 'Required|Between:3,64|Email|Unique:users',
'age' => 'Integer|Min:18',
'password' =>'Required|AlphaNum|Between:4,8|Confirmed',
'password_confirmation'=>'Required|AlphaNum|Between:4,8'
);
To make use of the validator you have to create anew instance first:
You attach the form input and the rules you have set above.
When they match you can save your form to your database or what else you would like to do with it. That will probably look like:
$validator = Validator::make($input,$rules);
Cool,
We can check now if the Validator passes or not...
if($validator->fails()){
$messages = $validator->messages();
return Redirect::to('yourpage')->withErrors($messages);
}else{
// Handle your data...
}

Check if field exists in Input during validation using Laravel

I want to make sure that certain fields are posted as part of the form but I don;t mind if some are empty values.
The 'required' validation rule won't work as I am happy to accept empty strings. I have tried the below, but as the 'address2' field is never sent, the validator doesn't process it.
Any ideas?
$rules = array(
'address2' => 'attribute_exists'
);
class CustomValidator extends Illuminate\Validation\Validator {
public function validateAttributeExists($attribute, $value, $parameters)
{
return isset($this->data[$attribute]);
}
}
You can use Input::has('address2') to check if something is posted by address2 input name. See the example:
if(Input::has('address2')) {
// Do something!
}
In Laravel 5,
if($request->has('address2')){
// do stuff
}
You should make custom validator like this.
use Symfony\Component\Translation\TranslatorInterface;
class CustomValidator extends Illuminate\Validation\Validator {
public function __construct(TranslatorInterface $translator, $data, $rules, $messages = array())
{
parent::__construct($translator, $data, $rules, $messages);
$this->implicitRules[] = 'AttributeExists';
}
public function validateAttributeExists($attribute, $value, $parameters)
{
return isset($this->data[$attribute]);
}
}
This will make AttributeExists work without to use require. For more explain about this. When you want to create new validator rule. If you don't set it in $implicitRules, that method will not work out if you don't use require rule before it. You can find more info in laravel source code.
When you submit a form each and every field is posted, matter of fact is if you leave some filed empty then that field value is null or empty. Just check the POST parameters once, to do so open the firebug console in firefox and submit the form, then check the post parameters. As you want to accept empty string what is the use of any rule?
else You can do this
$addr2=Input::get('address2');
if(isset($addr2)){
//do here whatever you want
}else{
//do something else
$addr2='';//empty string
}
Actually, Laravel has a method to validate if an attribute exists even if not filled.
$rules = [
'something' => 'present'
];
All the validation rules are stored in Validator class (/vendor/laravel/framework/src/Illuminate/Validation/Validator.php), you can check for the implementation of each rule, even no documented rules.

Resources