Large form validation structure - php

I'm currently building a form manager in PHP to validate large forms.
I'm wondering what is the best structure for that, because the number of fields will be different each time.
I'm already filtering the field to validate by using a prefix (ex : 'user_name', will be validated, but 'name' no).
My real problem is for the validation : I must check the type of the field (mail, zipcode, phone...)
AND check that the value for this type of field is valid.
I thought that I could use HTML5 Custom data" (ex: data-fieldtype="zipcode"), but i didn't know that the server can't get this attribute...
What's the way to go ?
I could use 2 inputs per field, one for the value and one for the type, but it looks really stupid !
Thanks if you can help.
EDIT :
Your answers are all interesting, i don't know which is best.
I will probably make a mix between your solutions, depending of the kind of form.
Thanks a lot.

Methinks, this shouldn't be played via the Browser without further thought: A malicious user would be able to manipulate a "INT ONLY" field into being freetext, and your application would suddenly have to deal with freetext in a field, that is thought to be validated as an integer (und thus e.g. safe for SQL).
You have two approaches:
Have your form validation structure stored in the DB, and submit a single hidden field, that carries the ID of the validation structure. On receiving the request, your script would request the structure from the DB, unserialize it, and work on it.
If you really need to go through the browser, serialize your validation structure, base64-encode it and use it as a single hidden field. For the reasons stated above, it is mandatory to authenticate this value, either hashing (concatenate with another string only known to the server, hash it, send the hash as a second hidden field, on next request verify the hash is correct) or by encryption (encrypt the serialized data before the browser roundtrip, decrypt afterwards, key known only to the server)
Both would make your prefix-trick unnecessary, increasing readability and maintainability.

If no framework is used, you can use an array of field => options.
$rules = [
'user_name' => 'required',
'user_email' => 'email required',
// ...
];
And then feed them to some validator class, where rules are methods and they're being called inside validate method dynamically:
class Validator {
public function __construct($data) { $this->data = $data; }
private function required($field) {}
private function email($email) {}
// etc
/** #method bool validate(array $rules) */
public function validate($rules) {}
}

Never ever validate on client-side only. Always validate serverside.
Usually you will have a base class for controller, and every other controller extends it.
Aa good approach is to have en every view controller (or the bootsrtap) a method check_params().
It should 1) get a copy or $_REQUEST, check every parameter needed, 2) delete $_REQUEST, 3) write back the checked and validated params.
abstract class controller_base {
public function __construct() { ...; $this->check_param();...}
protected final function check_param() {
foreach ($this->param_list() AS $name => $type) {...}
}
abstract public function param_list();
}
class controller_login extends controller_base {
public function param_list() {
return array('name' => 'string', 'password' => 'string');
}
}
The idea is that this way you
only use params that has been sanitized
you autmaticly delete every param not needed
you have a list in every controller that states the used params.

Related

Fixing Laravel form array validation recursion limits

Laravel Form Validation is great, except that it doesn't filter out extraneous keys when array notation is used.
I am type hinting the form request in my controller.
public function saveEdit(Post\EditRequest $request)
{
$valid = $request->validated();
}
If my form has address_1 and address_2, and the user spoofs address_3, the spoofed value will not turn up in $valid.
However, if my form uses array notation, such as venue[address_1], and venue[address_2], then the user can spoof venue[address_3], and it will turn up in $valid.
Has anyone else come across this? How did you deal with it?
It seems like the validated() method on the form request class needs to operate recursively.
Unfortunately, Laravel doesn't include built-in support for validating array keys yet, only array values. To do so, we need to add custom validation rules.
However, with alternative protection like $fillable model attributes and the array_only() helper function, creating these rules is a lot of extra work for a very unlikely edge case. We provide validation feedback for expected user error and sanitize to protect our data from unexpected input.
If the user spoofs a key, as long as we don't save it, or we filter it out, they won't be too concerned if they don't see a pretty validation message—it doesn't make sense to validate a field that shouldn't exist in the first place.
To illustrate how sanitization works, here's a Venue model that declares its fillable attributes:
class Venue extends Model
{
protected $fillable = [ 'address_1', 'address_2' ];
...
}
Now, if a malicious user attempts to spoof another attribute in the array:
<input name="venue[address_1]" value="...">
<input name="venue[address_2]" value="...">
<input name="venue[address_3]" value="spoof!">
...and we use the input array directly to update a model:
public function update(Request $request, $venueId)
{
$venue = Venue::find($venueId);
$venue->update($request->venue);
...
}
...the model will strip out the additional address_3 element from the input array because we never declared it as a fillable field. In other words, the model sanitized the input.
In some cases, we may need to simply sanitize array elements without using Eloquent models. We can use the array_only() function (or Arr:only()) to do this:
$venueAddress = array_only($request->venue, [ 'address_1', 'address_2' ]);
you can use dot notation
public function rules()
{
return [
'venue.address_1' => 'required',
'venue.address_2' => 'required',
'venue.address_3'=>'required',
];
}
and if you are using dynamic array you can do same by
public function rules()
{
return [
'venue.address_.*' => 'required'
];
}

