How can I validate GET controller params in CakePHP 2? - validation

Given this on the model:
public $validate = [
'amount' => array(
'rule' => array('comparison', '>=', 0),
'message' => 'You must buy over 0 of this item!'
)
];
How can I validate param #2 of the below?
public function buy(int $item, int $amount) {
Validation seems to be built only for POST, which I'd like to opt out of here.

First things first, modifying the database with GET requests is an anti-pattern for many different reasons. Even if you assume a friendly user agent (which you never should!), browsers can behave quirky and do unexpected stuff like for example sending GET request multiple times (that is perfectly valid as GET is not ment to modify data), which they usually won't for POST/PUT/DELETE.
I would strongly suggest to change your endpoint to handle POST requests instead.
That being said, you can generally validate whatever you want, the validation mechanisms first and foremost just validate data, they don't know or care where it stems from. You can hand over whatever data you want to your model, and let it validate it:
$data = array(
'item' => $item,
'amount' => $amount,
);
$this->ModelName->set($data);
if ($this->ModelName->validates()) {
// data is valid
} else {
// data is invalid
$errors = $this->ModelName->validationErrors;
}
Moreover you can use CakePHP's validation methods completely manually too:
App::uses('Utility', 'Validation');
$isValid = Validation::comparison($amount, '>' 0);
This example of course doesn't make too much sense, given that $isValid = $amount > 0 would do the same, however it should just show that you can validate anything everywhere without models being involved.
See also
Cookbook > Models > Data Validation > Validating Data from the Controller
Cookbook > Models > Data Validation > Core Validation Rules

Related

Laravel validation - fail when provided with input not defined in rules

Does Laravel validation provide any ways to fail when request contains input keys that are not defined in validation rules? Ex: Validator is instantiated with the following rules: ['name' => 'required', 'email' => 'required|email']. I want validation to fail if $request contains any other keys except name and email (Think of a user POSTing to the route end-point with undesirable data). Is that possible to achieve with simple validation rules?
P.S. I am aware of mass-assignment tricks with Eloquent, however I need to perform strict validation before any data is manipulated / persisted.
No, it's not possible to achieve with simple validation rules but would be easy to add.
All you would need to do is something like the following...
if ( count(request()->except(['name', 'email']) ) > 0) {
return false;
}

Controller and Routes in Laravel

What basically is the difference between Controller and Routes. We can control our data using routes file, then why do we need controllers?
Like:
<?php
// app/routes.php
// route to process the ducks form
Route::post('ducks', function()
{
// process the form here
// create the validation rules ------------------------
$rules = array(
'name' => 'required', // just a normal required validation
'email' => 'required|email|unique:ducks', // required and must be unique in the ducks table
'password' => 'required',
'password_confirm' => 'required|same:password' // required and has to match the password field
);
// do the validation ----------------------------------
// validate against the inputs from our form
$validator = Validator::make(Input::all(), $rules);
// check if the validator failed -----------------------
if ($validator->fails()) {
// get the error messages from the validator
$messages = $validator->messages();
// redirect our user back to the form with the errors from the validator
return Redirect::to('ducks')
->withErrors($validator);
} else {
// validation successful ---------------------------
// our duck has passed all tests!
// let him enter the database
// create the data for our duck
$duck = new Duck;
$duck->name = Input::get('name');
$duck->email = Input::get('email');
$duck->password = Hash::make(Input::get('password'));
// save our duck
$duck->save();
// redirect ----------------------------------------
// redirect our user back to the form so they can do it all over again
return Redirect::to('ducks');
}
});
Well, this is not my code, I read it somewhere, But, here this person has used the validation in routes.php file, and in my project, I used the validation technique in a controller named UserController, what difference does it make?
Routes translate each incoming HTTP request to an action call, for example to a method of a controller, whereas controller is the place where business logic are written. There is nothing wrong in handling all in one file, but once your projects gets bigger it would be nightmare to manage such code. It's like responsibility, route, route the request to specific controller, controller process it, pass result to view. Mostly it's design pattern.
We can even have all the code in one huge file without using any classes at all, but we know that is not a good idea. The current best practice is to separate the code depending on responsibilities (single responsibility principle) to make it easier to other developers to read and understand the code. Often the next developer is yourself in some months, so having a clean structure don't only benefit others but also your sanity when going back to your old code.
The name router imply that the class routs data, in this case from an URI to a controller and the controller handle the business rules for that particular controller
Routes in laravel is a place where you define your application end points and controller is where you write your business logic.
I had the same problem understanding Laravel when I started to learn and to make it simple, I have created some project in MCV style please check this
https://github.com/jagadeshanh/understanding-laravel

CakePHP 3.x unique validation not working for saving multiple records

I have a Questions table which has a validation like :
$validator
->notEmpty('title')
->add('title', [
'unique' => [
'rule' => [
'validateUnique',
['scope' => ['subject_id', 'chapter_id']]
],
'provider' => 'table'
]
]);
I want to save following records into my table at a time.
Array
(
[0] => Array
(
[subject_id] => 1
[chapter_id] => 4
[title] => What is a .ctp file used for in CakePHP?
)
[1] => Array
(
[subject_id] => 1
[chapter_id] => 4
[title] => What is a .ctp file used for in CakePHP?
)
)
I try to save it using saveMany() method. It save both records i.e. validation not working. I also try following code for transactional() method instead of saveMany() method, but validation also not working.
$entities = $this->Questions->newEntities($records);
$this->Questions->connection()->transactional(function () use ($entities) {
foreach ($entities as $entity) {
$this->Questions->save($entity);
}
});
My validation works fine if I save the records one by one using save() method or my records already saved in database. Why my unique validation not working for saveMany() and also for transactional() for duplicate new entities?
Validation happens before saving
Validation happens before saving, so that behavior is to be expected, given that the rule looks up the database, and not the request data (it would only have access to one set of data at the time anyways), ie, no matter how many datasets are being tested, none of the submitted ones will be saved yet, and therefore validation will pass unless a matching record already exists in the database.
So either create/patch and save all entities one by one in a custom transaction (and don't forget to add some proper failure checks),
$this->Questions->connection()->transactional(function () {
foreach ($this->request->data() as $set) {
$entity = $this->Questions->newEntity($set); // < validaton is being applied there
if (!$this->Questions->save($entity)) { // < not there
return false;
}
}
return true;
});
or use application rules instead.
Application rules are being applied in the saving process
Application rules are being applied in the actual saving process, ie upon calling Table::save(), so to avoid the hassle of using custom transactions, and generally to have a last line of defense, use them instead of/additionally to validation.
// QuestionsTable class
public function buildRules(\Cake\ORM\RulesChecker $rules)
{
$rules->add($rules->isUnique(['title', 'subject_id', 'chapter_id']));
// ...
return $rules;
}
See also
Cookbook > Database Access & ORM > Database Basics > Using Transactions
Cookbook > Database Access & ORM > Validation > Applying Application Rules
Cookbook > Database Access & ORM > Validation > Creating Unique Field Rules
Cookbook > Database Access & ORM > Validation > Validation vs. Application Rules

Symfony2: Questions about forms and validation

I have multistage user signup and I seem to be missing something.
I have a user entity and some other data embedded in the form which I want to collect as well. For the sake of an example we will say User has name and email and these map to fields on the user entity. On the same form I also have device data as a hidden field.
On my User entity i have a validation group 'plan' so when I submit I do something like this:
<?php
$user = new User();
$form = $this->createFormBuilder($user, array('validation_groups' => array('plan'))
->add('name')
->add('email')
->getForm();
$form->handleRequest($request)
if ($form->isValid()) {
$user = $form->getData();
$em->persist($user);
$em->flush();
} else {
return $this->render('myform.html.twig', array('form' => $form->createView()));
}
This is mostly rough psuedocode but now when I call $form->getData() or $request->request->all(), device_data is stripped out and no where to be found. I can get around this by not passing $user into createFormBuilder as the first argument but then my validation group doesn't seem to happen because it is bound to the user entity. Is there a way around this?
So it looks like the problem wasn't exactly as I descried, the correct solution which I had known beforehand is ->add('fieldname', null, array( 'mapped' => false )), however this was not working for me due to an implementation detail and using the DomCrawler.
You can then access the unmapped fields via the request object.
$request->request->get('form[fieldname]')
Andrew's answer is good but if you want to perform "direct validation" (through $form->isValid() method) you would prefer to use something like this
$form = $this->createFormBuilder($user, array('validation_groups' => array('plan'))
->add('name')
->add('email')
->add('agree','checkbox', array('mapped' => false, 'constraints' => array(new NotBlank()))
->getForm();
where agree field, for instance, that is a checkbox that you have to check like "accept terms and conditions" to move on.
mapped => false is telling to createFormBuilder to don't associate that field with an entity field as it will not be present

Override validation method in CakePHP

I want to ovveride the default url() validation method in CakePHP, since it does not allow the use of ~ inside urls. I thought it would be enough to declare a url() method in AppModel, but it seems that core methods have the precedence with respect to user defined ones.
I think (but I have not tried) one possible way would be to use
$validate = array('url' => array(
'rule' => array('Userdefined', 'url'),
'message' => 'This is not an URL!!!'
));
or something like that (what is the correct sintax?). But this is not completely satisfying.
Indeed I pass the $validate variable as a JSON object to my javascript, and then I do client validation accordingly. Basically I have rewritten part of the CakePHP validation automagic in javascript. So I really want to have
$validate = array('url' => array(
'rule' => 'url',
'message' => 'This is not an URL!!!'
));
in order not to break client-side validation.
EDIT: It turns out I were wrong. The problem is that methods in Validation are called differently from methods in Model, so one has to pay attention when copying/pasting.
The first difference is that $check will now be an array instead of a string, but this I already figured out. What I did not realize is that another array of parameters is passed to Validation methods in Model. Since the signature of url() was
url($check, $strict = false)
the result was that $strict always had the value true, thereby requiring full URLs with protocol prefix. Seeing that the intended URL with tilde was not validating I assumed that the problem was that CakePHP still used the old method.
Why not just use the custom validation and make an url validation function with a different name?
Otherwise the manual says that you can override the Validation classes methods with functions in either the AppModel, Model, or Behaviors.
Here is the relevant link in the book.
http://book.cakephp.org/view/150/Custom-Validation-Rules#Adding-your-own-Validation-Methods-152

Resources