So I'm making this login-app, and I've got troubles displaying the correct error-messages upon register. I want all the exceptions to be thrown and not just one exception in a "try...catch"-method.
So this is the setup:
// EXTENDED EXCEPTION CLASSES
class AException extends Exception {
public function __construct($message = null, $code = 0) {
echo $message;
}
}
class BException extends Exception {
public function __construct($message = null, $code = 0) {
echo $message;
}
}
// INDEX.PHP
try {
$register = new RegisterController();
} catch (AException | BException $e) {
$e->getMsg();
}
I have several factors that may trigger the exception, and I would like all the exceptions to be triggered and captured e.g. if the register form was posted empty, there should be one exception for username being empty, another exception for password being empty etc..
class RegisterController {
public function __construct() {
if (!empty($_POST)) {
$this->checkUserInput();
$this->checkPassInput();
}
}
//... executing code
private function checkUserInput() {
if (strlen($_POST['username']) < 3) { // check character length bigger than 3
throw new \AException("Username has too few characters.");
}
}
private function checkPassInput() {
if (strlen($_POST['password']) < 3) { // check character length bigger than 3
throw new \BException("Password has too few characters.");
}
}
}
So, how do I make my "try...catch"-method echo both the thrown exceptions? Is it possible?
Right now only the first thrown exception-message is displayed, so I guess I need to find some way for the script to continue after an exception has been thrown...
P.S. To clarify further: if a register form is posted with empty input-fields e.g. both username and password input is empty, I want the code to echo two exception-messages, both "Username has too few characters." and "Password has too few characters.".
That's not how the try/catch mechanism is supposed to work. It is not meant to report notices to end users, but to programmatically take action if an undesired situation occurs.
What you want is a simple form validation:
class RegisterController {
public $errors = [];
public function __construct() {
if (!empty($_POST)) {
$this->checkUserInput();
$this->checkPassInput();
}
private function checkUserInput() {
if (strlen($_POST['username']) < 3) { // check character length bigger than 3
$this->errors[] = "Username has too few characters.";
}
}
private function checkPassInput() {
if (strlen($_POST['password']) < 3) { // check character length bigger than 3
$this->errors[] = "Password has too few characters.";
}
}
}
Then you can use something like:
$register = new RegisterController();
if (!empty($register->errors)) {
foreach ($register->errors as $error) {
echo '<div class="error">' . $error . '</div>';
}
}
Related
I have a login page with two radio button(buyer or seller)
For example, I chose buyer the I will get a login field
Mobile
Password
Or, I chose seller the I will get a login field
Email
Password
I am using the below function for the login code.
function login($pdo){
$account_type=sanitize_data($_POST['account_type']);
$password =sanitize_data($_POST['password']);
if (empty($account_type)) {
$errorMsg= "Please select account type";
$code= "1" ;
}
elseif ($account_type==1) {
$mobileno=sanitize_data($_POST['mobileno']);
if(empty($mobileno)) {
$errorMsg= "Please enter mobile number.";
$code= "2";
}
elseif(is_numeric(trim($mobileno)) == false){
$errorMsg= "Please enter numeric value.";
$code= "2";
}elseif(strlen($mobileno)<10){
$errorMsg= "Number should be ten digits.";
$code= "2";
}
else{
// echo "<pre>";
echo "4";// getting issue here
}
}
elseif ($account_type==2) {
if(empty($email)){
$errorMsg="You did not enter a email.";
$code="2";
} //check for valid email
elseif(filter_var($email, FILTER_VALIDATE_EMAIL) === false){
$errorMsg= "You did not enter a valid email.";
$code="2";
}
else{
}
}
elseif( empty($password)) {
$errorMsg= "Please enter the password";
$code="3";
}
else{
try{
//query here
} catch(PDOExecption $e) {
$dbh->rollback();
print "Error!: " . $e->getMessage() . "</br>";
}
}
}
Now, I am getting issues in nested if condition.
For example, I choose buyer and I added 10 digits mobile number and submitted the form.
According to my code, it should check the server side validation for the field like the password is entered or not. right?
But it stops. I mean if the server-side validation is clear for mobile then it should check the next one but it's not checking.
I mean it's not checking the password. I think my execution is stopped once reach the else part.
elseif ($account_type==1) {
$mobileno=sanitize_data($_POST['mobileno']);
if(empty($mobileno)) {
$errorMsg= "Please enter mobile number.";
$code= "2";
}
elseif(is_numeric(trim($mobileno)) == false){
$errorMsg= "Please enter numeric value.";
$code= "2";
}elseif(strlen($mobileno)<10){
$errorMsg= "Number should be ten digits.";
$code= "2";
}
else{
// echo "<pre>";
echo "4"; // getting issue here
}
}
are the right way to use the code for login and server-side validation?
Actually, it's the outer clause that causes your issues. It reads like this:
if (empty($account_type)) {
// We don't have an account type.
}
elseif ($account_type==1) {
// Account type is '1'. Proceed in here.
}
elseif ($account_type==2) {
// Account type is '2'.
}
elseif(empty($password)) {
// We don't have a password.
}
else {
// We have a password and an account type,
// and it's neither 1 nor 2.
try {
...
} catch(PDOExecption $e) {
...
}
}
I'd suggest splitting your if chains - to be able to do this, though, you might also need to re-structure your sanitation, validation and error handling.
Wrapping your logic in a class might be worth a thought, for example:
// Mock db
class DbConnection {}
class Login {
/**
* #var Array
*/
protected $error;
/**
* #var Array
*/
protected $data;
/**
* #var DbConnection
*/
protected $db;
/**
* Mock sanitizing
*/
protected function sanitize(array $data): array {
return $data;
}
protected function validate(): bool {
// In our sanitize() method, we already made sure
// that everything we need is set, so we don't have
// to do it here.
$account_type = $this->data['account_type'];
$password = $this->data['password'];
// Return early if we don't have an account type or
// a password.
if (!$account_type) {
$this->error = [1, 'Please select account type'];
return false;
}
if(!$password) {
$this->error = [3, 'Please provide a password'];
return false;
}
if ($account_type == 1) {
$mobileno = $this->data['mobileno'];
// We might already have stripped everything that's not a number
// from our mobile number string when sanitizing it, so we already
// made sure it's either empty or numeric.
//
// To validate it, we could either use a regex, or one of the PHP
// ports of Google's libphonenumber library:
// - https://stackoverflow.com/questions/123559/how-to-validate-phone-numbers-using-regex
// - https://stackoverflow.com/questions/22378736/regex-for-mobile-number-validation/
// - https://github.com/giggsey/libphonenumber-for-php
//
// Let's assume we already used one of those methods, so the value of
// $mobileno would be either valid or false.
if (!$mobileno) {
$this->error = [2, 'Please enter a valid mobile number'];
return false;
}
return true;
}
if ($account_type==2) {
// Some validation logic. If nothing fails, we'll finally return true.
return true;
}
}
/**
* #return Mixed - Boolean|User (object, array...)
*/
public function login() {
if (!$this->validate()) {
return false;
}
$password = $this->data['password'];
try {
// Query db for password (and maybe a username, too)
return ['User' => '...'];
}
catch(PDOExecption $e) {
// Exception handling;
}
}
public function getError() {
return $this->error;
}
public function __construct(array $data, DbConnection $db) {
$this->data = $this->sanitize($data);
$this->db = $db;
}
}
You're login process would then be:
// Mock POST data
$_POST = [
'account_type' => '1',
'password' => 'PwG2c?4tyUzEtD!9',
'mobileno' => '012345678986'
];
// Evaluate login attempt
$attempt = new Login($_POST, new DbConnection());
$user = $attempt->login();
if (!$user) {
var_dump($attempt->getError());
}
else {
var_dump($user);
}
Sources used:
How to validate phone numbers using regex
Regex for Mobile Number Validation
https://github.com/giggsey/libphonenumber-for-php
I am developing a Register/Login system with validation. Registering system is working well. For example, when I register the same email twice, the following message appears:
Email already registered!
However, when I log-in with the same e-mail and password, an error occurs. The following message appears as a validation error:
Email not registered!
Even if the email is registered in DB.
Code for e-mail validation:
<?php
public function validateEmail($par)
{
if (filter_var($par, FILTER_VALIDATE_EMAIL)) {
return true;
} else {
$this->setErro("Invalid Email!");
return false;
}
}
public function validateIssetEmail($email, $action = null)
{
$b = $this->cadastro->getIssetEmail($email);
if ($action == null) {
if ($b > 0) {
$this->setErro("Email already registered!");
return false;
} else {
return true;
}
} else {
if ($b > 0) {
return true;
} else {
$this->setErro("Email not registered!");
return false;
}
}
}
Code for login controller:
<?php
$validate = new Classes\ClassValidate();
$validate->validateFields($_POST);
$validate->validateEmail($email);
$validate->validateIssetEmail($email,"login");
$validate->validateStrongSenha($senha);
$validate->validateSenha($email,$senha);
var_dump($validate->getErro());
Code for class login:
<?php
namespace Models;
class ClassLogin extends ClassCrud
{
# Returns user data
public function getDataUser($email)
{
$b = $this->selectDB(
"*",
"users",
"where email=?",
array(
$email
)
);
$f = $b->fetch(\PDO::FETCH_ASSOC);
$r = $b->rowCount();
return $arrData = [
"data" => $f,
"rows" => $r
];
}
}
My getIssetEmail method exists on Register code only.
# Check directly at the bank if the email is registered
public function getIssetEmail($email)
{
$b = $this->selectDB(
"*",
"users",
"where email=?",
[
$email
]
);
return $r = $b->rowCount(); // returns the amount of rows in the search
}
And ClassPassword
<?php
namespace Classes;
use Models\ClassLogin;
class ClassPassword
{
private $db;
public function __construct()
{
$this->db = new ClassLogin();
}
# Create password's hash to save in DB
public function passwordHash($senha)
{
return password_hash($senha, PASSWORD_DEFAULT);
}
# Verify if password's hash is correct
public function verifyHash($email, $senha)
{
$hashDb = $this->db->getDataUser($email);
return password_verify($senha, $hashDb["data"]["senha"]);
}
}
This is not an answer but hopefully it will help in debugging.
First, I'm going to change your code. This is 100% a style choice but I personally think it is easier to follow. If you have an if statement that always returns, you don't technically need an else. Once again, this is a style choice and you don't have to follow it.
Second, if you can, try adding logging into your workflow, it will save you so much time debugging. It isn't always an option, especially for legacy code bases, but it is awesome when you can inspect complex code. In this example, I"m just making a couple of helper methods that dump stuff but normally I'd use something like Monolog to write to a stream that I can tail, and I can easily turn it off in production. When logging, sometimes it helps to avoid identical messages so that you can easily find the exact line number you are on, too.
So with those changes, try running this code inside of your class:
private function logMessage($message)
{
echo $message . PHP_EOL;
}
private function logVariable($variable)
{
var_dump($variable);
}
public function validateIssetEmail($email, $action = null)
{
$this->logVariable($email);
$this->logVariable($action);
$b = $this->cadastro->getIssetEmail($email);
$this->logVariable($b);
if ($action === null) {
$this->logMessage('Action was null');
if ($b > 0) {
$this->logMessage('B is greater than zero');
$this->setErro("Email already registered!");
return false;
}
$this->logMessage('B was not greater than zero');
return true;
}
$this->logMessage('Action was not null');
if ($b > 0) {
$this->logMessage('B is greater than zero');
return true;
}
$this->logMessage('B was not greater than zero');
$this->setErro("Email not registered!");
return false;
}
This should log in human-readable form every step. You should be able to walk through this and identify where your bug is. For instance, in the comments above you said that a variable was 0 in a block that was guarded by a check that guarantees that that shouldn't happen.
This is the wrong part i guess you assigned login as action so you can call cadastro class inside of the function
$cadastro = new Cadastro();
$b = $cadastro->getIssetEmail($email);
if ($action == null) {
if ($b > 0) {
$this->setErro("Email already registered!");
return false;
} else {
return true;
}
} else {
if ($b > 0) {
return true;
} else {
$this->setErro("Email not registered!");
return false;
}
}
I have my own MVC and in my BaseController i create simple method flashMessage.
public function flashMessage($name, $value)
{
if(!isset($_SESSION['message'][$name])) {
$_SESSION['message'][$name] = $value;
}
}
This work good but i dont know when to destroy this session. Is good idea to put in __destructor session_unset($_SESSION['message']); ?
This work good but my message has no lifetime
public function authenticate()
{
if(isset($_POST['submit']))
{
$username = $this->inputFilter($_POST['username']);
$password = $this->inputFilter($_POST['password']);
// check if user exist
if(!$this->auth->autheticate($username, $password)) {
$this->flashMessage('error', 'Error: Invalid username or password!');
return $this->redirect('login');
}else {
$this->flashMessage('success', 'Success: Uspešno ste se prijavili na sistem!');
return $this->redirect('home');
}
}
}
You remove it on read.
A simple example, by making the flash message a class.
class FlashMessage
{
static function create($name, $value)
{
if(!isset($_SESSION['message'][$name])) {
$_SESSION['message'][$name] = $value;
}
}
static function read($name)
{
if(isset($_SESSION['message'][$name])) {
$message = $_SESSION['message'][$name];
unset($_SESSION['message'][$name]);
return $message;
}
//return null, false or throw exception
}
}
I suggest looking in already implemented flash messages algorithm.
E.g. in Yii you can set to remove flash message when it was shown:
$this->setFlash('type', 'message');
$this->showFlash('type');
function showFlash($type) {
$msg = isset($_SESSION['message'][$type]) ? $_SESSION['message'][$type] : null;
if (!is_null($msg)) {
unset($_SESSION['message'][$type]);
}
return $msg;
}
I have a php file(register.php) with a public function register($data) where errors are validated.Then errors are counted and if no errors are found, validation is passed.
register.php:
class ARegister {
public function register($data) {
$user = $data['userData'];
//validate provided data
$errors = $this->validateUser($data);
if(count($errors) == 0) {
//first validation
}
}
public function validateUser($data, $botProtection = true) {
$id = $data['fieldId'];
$user = $data['userData'];
$errors = array();
$validator = new AValidator();
if( $validator->isEmpty($user['password']) )
$errors[] = array(
"id" => $id['password'],
"msg" => Lang::get('password_required')
);
return $errors;
}
The problem is, that I need to get this confirmation of validated data to my other php file (othervalidation.php) where I've made another validation:
othervalidation.php:
<?php
require 'register.php';
if ( !empty($action) ) {
switch ( $action ) {
case 'process_payment':
try {
$instance = new ARegister();
if($instance->validateUser($data, $errors)) {
throw new Exception('Validation error');
}
} catch (Exception $e) {
$status = false;
$message = $e->getMessage();
}
}
How can I send the result of $errors variable to my other validation (othervalidation.php)?
I looked at your new code design and here's the new problems I found.
First, in your register function, you use the errors variable as an integer while your validate function returns an array. You got two possibilities here.
You can change your register method to check out if your error array is empty like this:
if(empty($errors)) {
//first validation
}
Count is also valid, but I still prefer empty since it's syntactically clearer. Furthermore, the count function returns 1 if the parameter is not an array or a countable object or 0 if the parameter is NULL. As I said, it is a functional solution in your current case but, in some other contexts, it might cause you unexpected results.
Here in your method declaration, I see that you are expecting a boolean (botProtection).
public function validateUser($data, $botProtection = true) {
But you are supplying an errors parameter
if($instance->validateUser($data, $errors)) {
You don't provide me the declaration of the errors variable, but it is probably not matching the bot protection parameter your function is expecting. PHP is using lose typing, it is useful but, once again, you got to be careful for bugs hard to find. For public function, you should always make sure a way or another that the supplied parameter won't lead to code crash.
In your code, the data parameter seems to be an array. You can use parameter hinting to force the use of array like this:
public function register(array $data) {
public function validateUser(array $data, $botProtection = true) {
And even specific class (as if you where using "instance of" in a condition)
public function register(MyDataClass $data) {
public function validateUser(MyDataClass $data, $botProtection = true) {
Also, you're not even using the botProtection parameter in your validateUser method.
On the same function call:
if($instance->validateUser($data, $errors)) {
you are expecting a Boolean (true or false), but the method returns an array. If you want to use the code the way it is currently designed, you must use it like this
if(!empty($instance->validateUser($data, $errors)) {
Here, I'm not so sure it is necessary to use exception. Ain't it be easier to design your code like this?
if(!empty($instance->validateUser($data, $errors)) {
$message = 'Validation error';
}
In your validate function, is the "isEmpty" function also validating if the client provided a password?
If that's the case you could validate it like this:
if(!in_array($user['password']) or empty($user['password']))
With those corrections, your code should be functional.
Here's a sample of how I would had design your code (considering the code sample provided):
class ARegister {
public function register($data) {
$user = $data['userData']; //don't declare it here, all the user validations must be done in validateUser($data, &$errors)
$errors = array();
if($this->validateUser($data, $errors)) {
//first validation
}
}
/**
* Note: If you are not returing more than one error at the time, $errors should be a string instead of an array.
*/
public function validateUser($data, array &$errors) {
$isValid = false;
if (in_array($data['fieldId']) and in_array($data['fieldId']['password']) and in_array($data['userData'])){
if(!in_array($data['userData']['password']) or empty($data['userData']['password'])){
$errors[$data['fieldId']['password']] = Lang::get('password_required');
}
else{
$isValid = true;
}
}
else{
//an invalid data array had been provided
}
return $isValid;
}
For the next part, if the code is executed directly in the view and you are a beginner, create a procedural external controller file (all functions will be public...). If you are a professional, you MUST create a class to encapsulate the treatment.
You must not do treatment directly in the view. The view is a dumb placeholder for data presentation and collecting client's input. The sole action it must do is display the data sent by the controller and send back the client's input to the controller.
The treatment on data is the controller responsibility.
if (!empty($action) ) {
$errors =array();
switch ( $action ) {
case 'process_payment':
$instance = new ARegister();
if($instance->validateUser($data, $errors)) {
//the user is valid, do the treatment
}
else
PageManager::dispayError($errors);
}
unset($instance);
}
}
Here's an example how you can centralize your error display
/**
* Can be more complexe than that, but I'm at my father's home at four hundred kms away from Montreal right now..
*/
public static function dispayError($errors, $size = 4){
if (is_numeric($size)){
if ($size < 0){
$size = 1;
}
elseif($size > 5){
$size = 5;
}
}
else{
$size = 4;
}
if (is_scalar($errors)){
echo '<h' . $size . 'class="ERROR_MESSAGE">' . $errors . '</h' . $size . '><br>';
}
elseif (is_array($errors)){
foreach ($errors as $error){
if (is_scalar($error)){
echo '<h' . $size . 'class="ERROR_MESSAGE">' . $error . '</h' . $size . '><br>';
}
}
}
}
Of course, you can also support many kind of message:
public static function dispayError($errors, $size = 4){
self::displayMessage("ERROR_MESSAGE", $errors, $size=4);
}
private static displayMessage($class, $messages, $size=4)
Well, took me two hours to write that. I hope you have now enough material to build an efficient, reusable and, no less important, safe code design.
Good success,
Jonathan Parent-Lévesque from Montreal
You can try something like this:
class ARegister {
private $error = 0;
public function register($data) {
if (!$this->validateUser($data)){
$this->error++;
}
}
public function getErrorCount(){
return $this->error;
}
public resetErrorCount(){
$this->error = 0;
}
Or pass the error by reference:
public function register(&$error, $data) {
if (!$this->validateUser($data)){
$error++;
}
}
Personally, I would do all the validation in the same method (in the class for encapsulation), use an error message parameter (passed by reference) to return why the validation failed and use the return statement to return true or false.
class MyClass{
public function validation(&$errorMessage, $firstParameter, $secondParameter){
$success = false;
if (!$this->firstValidation($firstParameter)){
$errorMessage = "this is not working pal.";
}
elseif (!this->secondeValidation($firstParameter)){
$errorMessage = "Still not working buddy...";
}
else{
$success = true;
}
return $success;
}
private function firstValidation($firstParameter){
$success = false;
return $success;
}
private function secondeValidation($secondParameter){
$success = false;
return $success;
}
}
In your other file:
<?php
$instance = new MyClass();
$errorMessage = "";
if ($instance->validation($errorMessage, $firstParameter, $secondParameter)){
echo "Woot, it's working!!!";
}
else{
echo $errorMessage;
}
?>
Is one of these code solutions fit your needs?
Jonathan Parent-Lévesque from Montreal
I can't work out why this method keeps returning false. I'm checking the length of a password entered. It is definitely over 5 characters.The name of the input field is correct. But it always throws the exception throw new Exception("Password must contain at least 6 characters.");.
method:
public function checkPassword(){
if(strlen($this->post_data['register_password']) > 5){
}else{
throw new Exception("Password must contain at least 6 characters.");
}
}
calling:
if( isset($_POST['register-submit'])){
$error = '';
$register = new register($_POST, $dbh);
try {
if($register->checkUsername()){
if($register->checkPassword()){
}
}
} catch (Exception $e) {
$error .= '<span style="color:red;">' . $e->getMessage() . '</span>';
}
}
edit: added more of the class
public $post_data = array();
private $dbh;
public function __construct($post_data, PDO $dbh){
$this->error = array();
$this->post_data = array_map('trim', $post_data);
$this->dbh = $dbh;
}
Try putting var_dump($_POST) at the top of the calling code. Make sure you have the right variable name for password. I notice you have register-submit for the submit button and register_password for the password. Did you mix the hyphen (-) with an underscore (_)?
You create the class register, with $_POST, then use $this->post_data to fetch register_password. I doubt that there is something wrong with $this->post_data, add var_dump to debug:
public function checkPassword(){
if(strlen($_POST['register_password']) > 5){
return true;
}else{
// for debug
// var_dump($this->post_data['register_password']);
throw new Exception("Password must contain at least 6 characters.");
}
}
add var_dump to debug the __construct:
public function __construct($post_data, PDO $dbh){
$this->error = array();
$this->post_data = array_map('trim', $post_data);
var_dump($this->post_data);
$this->dbh = $dbh;
}