Checking for empty values when saving into multiple tables with ZF2

I'm using ZF2 and mysql, but the question is platform-independent. I have a data transfer object Organization that gets hydrated from an html form. OrganizationMapper has a save method that (1) gets Organization as an argument and (2) fills a couple of database tables one after another.
Suppose the 1st table gets filled ok, but the 2nd doesn't because one of the properties of Organization isn't set (not null constraint on a column). The user gets an error, but the 1st table is already filled. If he attempts to submit the form again, but this time with all html fields filled, all the tables get filled ok, but the 1st has a previous unused row.
How could I avoid this situation?
I thought of checking for empty values with if's in the mapper's save method, but it doesn't seem elegant. I know about the InputFilter validations in ZF2, but these check the user input in the form, they don't check things when the php code communicates with the database.
Any help?
The best way is to validate all the data before you start writing it to the database.
I didn't use ZF2 and this solution is actually framework-dependent, so you need to check the ZF2 docs. For example, in Yii, you just define validation rules for every field of the model, so you can ensure that your Organization contains all the data before you start saving it to the database, probably something similar is possible in Zend.
Note, that validation doesn't mean just to check for empty values, you may need to verify different things, like: "email is correct email like xxx#yyy.com", "name is not empty", "name length is more than 3 chars", "name length is less than 1000 chars" and so on.
For Yii it roughly looks like this:
class Organization extends ActiveRecord {
...
// here we define the validation rules
public function rules() {
return [
// name is required
['name', 'required'],
// check min / max length
['name', 'string', 'min' => 3, 'max' => 12],
// check if email is valid
['email', 'email']
];
}
}
Now you can do $organization->validate() to make sure everything is correct (also when you do $organization->save() the rules will be checked before saving to the database).
And one more solution to protect from the inconsistent data is to use transactions. In the case you write to multiple tables, you anyway need them, even if you validated everything. Unexpected things happen, so it is better to protect your saving code like this (pseudo-code):
$transaction->start();
try {
$table1->writeSomeData();
$table2->writeMoreData();
$transaction->commit();
} (catch Exception $e) {
$transaction->rollback();
}
Again, check your framework documentation, it probably supports this in some way.

Forms with Symfony2 - Doctrine Entity with some immutable constructor parameters and OneToMany association

