
The third part of the tutorial series will present you with a fully functionable solution for a login and signup page that activates a RECaptcha, via Zend_Captcha as a Service, when the user attempts to login/signup 3 times and fails. The tutorial will also show you how to use your models and how to structure your bussiness logic inside your module based application.
Continuing with the tutorial series, we will now see how to manage a login / sign up page with spam prevention via (RE)captcha provided by Zend_Captcha. We will see how to manage validation on the server side, how to contain your business logic in your models, how to prevent multiple submissions and how to communicate with your database without giving your controller any knowledge about your DAO. We will use the Repository and Factory design pattern to be able to easily Unit Test your models and to contain your business logic and database access object into separate layers. We will also see how to insert your dependencies everywhere in your objects to follow the Dependency Injection standards so that your code can be easily unit tested.
1. The database & the database config
Before we start, lets create a database with a table called users.
The table users should contain 3 columns :
- id – primary key, auto increment, not null, unsigned int
- username – varchar(128), not null
- password – varchar(32), not null
Now, in your application.ini, go to your resources.multidb.front_db and change the values to your connection string, as an example:
... resources.multidb.front_db.adapter = "pdo_mysql" resources.multidb.front_db.host = localhost resources.multidb.front_db.username = MyDatabaseUsername resources.multidb.front_db.password = MyDatabasePassword resources.multidb.front_db.dbname = MyDatabaseName resources.multidb.front_db.default = true ...
2. The layouts
We want to have a separate layout for the logged in users and another layout for guests.
To achieve this, in your /app_root/modules/frontoffice/layouts/ make sure you have 2 files called: “public.phtml” and “layout.phtml” with the following contents:
layout.phtml
< ?php echo $this->doctype(); ?> < html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> < head> < ?php echo $this->headMeta(); ?> < ?php echo $this->headTitle(); ?> < ?php echo $this->headStyle(); ?> < ?php echo $this->headLink(); ?> < script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">< /script> < ?php echo $this->headScript(); ?> < /head> < body> < div class="container"> < ?php foreach ($this->messages['error'] as $message):?> < div style="color:red;">< ?php echo $message?>< /div> < ?php endforeach;?> < ?php foreach ($this->messages['success'] as $message):?> < div style="color:green;">< ?php echo $message?>< /div> < ?php endforeach;?> < br /> < br /> < p>You are now logged in!< /p> < ?php echo $this -> layout () -> content; ?> < /div> < /body> < /html>
and in your public.phtml
< ?php echo $this->doctype(); ?> < html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> < head> < ?php echo $this->headMeta(); ?> < ?php echo $this->headTitle(); ?> < ?php echo $this->headStyle(); ?> < ?php echo $this->headLink(); ?> < script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">< /script> < ?php echo $this->headScript(); ?> < /head> < body> < div class="container"> < ?php foreach ($this->messages['error'] as $message):?> < div style="color:red;">< /div> < ?php endforeach;?> < ?php foreach ($this->messages['success'] as $message):?> < div style="color:green;">< /div> < ?php endforeach;?> < br /> < br /> < ?php echo $this -> layout () -> content; ?> < /div> < /body> < /html>
This way, we can show a custom UI for the logged in users and another UI for the guests.
3. The Auth Controller Plugin
We now want to make sure that guests are redirected to the users/login page (we will talk about that controller/action in a moment).
Also, if the user is already authenticated, we do not want to allow him on the users/login page and he should be redirected to the index/index page
To achieve this, we will create a controller plugin in the /app_root/library/Custom/Controller/Plugin called Auth.php with the following contents:
class Custom_Controller_Plugin_Auth extends Zend_Controller_Plugin_Abstract
{
/**
* @var Zend_Auth
*/
protected $_auth;
public function __construct(Zend_Auth $auth)
{
$this->_auth = $auth;
}
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
//Check if the user is not logged in
if (!$this->_auth->hasIdentity())
{
return $this->_redirect($request, 'users', 'login', 'frontoffice');
}
//The user is logged in
//Check if the authenticated user tries to access the users/login path
if ('frontoffice' == $request->getModuleName()
&& 'users' == $request->getControllerName()
&& 'login' == $request->getActionName())
{
return $this->_redirect($request, 'index', 'index', 'frontoffice');
}
}
protected function _redirect($request, $controller, $action, $module)
{
if ($request->getControllerName() == $controller
&& $request->getActionName() == $action
&& $request->getModuleName() == $module)
{
return TRUE;
}
$url = Zend_Controller_Front::getInstance()->getBaseUrl();
$url .= '/' . $module
. '/' . $controller
. '/' . $action;
if (DEBUG)
{
debug_redirect($url);
}
return $this->_response->setRedirect($url);
}
}
To run this plugin we need to go to the Application Bootstrap (the one from the app_root) and add the following function :
//init Auth Plugin
protected function _initAuthPlugin()
{
Zend_Controller_Front::getInstance()->registerPlugin(
new Custom_Controller_Plugin_Auth(Zend_Auth::getInstance()));
}
Now this basically tells our application to redirect to the /frontoffice/users/login in case we are not logged in (and it will avoid a redirect loop also if we are already there)
4. The Users Controller
The users controller will allows us to manage a signup and a login action for our guests. The controller will only handle the request and will communicate with the view to render the form and error messages. It will also communicate with the User Repository for user actions (to find out if the user already exists or if the form is valid or not).
Thus, in your /app_root/modules/frontoffice/controllers/ create the file UsersController with the following contents:
class UsersController extends Frontoffice_Library_Controller_Action_Abstract
{
public function indexAction()
{
return $this->redirect('users', 'login');
}
public function loginAction()
{
Zend_Layout::getMvcInstance()->setLayout('public');
$form = new Frontoffice_Form_Login(
array('userRepository' => Frontoffice_Model_Repositories_UsersFactory::factory()));
if ($this->_request->isPost())
{
$data = $this->_request->getPost();
if ($form->isValid($data))
{
return $this->redirect('index', 'index');
}
$form->setDefaults($data);
}
$this->view->form = $form;
}
public function logoutAction()
{
Zend_Auth::getInstance()->clearIdentity();
return $this->redirect('users', 'login');
}
}
This tells our application to redirect to the users/login in case we are trying to access the users/index path. If we are already on the login action, it simply instantiates a new form and injects the Users Repository.
If the request is actually a post and the form is valid with the data provided via the POST method, then it redirects the user to the index/index
Also, the action sets the layout to the public layout since the guest is not allowed to see our logged in UI.
You may ask yourself where are the errors treated. These are set in the form directly (either from form or model validation) and will be displayed in the view from the form so the controller is not aware of them
Also, if we go to users/logout we will be redirected to the users/login and our identity will be deleted
5. The Login View
Lets create the login.phtml view now in your /app_root/modules/frontoffice/views/scripts/users/ with the following contents:
< div style="width:960px; margin:100px auto;">
< h2>Login header
< div style="width:500px; margin:5px; padding:0px 20px; float:left">
< h3>Please login< /h3>
< ?php if ($this->form->isErrors()):?>
< ?php foreach ($this->form->getErrors() as $errors):?>
< ?php foreach ($errors as $message):?>
< div style="color:red">< /div>
< ?php endforeach;?>
< ?php endforeach;?>
< ?php foreach ($this->form->getErrorMessages() as $error):?>
< div style="color:red">< /div>
< ?php endforeach;?>
< ?php endif; ?>
< form id="login" action="< ?php echo $this->escape($this->form->getAction()); ?>" method="< ?php echo $this->escape($this->form->getMethod());?>">
Username: < ?php echo $this->form->username;?>< br />< br />
Password: < ?php echo $this->form->password;?>< br />< br />
< ?php echo $this->form->captcha;?>
< input type="submit" value="Login" /> or < a href="< ?php echo $this->url(array('controller' => 'users', 'action' => 'signup'), null, true);?>">Sign up< /a>
< ?php echo $this->form->___h;?>
< br />
< br />
< /form>
< /div>
< /div>
It is a simple view which renders some form elements and the captcha (the form takes care to enable it or not)
6. The Login Form
The login form will allow us to define the elements we want to show; it will also be injected with the user repository so it can query it for an existent username and fail if it finds one but also user the repository to do the validation. I am not using form validation for elements that contain business logic. As an example, the “Not Empty” validation is done by the form while the requirements for the elements is done by the user repository.
The error messages are set in the form, by both the form and the repository, based on the source of it.
If the form contains errors (either from the element validators or from the business logic validators), they will be shown via the view.
If you the form gives an error 3 times, the captcha will be enabled and will now allow you to pass the form validation until you enter the right captcha code (note here that if you are behind a proxy, the captcha validation will not work)
Now lets create the form class. In your /app_root/modules/frontoffice/forms/ create a file called Login.php with the following contents:
class Frontoffice_Form_Login extends Custom_Form
{
/**
* @var Admin_Model_Repositories_Users
*/
protected $_user_repository;
public function setUserRepository( $user_repository)
{
$this->_user_repository = $user_repository;
}
public function __construct($options = null)
{
parent::__construct($options);
$this->setName('login');
$element = new Zend_Form_Element_Text('username', array('disableLoadDefaultDecorators' => true));
$element->addDecorator('ViewHelper')
->setRequired(true)
->addErrorMessage('The username is required.');
$this->addElement($element);
$element = new Zend_Form_Element_Password('password', array('disableLoadDefaultDecorators' => true));
$element->addDecorator('ViewHelper')
->setRequired(true)
->addErrorMessage('The password is required.');
$this->addElement($element);
$element = new Zend_Form_Element_Hash('___h', array('disableLoadDefaultDecorators' => true));
$element->setSalt('unique')
->addDecorator('ViewHelper')
->addErrorMessage('Form must not be resubmitted');
$this->addElement($element);
$captcha_session = new Zend_Session_Namespace('captcha');
if ($captcha_session->tries > 999)
{
$recaptcha = new Zend_Service_ReCaptcha('API_KEY_HERE',
'API_KEY_HERE');
$recaptcha->setOption('theme', 'clean');
$element = new Zend_Form_Element_Captcha('captcha',
array('disableLoadDefaultDecorators' => true,
'captcha' => 'ReCaptcha',
'captchaOptions' => array('captcha' => 'ReCaptcha',
'service' => $recaptcha)));
$element->addErrorMessage('Invalid security captcha code');
$this->addElement($element);
}
$this->clearDecorators();
$this->addDecorator('FormElements')
->addDecorator('Form');
}
public function isValid($data)
{
if (parent::isValid($data))
{
if ($this->_user_repository->authenticate($data['username'], $data['password']))
{
Zend_Session::namespaceUnset('captcha');
return TRUE;
}
else
{
$this->setErrors(array('Invalid username or password'));
}
}
$captcha_session = new Zend_Session_Namespace('captcha');
if (empty($captcha_session->tries))
{
$captcha_session->tries = 0;
}
$captcha_session->tries = $captcha_session->tries + 1;
return FALSE;
}
}
7. The Users Repository (Model)
Now lets see what the authenticate method does:
Go into your /app_root/modules/frontoffice/models/Repositories/ and create the file Users.php with the following contents:
class Frontoffice_Model_Repositories_Users
{
/**
* @var Admin_Model_Entities_User
*/
protected $_user_entity;
/**
*
* @param Admin_Model_Entities_User $user_entity
*/
public function __construct($user_entity)
{
$this->_user_entity = $user_entity;
}
/**
* var array
*/
protected $_messages = array();
/**
* Sets an error message
*
* @param string $name
* @param string $message
*
* @return bool
*/
public function setMessage($name, $message)
{
$this->_messages[$name] = $message;
return TRUE;
}
/**
* Returns a list of all error messages
*
* @return array
*/
public function getMessages()
{
return $this->_messages;
}
/**
* Authenticates a user based on the username and password
*
* @param string $username
* @param string $password
*
* @return boolean
*/
public function authenticate($username, $password)
{
$filter = new Zend_Validate_StringLength(array('min' => 5, 'max' => 25));
if (!empty($password) && !$filter->isValid($password))
{
$this->setMessage('password', 'Invalid password. Length must be between 5 and 25 characters');
return FALSE;
}
if (TRUE === $this->_user_entity->loginByUsernameAndPassword($username, $password))
{
$storage = $this->_user_entity->getResultRowObject(array(
'id',
'username'));
$storage->name = $storage->username;
Zend_Session::rememberMe(60 * 60 * 24 * 7 * 2);
Zend_Auth::getInstance()->getStorage()->write($storage);
return TRUE;
}
return FALSE;
}
The Repository uses composition and gets injected with the user entity object which provides the interface to communicate with the database.
The Repository holds the validation of the parameters and manages the relationship between the controller and the database model.
We will have one method called authenticate will use basically run the validation code of the parameters and if it passes the validation, it reaches the User Entity and tries to query it there. It always returns a boolean based on the status.
To call the repository, we will use the factory (below) to get the object. Create a new file in your /app_root/modules/frontoffice/models/Repositories/ named UsersFactory.php with the following contents:
class Frontoffice_Model_Repositories_UsersFactory
{
/**
* @var Frontoffice_Model_Repositories_Users
*/
protected static $_repository;
public static function setRepository($repository)
{
self::$_repository = $repository;
}
/**
* @return Frontoffice_Model_Repositories_Users
*/
public static function factory()
{
if (null !== self::$_repository)
{
return self::$_repository;
}
$user_entity = new Frontoffice_Model_Entities_User();
return new Frontoffice_Model_Repositories_Users($user_entity);
}
}
8. The User Entity
Now we will see the actual User Entity which provides the interface which communicates with the database itself:
Create a file in your /app_root/modules/frontoffice/models/Entities/ with the name User.php with the following contents:
class Frontoffice_Model_Entities_User extends Custom_Db_Table_Abstract
{
protected $_name = 'users';
protected $_use_adapter = 'front_db';
protected $_auth_adapter;
const PASSWORD_HASH = 'MY_PASSWORD_HASH_WHICH_SHOULD_BE_SOMETHING_SECURE';
/**
* Logins a user based on his username and password
*
* @param string $username
* @param string $password
*
* @return Zend_Auth_Result
*/
public function loginByUsernameAndPassword($username, $password)
{
$password = $this->_encryptPassword($password);
$this->_auth_adapter = new Zend_Auth_Adapter_DbTable( $this->getAdapter() );
$this->_auth_adapter->setTableName('users')
->setIdentityColumn('username')
->setCredentialColumn('password');
$this->_auth_adapter->setIdentity($username)
->setCredential($password);
$result = Zend_Auth::getInstance()->authenticate($this->_auth_adapter);
return $result->isValid();
}
/**
* Returns the result row as a stdClass object
*
* @param string|array $returnColumns
* @param string|array $omitColumns
* @return stdClass|boolean
*/
public function getResultRowObject($returnColumns, $omitColumns = array())
{
return $this->_auth_adapter->getResultRowObject($returnColumns, $omitColumns);
}
/**
* Encrypts a value by md5 + static token
* 10 times
*
* @param string $value
*
* @return string $value
*/
protected function _encryptPassword($value)
{
for ($i = 0; $i < 10; $i++)
{
$value = md5($value . self::PASSWORD_HASH);
}
return $value;
}
The User Entity provides methods to C.R.U.D. the database via the Repository.
In our case, we want to be able to run the Zend_Auth_Adapter_DbTable on our database and retrieve our needed information in order to write them in the Auth Storage.
Also we will use a method to encrypt our password va multi md5, in a secure way.
Now if you run the application you should get 5 types of error messages:
- Username empty
- Password empty
- Form cannot be resubmitted
- Invalid captcha code
- Invalid username or password (this is due to the fact that a hacker should not know what went wrong with his u/p combination.
To test that the form works correctly check ZFDebug queries and see what it tried to search in the database (the username and password combination - the password will be encrypted. Copy paste that password and add it manually to the database, to your username. Now retry and you should get redirected to the index/index and be logged in)
9. The Sign Up action
Now that we have the login done, lets go to the UsersController and create our signup method:
Add the following code to your UsersController.php
//...
//previous code here
//...
public function signupAction()
{
Zend_Layout::getMvcInstance()->setLayout('public');
$user_repository = Frontoffice_Model_Repositories_UsersFactory::factory();
$form = new Frontoffice_Form_Signup(array('userRepository' => $user_repository));
if ($this->_request->isPost())
{
$data = $this->_request->getPost();
if ($id = $form->isValid($data))
{
$user_repository->authenticate($data['username'], $data['password']);
return $this->redirect('index','index');
}
$form->setDefaults($data);
}
$this->view->form = $form;
}
Basically, the code is almost identical (at this level of simplicity atleast) with the login action.
If the form subbmision is correct, make sure to authenticate the user and redirect him to the index/index page.
10. The Sign Up View
Now lets see the View for the Sign Up action:
Go into your app_root/modules/frontoffice/views/scripts/users and create a file called login.phtml with the following contents:
< div style="width:950px; margin:5px; padding:0px 20px; float:left">
< h2>Create account:< /h2>
< ?php if ($this->form->isErrors()):?>
< ?php foreach ($this->form->getErrors() as $errors):?>
< ?php foreach ($errors as $message):?>
< div style="color:red">< ?php echo $message;?>< /div>
< ?php endforeach;?>
< ?php endforeach;?>
< ?php foreach ($this->form->getErrorMessages() as $error):?>
< div style="color:red">< ?php echo $error;?>< /div>
< ?php endforeach;?>
< ?php endif; ?>
< form id="login" action="< ?php echo $this->escape($this->form->getAction()); ?>" method="< ?php echo $this->escape($this->form->getMethod());?>">
Username: < br />< ?php echo $this->form->username;?>< br />< br />
Password: < ?php echo $this->form->password;?> < br />< br />
Confirm Password: < ?php echo $this->form->confirm_password;?>< br />< br />
< ?php echo $this->form->captcha;?>
< input type="submit" value="Create account" /> or < a href="< ?php echo $this->url(array('controller' => 'users', 'action' => 'login'), null, true);?>">Login
< ?php echo $this->form->___h;?>
< /form>
< /div>
11. The Auth Plugin
Currently, if we try to signup we will always get redirected to the login page. In order to allows us to view the signup page, we must go to the app_root/library/Custom/Controller/Plugin/Auth.php and there we should add the following condition in the dispatchLoopStartup :
//Check if the user is not logged in
if (!$this->_auth->hasIdentity())
{
return $this->_redirect($request, 'users', 'login', 'frontoffice');
}
becomes
//Check if the user is not logged in
if (!$this->_auth->hasIdentity()
&& FALSE === ( 'frontoffice' == $request->getModuleName()
&& 'users' == $request->getControllerName()
&& 'signup' == $request->getActionName()))
{
return $this->_redirect($request, 'users', 'login', 'frontoffice');
}
This gives us permissions to view the users/signup page
12. The Users Repository (again)
//previous code here
//...
/**
* Creates a new user
*
* @param $username
* @param $password
*
* @return bool|int
*/
public function createUser($username, $password)
{
$filter = new Zend_Validate_StringLength(array('min' => 5, 'max' => 25));
if (!$filter->isValid($password))
{
$this->setMessage('password', 'The password must be between 5 and 25 characters length');
return FALSE;
}
if (FALSE !== $this->_user_entity->findByUsername($username))
{
$this->setMessage('username', 'Username already exists');
return FALSE;
}
if (!$id = $this->_user_entity->create($username, $password))
{
$this->setMessage(null, 'An uknown error occured. Please contact the support team');
return FALSE;
}
return $id;
}
This allows us to call the createUser with the username and password and the system checks if the values are correct. We have the logic validation on the repository side while the passowrd/confirm password will be kept on the form side
If the validation passses, we then check to see if we don't already have another user with the same username.
If we don't, we try to create the user with the username and password and if this works we return back the new user id
13. The Users Entity (again)
Go to the /app_root/modules/frontoffice/models/Entities/User.php and add the following code after the previously added one:
//previous code here
//...
/**
* Retrieves an User Object by its ID
*
* @param integer $user_id
*
* @return Zend_Db_Table_Row_Abstract|bool
*/
public function findByUsername($username)
{
$result = $this->fetchRow($this->getAdapter()->quoteInto('email = ?', $username));
if (!empty($result))
{
return $result;
}
return FALSE;
}
/**
* Creates a new user object in the database with the specified
* column values
*
* @param string $username
* @param string $password
*
* return bool|integer
*/
public function create($username, $password)
{
$user = $this->createRow();
$user->email = $username;
$user->password = $this->_encryptPassword($password);
try
{
$user->save();
return $user->id;
}
catch (Exception $e)
{
debug($e);
return FALSE;
}
}
These two methods provide the interface to the Database Object for the Repository via the Entity.
14. Last but not least, The Sign Up Form
The sign up form will be injected with the user repository, will create the username,password/confirm password elemenets, will provide validation for the empty state of the fields + check to see if the 2 password fields are identical and will enable the captcha if the form fails 3 times.
If the form validation is valid, it runs the repository createUser() method. If this validation fails, it will set the form error messages based on the repository messages
If it passes, then it will return the $id of the newly created user
Once the user is created, the controller will just authenticate the user via the standard authenticate() method from the repository with the provided userame and password from the create form.
In your app_path/modules/frontoffice/forms create the file Signup.php with the following contents:
class Frontoffice_Form_Signup extends Custom_Form
{
/**
* @var Frontoffice_Model_Repositories_Users
*/
protected $_user_repository;
/**
* Sets the user repository
*
* @param Frontoffice_Model_Repositories_Users $repository
*/
public function setUserRepository($repository)
{
$this->_user_repository = $repository;
}
public function __construct($options = null)
{
parent::__construct($options);
$this->setName('create_account');
$element = new Zend_Form_Element_Text('username', array('disableLoadDefaultDecorators' => true));
$element->addDecorator('ViewHelper')
->setRequired(true)
->addErrorMessage('Please provide an username value');
$this->addElement($element);
$element = new Zend_Form_Element_Password('password', array('disableLoadDefaultDecorators' => true));
$element->addDecorator('ViewHelper')
->setRequired(true)
->setAttrib('autocomplete', 'off')
->addErrorMessage('Please enter a password');
$this->addElement($element);
$element = new Zend_Form_Element_Password('confirm_password', array('disableLoadDefaultDecorators' => true));
$element->addDecorator('ViewHelper')
->setRequired(true)
->addValidator(new Frontoffice_Form_Validate_IdenticalFormValues('password'), true)
->addErrorMessage('The two passwords do not match');
$this->addElement($element);
$element = new Zend_Form_Element_Hash('___h', array('disableLoadDefaultDecorators' => true));
$element->setSalt('unique')
->addDecorator('ViewHelper')
->addErrorMessage('Form must not be resubmitted');
$this->addElement($element);
$captcha_session = new Zend_Session_Namespace('captcha');
if ($captcha_session->tries > 3)
{
$recaptcha = new Zend_Service_ReCaptcha('6LeDkroSAAAAAHAe8FnYK2e9-jbLdAbk8XXn_0UK',
'6LeDkroSAAAAACLmAdTSdM9sifKJWERFDRUSB0So');
$recaptcha->setOption('theme', 'clean');
$element = new Zend_Form_Element_Captcha('captcha',
array('disableLoadDefaultDecorators' => true,
'captcha' => 'ReCaptcha',
'captchaOptions' => array('captcha' => 'ReCaptcha',
'service' => $recaptcha)));
$element->addErrorMessage('Invalid security captcha code');
$this->addElement($element);
}
$this->clearDecorators();
$this->addDecorator('FormElements')
->addDecorator('Form');
}
public function isValid($data)
{
if (!parent::isValid($data))
{
$this->_incrementCaptcha();
return FALSE;
}
if (!$id = $this->_user_repository->createUser($data['username'],
$data['password']))
{
foreach ($this->_user_repository->getMessages() as $element_name => $message)
{
$this->addErrors(array($message));
}
$this->_incrementCaptcha();
return FALSE;
}
return $id;
}
protected function _incrementCaptcha()
{
$captcha_session = new Zend_Session_Namespace('captcha');
if (empty($captcha_session->tries))
{
$captcha_session->tries = 0;
}
$captcha_session->tries = $captcha_session->tries + 1;
}
}
However, we are not done yet, since we have a custom validator that check if 2 fields are identical. Thus, lets create, in the path /app_path/modules/frontoffice/forms/Validate the file called IdenticalFormValues.php with the following contents:
class Frontoffice_Form_Validate_IdenticalFormValues extends Zend_Validate_Abstract
{
const NOT_MATCH = 'notMatch';
protected $_messageTemplates = array(
self::NOT_MATCH => 'Values don\'t match'
);
protected $_token_key;
public function __construct($token_key = 'confirm_password')
{
$this->_token_key = $token_key;
}
public function isValid($value, $context = null)
{
$value = (string) $value;
$this->_setValue($value);
if (is_array($context))
{
if (isset($context[$this->_token_key]) && ($value == $context[$this->_token_key]))
{
return true;
}
}
elseif ($value == $context)
{
return true;
}
$this->_error(self::NOT_MATCH);
return false;
}
}
15. Finale
This was a big tutorial but should pretty much cover everything in order to build a secure signup/login system
See you until next time for the next tutorial!





