I'm writing this in PHP, but, generally speaking, what methods and properties would a user (registration?) class have? I know I can use MDB2 from PEAR for database abstraction, and one feature I can think of is making sure the username is valid.
$user->validateUserName($username, $regex);
Can anybody think of any more?
Thanks.
Edit:
Username validation would be a class of its own right (Validator class or something)?
Well, you should check if
Username is already in use
Password is strong enough
User's Mail address is valid
You also need methods to
create a new account
change User's data
delete an existing account?
allow Users to recover forgotten pws
There shouldn't be a such class at all. Registration is a whole process of receiving data, validating that data and finally, processing validated data if validation succeed. In other words: registration is that part of code which uses some object to solve a given problem.
What will you need?
An object that represents a single user.
An object that takes User object and saves it.
Some validation mechanism (it will use at least several objects).
Let's design an interface. How will we use the code we're going to write?
try {
$validator = new Validator($_POST);
$validator->addRules(array(
'username' => new LengthValidator(array('min' => 3, 'max' => 15)),
'password' => array(
new LengthValidator(array('min' => 6))
new RegexpValidator(array('pattern' => '#...#'))
),
'email' => array(
new EmailValidator(),
new UniqueValidator(....)
)
));
$data = $validator->validate();
$userService = new UserService($databaseHandler, $someOtherArguments);
$user = new User();
$user->setUsername($data['username']);
$user->setPassword($data['password']);
$user->setEmail($data['email']);
$user->setFirstname($data['firstname']);
$user->setLastname($data['lastname']);
$userService->save($user);
} catch (ValidationException $ve) {
// validation failed
}
That's of course only an example - it can be designed totally different way.
ValidForm, it's a great class structured to handle your forms both client/server-sided. However, no MySQL is passed through the form.
Related
This may be a very simple question about ZF2, but I can't get my head around it. So please bear with me.
Suppose I create a user registration form. It has 2 inputs: username and password. In the model, I create a class User that has $username and $password variables, and the setters + getters for the two variables.
My question is how to pass what a user writes into the HTML inputs to the corresponding setters? Obviously, it has to do with the $_POST array. But how is it done internally in ZF2? What should I use to pass the username input to the actual $username variable?
You will need to use a hydrator to populate the model's data. An example would be Zend\Stdlib\Hydrator\ClassMethods which accepts an array (such as the post data) and calls the setters of the target object.
$hydrator = new \Zend\Stdlib\Hydrator\ClassMethods;
$user = new \User\Model\User;
$data = [
'username' => 'foo',
'password' => 'bar',
];
$hydrator->hydrate($data, $user);
echo $user->getUsername(); // foo
Keep in mind however that you will need to ensure that you sanitize all user supplied data (e.g hash the password) as well as validate the form data is correct (e.g. ensure a minimum complexity of the password, or to ensure valid e-mail address for the username).
The Zend\Form component is designed to integrate all these requirements as well as allow you to construct the forms HTML output. You would attach the hydrator to the form and once is has been validated you can retrieve a constructed entity populated will the user supplied data using $form->getData().
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
// hydration occurs internally and returns our user populated
$user = $form->getData();
if ($user instanceof User) {
echo $user->getUsername();
}
}
}
The ZF2 tutorial gives a detailed explanation of the process, if you have not already created the example project I highly recommend doing so.
If your form is posting, the values will be stored within the post with the input ids as the keys. You can use a couple of ways to access them from there.
$this->getRequest()
Will get everything, then you can use ->getParams() to get all the post parameters or even ->getParam('username') to get just the ones you need.
I am using CakePHP for my application, where I have a User model. This User has a password, which has a regex to validate.
The regex forces the user to use a password at least 6 characters long, containing at least 1 number and special char.
The validation looks like this:
'password' => array(
'ruleName' => array(
'rule' => array('custom', '/^.*(?=.{6,})(?=.*\d)(?=.*[a-zA-Z])(?=.*[##$%^&+=]).*$/i'),
'message' => 'Password not legit',
'allowEmpty' => true
)
)
When I want to edit my password, this validation works great. But when I want to edit the user (no option to change password there), the $this->User->save() fails.
If I debug my $this->User->validationErrors, the only thing shown is:
array(
'password' => '*****'
)
The password field is not set in my post data, so the validation should not happen at all.
When I comment this block of validation code, the user can be saved.
Anyone knows what I am doing wrong?
I solved it myself already. Before saving the User object, I already did a $this->User->Read(null, $userid) for other purposes.
This resulted in remembering the values of the read (including password) in the $this->User object.
Since the save method is called on the $this->User object, the password value is trying to get saved too. But since *** isn't valid according to the regex, the save fails.
Thanks for the help anyway!
I was investigating CakePHP (2.3.4) Model::save method to insert new records and ran into a little snag:
//Two fields provided, email field intended to fail validation
$data = array('Member' => array(
'username' => 'hello',
'email' => 'world'
));
$this->Member->create();
var_dump($this->Member->save($data, true));
The above save() will return false and no data will be written to the database. However if I change the data to:
//One field provided, intended to pass validation
$data = array('Member' => array(
'username' => 'hello'
));
then save() will attempt to write a new record to database with a blank email field. I realize that skipping validation for unspecified fields might be a useful behavior during updates, but is there a CakePHP recommended way to handle partially empty data sets when creating new records? Thanks a lot.
Edit:
Thanks to Sam Delaney for the tip. In case anybody else gets stumped, this did the trick: CakePHP Data Validation: field 'required' key
This key accepts either a boolean, or create or update. Setting this key to true will make the field always required. While setting it to create or update will make the field required only for update or create operations. If ‘required’ is evaluated to true, the field must be present in the data array. For example, if the validation rule has been defined as follows:
I had originally baked the model and forgotten that required => true was left out of the validation rule. Setting it to true or 'create' would've avoided me blank records getting inserted due to gibberish data array.
IMHO what you've experienced is the desired behavior. Consider that you have the same two fields within a form and the user provides a value for only username. Both username and email field are submitted even though email is empty. Then on the server, you try to save the record only to find out that it has failed validation which you feedback to the user. On the other hand, perhaps in your system it is perfectly possible to create a user without requiring an email (for example root access account), CakePHP's validation implementation allows both of these scenarios.
If this flexibility isn't what you desire, just set the required attribute for your validation rule as true:
public $validate = array(
'email' => array(
'rule' => 'email',
'required' => true
)
);
This will satisfy your all or nothing requirement.
I've done quite a few Lithium tutorials (links below in case they help someone else, and also to show I've done my homework:) and I understand the most basic parts of creating models, views, controllers and using MVC to create a DB record based on form input.
However, I'm new to MVC for webapps and Lithium, and I'm not sure how I should write my code in more complicated situations. This is a general question, but two specific validation questions that I have are:
How should I validate date data submitted from the form?
How should I check that the two user email fields have the same value?
I would be very grateful for any help with these questions, and concrete examples like this will also really help me understand how to do good MVC coding in other situations as well!
Date entry - validating data split across multiple form inputs
For UI reasons, the sign up form asks users to enter their DOB in three fields:
<?=$this->form->field('birthday', array('type' => 'select', 'list' => array(/*...*/))); ?>
<?=$this->form->field('birthmonth', array('type' => 'select', 'list' => array(/*...*/))); ?>
<?=$this->form->field('birthyear', array('type' => 'select', 'list' => array(/*...*/))); ?>
What is the best way to validate this server-side? I think I should take advantage of the automagic validation, but I'm not sure of the best way do that for a set of variables that aren't really part of the Model. E.g.:
Should I post-process the $this->request->data in UsersController? E.g. modify $this->request->data inside UsersController before passing it to Users::create.
Should I pull the form fields out of $this->request->data and use a static call to Validator::isDate inside UsersController?
Is there a way to write a validation rule in the model for combinations of form variables that aren't part of the model?
should I override Users::create and do all the extra validation and post-processing there?
All of these seem like they could work, although some seem a little bit ugly and I don't know which ones could cause major problems for me in the future.
[EDIT: Closely related to this is the problem of combining the three form fields into a single field to be saved in the model]
Email entry - checking two form fields are identical, but only storing one
For common sense/common practice, the sign up form asks users to specify their email address twice:
<?=$this->form->field('email_address'); ?>
<?=$this->form->field('verify_email_address'); ?>
How can I write an automagic validation rule that checks these two form fields have the same value, but only saves email_address to the database?
This feels like it's pretty much the same question as the above one because the list of possible answers that I can think of is the same - so I'm submitting this as one question, but I'd really appreciate your help with both parts, as I think the solution to this one is going to be subtle and different and equally enlightening!
[EDIT: Closely related to this is the problem of not storing verify_email_address into my model and DB]
Some background reading on Lithium
I've read others, but these three tutorials got me to where I am with users and sign up forms now...
Blog tutorial
Extended blog tutorial
MySQL blog tutorial
Some other StackOverflow questions on closely related topics (but not answering it and also not Lithium-specific)
One answer to this question suggests creating a separate controller (and model and...?) - it doesn't feel very "Lithium" to me, and I'm worried it could be fragile/easily buggy as well
This wonderful story convinced me I was right to be worried about putting it in the controller, but I'm not sure what a good solution would be
This one on views makes me think I should put it in the model somehow, but I don't know the best way to do this in Lithium (see my bulleted list under Date Entry above)
And this Scribd presentation asked the question I'm hoping to answer on the last page... whereupon it stopped without answering it!
NB: CakePHP-style answers are fine too. I don't know it, but it's similar and I'm sure I can translate from it if I need to!
I'd recommend doing this in the Model rather than the Controller - that way it happens no matter where you do the save from.
For the date field issue, in your model, override the save() method and handle converting the multiple fields in the data to one date field before calling parent::save to do the actual saving. Any advanced manipulation can happen there.
The technique described in your comment of using a hidden form field to get error messages to display sounds pretty good.
For comparing that two email fields are equal, I'd recommend defining a custom validator. You can do this in your bootstrap using Validator::add.
use lithium\util\Validator;
use InvalidArgumentException;
Validator::add('match', function($value, $format = null, array $options = array()) {
$options += array(
'against' => '',
'values' => array()
);
extract($options);
if (array_key_exists($against, $values)) {
return $values[$against] == $value;
}
return false;
});
Then in your model:
public $validates = array(
"email" => array(
"match",
"message" => "Please re-type your email address.",
"against" => "email2"
)
);
Edit: Per the comments, here's a way to do custom rule validation in a controller:
public function save() {
$entity = MyModel::create($this->request->data);
$rules = array(
"email" => array(
"match",
"message" => "Please re-type your email address.",
"against" => "email2"
)
);
if (!$entity->validates($rules)) {
return compact('entity');
}
// if your model defines a `$_schema` and sets `$_meta = array('locked' => true)`
// then any fields not in the schema will not be saved to the db
// here's another way using the `'whitelist'` param
$blacklist = array('email2', 'some', 'other', 'fields');
$whitelist = array_keys($entity->data());
$whitelist = array_diff($whitelist, $blacklist);
if ($entity->save(null, compact('whitelist'))) {
$this->redirect(
array("Controller::view", "args" => array($entity->_id)),
array('exit' => true)
);
}
return compact('entity');
}
An advantage of setting the data to the entity is that it will be automatically prefilled in your form if there's a validation error.
I'm new to CakePHP and I'm stuck in reading a Model using other fields. I did a cake bake command to generate a simple users CRUD. I can view the user using the url CakePHP provided.
/users/view/1
I can view the user using id = 1. What if I want to view a user by name instead of id?
/users/view/username
By default the view function reads the User model by id.
$this->User->read(null, $id)
Thank you.
you can use find function or findBy<Field>() in your case findByUsername()
check this
I've never used cakePHP myself but I'm going to suggest that you will likely have to implement a new user model method, something like getUserByUsername($username)
This would then in turn interface with your DAL that would get the details of that user based on the username and return a user object that can be used however you wish...
It seems that CakePHP is focusing to deprecate some functions, such as findAll(). Perhaps soon the magic methods such as findBy<field>() will have the same fate.
I can recommend what martswite is suggesting, you should create your custom function:
function findUser($username=''){
return $this->find('first', array(
'conditions' => array(
'User.username' => $username
)
));
}
Perhaps you have a status field, maybe the profile isn't public, you can add a condition:
function findUser($username=''){
return $this->find('first', array(
'conditions' => array(
'User.username' => $username,
'User.status' => 1
)
));
}
I think that's more modular than findBy<Field>.