I have a OneToMany association between a Server entity and Client entities in the database. One server can have many clients. I want to make a form where the user can choose a server from a dropdown, fill in some details for a new client, and submit it.
Goal
To create a form where a user can input data into fields for the Client, choose a Server from the dropdown, then click submit and have this data (and the association) persisted via Doctrine.
Simple, right? Hell no. We'll get to that. Here's the pretty form as it stands:
Things of note:
Server is populated from the Server entities (EntityRepository::findAll())
Client is a dropdown with hardcoded values
Port, endpoint, username and password are all text fields
Client Entity
In my infinite wisdom I have declared that my Client entity has the following constructor signature:
class Client
{
/** -- SNIP -- **/
public function __construct($type, $port, $endPoint, $authPassword, $authUsername);
/** -- SNIP -- **/
}
This will not change. To create a valid Client object, the above constructor parameters exist. They are not optional, and this object cannot be created without the above parameters being given upon object instantiation.
Potential Problems:
The type property is immutable. Once you've created a client, you cannot change the type.
I do not have a setter for type. It is a constructor parameter only. This is because once a client is created, you cannot change the type. Therefore I am enforcing this at the entity level. As a result, there is no setType() or changeType() method.
I do not have the standard setObject naming convention. I state that to change the port, for example, the method name is changePort() not setPort(). This is how I require my object API to function, before the use of an ORM.
Server Entity
I'm using __toString() to concatenate the name and ipAddress members to display in the form dropdown:
class Server
{
/** -- SNIP -- **/
public function __toString()
{
return sprintf('%s - %s', $this->name, $this->ipAddress);
}
/** -- SNIP -- **/
}
Custom Form Type
I used Building Forms with Entities as a baseline for my code.
Here is the ClientType I created to build the form for me:
class ClientType extends AbstractType
{
/**
* #var UrlGenerator
*/
protected $urlGenerator;
/**
* #constructor
*
* #param UrlGenerator $urlGenerator
*/
public function __construct(UrlGenerator $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** Dropdown box containing the server name **/
$builder->add('server', 'entity', [
'class' => 'App\Model\Entity\Server',
'query_builder' => function(ServerRepository $serverRepository) {
return $serverRepository->createQueryBuilder('s');
},
'empty_data' => '--- NO SERVERS ---'
]);
/** Dropdown box containing the client names **/
$builder->add('client', 'choice', [
'choices' => [
'transmission' => 'transmission',
'deluge' => 'deluge'
],
'mapped' => false
]);
/** The rest of the form elements **/
$builder->add('port')
->add('authUsername')
->add('authPassword')
->add('endPoint')
->add('addClient', 'submit');
$builder->setAction($this->urlGenerator->generate('admin_servers_add_client'))->setMethod('POST');
}
/**
* {#inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'data_class' => 'App\Model\Entity\Client',
'empty_data' => function(FormInterface $form) {
return new Client(
$form->getData()['client'],
$form->getData()['port'],
$form->getData()['endPoint'],
$form->getData()['authPassword'],
$form->getData()['authUsername']
);
}
]);
}
/**
* {#inheritdoc}
*/
public function getName()
{
return 'client';
}
}
The above code is what actually generates the form to be used client-side (via twig).
The Problems
First and foremost, with the above code, submitting the form gives me:
NoSuchPropertyException in PropertyAccessor.php line 456:
Neither the property "port" nor one of the methods "addPort()"/"removePort()", "setPort()", "port()", "__set()" or "__call()" exist and have public access in class "App\Model\Entity\Client".
So it can't find the port method. That's because it's changePort() as I explained earlier. How do I tell it that it should use changePort() instead? According to the docs I would have to use the entity type for port, endPoint etc. But they're just text fields. How do I go about this the right way?
I have tried:
Setting ['mapped' => false] on port, authUsername etc. This gives me null for all the client fields, but it does seem to have the relevant server details with it. Regardless, $form->isValid() return false. Here's what var_dump() shows me:
A combination of other things involving setting each on field to "entity", and more..
Basically, "it's not working". But this is as far as I've got. What am I doing wrong? I am reading the manual over and over but everything is so far apart that I don't know if I should be using a DataTransformer, the Entity Field Type, or otherwise. I'm close to scrapping Symfony/Forms altogether and just writing this myself in a tenth of the time.
Could someone please give me a solid answer on how to get where I want to be? Also this may help future users :-)
There are a few problems with the above solution, so here's how I got it working!
Nulls
It turns out that in setDefaultOptions(), the code: $form->getData['key'] was returning null, hence all the nulls in the screenshot. This needed to be changed to $form->get('key')->getData()
return new Client(
$form->get('client')->getData(),
$form->get('port')->getData(),
$form->get('endPoint')->getData(),
$form->get('authPassword')->getData(),
$form->get('authUsername')->getData()
);
As a result, the data came through as expected, with all the values intact (apart from the id).
Twig Csrf
According to the documentation you can set csrf_protection => false in your form options. If you don't do this, you will need to render the hidden csrf field in your form:
{{ form_rest(form) }}
This renders the rest of the form fields for you, including the hidden _token one:
Symfony2 has a mechanism that helps to prevent cross-site scripting: they generate a CSRF token that have to be used for form validation. Here, in your example, you're not displaying (so not submitting) it with form_rest(form). Basically form_rest(form) will "render" every field that you didn't render before but that is contained into the form object that you've passed to your view. CSRF token is one of those values.
Silex
Here's the error I was getting after solving the above issue:
The CSRF token is invalid. Please try to resubmit the form.
I'm using Silex, and when registering the FormServiceProvider, I had the following:
$app->register(new FormServiceProvider, [
'form.secret' => uniqid(rand(), true)
]);
This Post shows how Silex is giving you some deprecated CsrfProvider code:
Turned out it was not due to my ajax, but because Silex gives you a deprecated DefaultCsrfProvider which uses the session ID itself as part of the token, and I change the ID randomly for security. Instead, explicitly telling it to use the new CsrfTokenManager fixes it, since that one generates a token and stores it in the session, such that the session ID can change without affecting the validity of the token.
As a result, I had to remove the form.secret option and also add the following to my application bootstrap, before registering the form provider:
/** Use a CSRF provider that does not depend on the session ID being constant. We change the session ID randomly */
$app['form.csrf_provider'] = $app->share(function ($app) {
$storage = new Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage($app['session']);
return new Symfony\Component\Security\Csrf\CsrfTokenManager(null, $storage);
});
With the above modifications, the form now posts and the data is persisted in the database correctly, including the doctrine association!

Symfony Adding and Dealing With Custom Form Fields

I am using Symfony with propel to generate a form called BaseMeetingMeetingsForm.
In MeetingMeetingsForm.class.php I have the following configure method:
public function configure() {
$this->useFields(array('name', 'group_id', 'location', 'start', 'length'));
$this->widgetSchema['invited'] = new myWidgetFormTokenAutocompleter(array("url"=>"/user/json"));
}
In MeetingMeetings.php my save method is simply:
public function save(PropelPDO $con = null) {
$this->setOwnerId(Meeting::getUserId());
return parent::save($con);
}
However propel doesn't know about my custom field and as such doesn't do anything with it. Where and how to I put in a special section that can deal with this form field, please be aware it is not just a simple save to database, I need to deal with the input specially before it is input.
Thanks for your time and advice,
You have to define a validator (and/or create your own). The validator clean() method returns the value that needs to be persisted.
In Doctrine (I don't know Propel) the form then calls the doUpdateObject() on the form, which in turns calls the fromArray($arr) function on the model.
So if it's already a property on your model you'll only need to create the validator. If it's a more complex widget, you'll need to add some logic to the form.

Does this function have too many parameters?

In the end, I got this function. I don't know whether it's normal or not.
function user_registration($user_name, $user_email, $user_pass, $address,
$city, $postalcode, $country, $phone, $mobilephone)
How and why can I improve this?
You could either pass an array with all variables packed nicely together, or just make a "User" class and add all properties via setters and do the validation in the end with a dedicated method:
class User {
public function setName($name) {
$this->name = $name;
}
[...]
public function register() {
//Validate input
if (empty($this->name))
$this->errors[] = "ERROR, Username must not be emtpy";
//Add the user to the database
//Your SQL query
return empty($this->errors);
}
}
$user = new User();
$user->setName("Peter");
$success = $user->register();
if (!$success)
echo "ERRORS OCCURED: ".print_r($user->errors, true);
A solution would be to only have one parameter, that can contain several pieces of data -- like an array.
Your function could be defined this way :
function user_registration(array $data) {
// work with $data['name']
// and $data['email']
// ...
}
And you'd call it like this :
user_registration(array(
'name' => 'blah',
'email' => 'test#example.com',
'pass' => '123456',
// and so on
));
Nice things are :
You can add / remove "parameters" easily
The "parameters" can be passed in any order you want
Not so bad things are :
No hint while typing in your IDE
No documentation (like phpDoc)
I personally don't think it has too many parameters. From looking at the functions definition it is clear what you require as input which wouldn't be so obvious if called with an array.
"If it aint broke don't fix it!"
When you look at your argument names, you cannot but notice that they can be grouped into three different groups:
User Data: $user_name, $user_pass
Address Data: $address, $city, $postalcode, $country
Contact Data: $user_email, $phone, $mobilephone
Consequently, you could apply Introduce Parameter Object:
Often you see a particular group of parameters that tend to be passed together. Several methods may use this group, either on one class or in several classes. Such a group of classes is a data clump and can be replaced with an object that carries all of this data. It is worthwhile to turn these parameters into objects just to group the data together. This refactoring is useful because it reduces the size of the parameter lists, and long parameter lists are hard to understand. The defined accessors on the new object also make the code more consistent, which again makes it easier to understand and modify.
If you dont want to do OOP you could group the arguments into arrays as well but you'll lose all the type benefits then. I will just assume you don't mind using objects. So, after applying the Refactoring you'll end up with
function user_registration(User $user, Address $address, Contact $contact)
Looking at that parameter list should make you notice that Address and Contact likely belong to User in the first place, so you could consider changing the function signature to just
function user_registration(User $user)
and then call it like this:
$user = new User('johndoe', 'secretsauce');
$user->setAddress(new Address('Doe Street', 'Doe Town', 12345, 'Neverland'));
$user->setContact('jdoe#example.com', '+123 12345', '+123 54321');
user_registration($user);
We could probably make username and password into a Credentials object as well and then just do
user_registration(new User($credentials, $address, $contact));
By requiring the data in the ctor we make sure newly registered users do have all these information. We could argue whether we need Address and Contact for registering users though, so Setter injection might be good enough here:
$user = new User(new Credentials('johndoe', 'secretsauce'));
$user->setAddress(new Address('Doe Street', 'Doe Town', 12345, 'Neverland'));
$user->setContact(new Contact('jdoe#example.com', '+123 12345', '+123 54321'));
user_registration($user);
However, user_registration as a separate function in global scope is misplaced. By GRASP's Information Expert principle, methods should be on the objects that have the most information to fulfill the responsibility. This improves Cohesion and reduces Coupling. In other words:
$user = new User($credentials);
$user->setAddress($address);
$user->setContact($contact);
$user->register();
One problem with the User class now is that it contains the password. The password is only ever needed to authenticate the user against the authentication service. We could argue about the username, but the password definitely should not be part of the User object at all. So you should do something like
$user = new User;
$user->setAddress($address);
$user->setContact($contact);
$user->register($credentials);
and when register() is called, it will only use the credentials to delegate insertion of a new user into the user storage. But it will not retain them in the actual User instance.
Finally, you might want to add a Simple Factory or Builder pattern to encapsulate the creation of the User to simplify the aggregation of the various instances. Or you might want to introduce a Repository pattern and move the register() method there. This is beyond scope for this question though.
As a general rule of thumb (not as a steadfast rule), anytime you have to ask "Does this function have too many parameters?" -- the answer is yes. Your intuition is telling you something that your brain just hasn't yet been able to work out.
In this particular case, the first thing that comes to mind is that your user cred.s should be checked first (does the username already exist? is the pw complex enough) and your user details should be added separately, probably using an object or array.
One way is to pass an array as parameter to this function and put all info in that array:
function user_registration(array $user_info)
{
// process $user_info;
}
Function (method) without any parameters is best. Function with one paremeter is better than function with 2 parameters. Function with 2 parameters is better than function with 3 parameters and so on.
#Florianh has given a perfect solution how your code can be improved. With this comment, I would like to elaborate on the "why" part from a system design perspective.
From an Object Oriented perspective, other objects should be able to manipulate attributes. That is why attributes should never be defined as "public". They should be "private" e.g.:
private var $name;
The reason for this is when other objects would manipulate this variable, the correct working of the object is at risk. Methods, on the other hand, can be defined publicly:
public function register()
Accordingly, the manipulation of the attributes will happen through the appropriate methods. A method can be used to also evaluate the correctness of operations on the attributes.
There are two operations that can happen: reading the current value of an attribute by using Get methods and saving a new value of an attribute by using Set methods.
A good practice would be to implement a get method for each class attribute. Each attribute that can be modified, should have an appropriate set method as well.
Sometimes it is better to not implement a get/set method (e.g.: showData()) after all. This is because the usage of getters and setters within a particular class may possibly lead to a reduced performance. However, this means that when changing or implementing the class, one must be careful when trying to save false information and as a result putting the integrity of the class at risk.
Now, consider the fact that you decide to use only one phone number instead of both a phone- and mobile number. When the mobile number becomes deprecated, your main program still remains the same. All you have to do is change/remove one method. The advantage of using getters and setters is an increase in adaptability and maintainability.
I make array of keys like
$fields = array('field1', 'field2');
function register (array $values, array $keys)
{
$data = array();
foreach ($keys as $one)
{
if (isset($values[$one])) $data[$one] = $values[$one];
}
// or you can use array functions like array_flip and after - array intersect
}
I'd make it this way
fields=explode(",","name,surname,lastname,street,city,region,zip,country");
user_registration($fields);
Because I am sure these variables coming from $_POST

Categories