I have a project object.
A user can assign a worker object for it.
99% of the case the project object has all the proper fields set.
In some cases, the project is missing a field.
In order for a worker to be assign to a project the project must have all the required fields setup.
To solve this, I throw exceptions like this: ( don't try to find a pattern here, the real example is more complex )
if ($project->startDate == false ) {
throw new Exception("Missing startDate attribute for project id: $id");
}
if ($project->finishDate == false ) {
throw new Exception("Missing finishDate attribute for project id: $id");
}
if ($project->startDate > $project->finishDate ) {
throw new Exception("Invalid start date for project id: $id");
}
The problem with this is that I need to display a custom message to the user each time. For example if the first error is thrown the user should see: 'Please setup the project start date' and so on.
How can I do this ?
Just define your own exception class and your whole code in a try .. catch:
class FormException extends Exception {
private var $userMessage;
public function __construct($message, $userMessage) {
parent::__construct($message);
$this->userMessage = $userMessage;
}
public function getUserMessage() {return $this->userMessage;}
}
try {
// Whole code goes here, probably a function call
throw new FormException("Missing startDate attribute for project id: $id",
'Please setup the project start date');
} catch (FormException $e) {
echo $e->getUserMessage();
error_log($e->getMessage());
}
By the way, if you want to include variable contents in a string, either use double quotes ("id: $id") or concatenate ('id: ' . $id).
Try this:
try
{
$Message = '';
if ($project->startDate == false ) {
$Message = "Please setup the project start date\n";
throw new Exception("Missing startDate attribute for project id: $id");
}
if ($project->finishDate == false ) {
$Message = "Please setup the project finish date\n";
throw new Exception("Missing finishDate attribute for project id: $id");
}
if ($project->approved == false ) {
$Message = "Please setup the project approved field\n";
throw new Exception("Missing approved attribute for project id: $id");
}
}
catch(Exception $Ex)
{
// Log in some way you like the $Ex-getMessage();
echo nl2br($Message);
}
Inside your project class, create a new method, probably called getErrorMessage.
That function should do the checks (no need to have the concrete way of validation outside of project or create a validation object for project). Then:
if ($message = $project->getErrorMessage())
{
throw new Exception(sprintf('%s (project id: %d)', $message, $id));
}
If you decide to not throw exceptions for design reasons, this still is of use.
You gain more flexibility with a validator object that can naturally provide more detailed information than a single method. So it might be the better thing to do:
class ProjectValidator
{
private $project;
private $errors;
public function __construct($project)
{
$this->project = $project;
}
public function isValid()
{
// run your checks, add errors to the array as appropriate.
$errors = array();
if (!$this->project->startDate)
{
$errors[] = 'Missing startDate attribute';
}
if (!$this->project->finishDate)
{
$errors[] = 'Missing finishDate attribute';
}
if (!$this->project->approved)
{
$errors[] = 'Missing approved attribute';
}
$this->errors = $errors;
return (bool) count($this->errors);
}
public function getErrors()
{
return $this->errors;
}
}
$validator = new ProjectValidator($project);
if (!$validator->isValid())
{
// throw an exception or do whatever you want in case it's not valid
$errors = $validator->getMessages();
$message = sprintf("Project id: %d has the following %d error(s):\n", $id, count($errors));
foreach($errors as $index => $error)
{
$message .= sprintf("%d. %s\n", $index+1, $error);
}
throw new Exception($message);
}
Related
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>';
}
}
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 have a form which represnts single answer object (this is standard propel generated form I have not changed much there only some validation rules) and another form that represents a collection of answers code as below:
class BbQuestionAnswersForm extends sfForm {
public function __construct($defaults = array(), $options = array(), $CSRFSecret = null) {
parent::__construct($defaults, $options, $CSRFSecret);
}
public function configure() {
if (!$questions = $this->getOption('questions')) {
throw new InvalidArgumentException('The form need array of BbExamQuestion objects.');
}
if (!$taker = $this->getOption('taker')) {
throw new InvalidArgumentException('The form need BbExamtaker object.');
}
if (!$user = $this->getOption('questions')) {
throw new InvalidArgumentException('The form need sfGuardUser object.');
}
foreach($questions as $question) {
$answer = new BbExamAnswer();
$answer->setBbExamQuestion($question);
$answer->setBbExamTaker($taker);
$answer->setCreatedBy($user);
$answer->setUpdatedBy($user);
$form = new BbExamAnswerForm($answer, array('question' => $question));
$this->embedForm($question->getId(), $form);
}
$this->widgetSchema->setNameFormat('solve[%s]');
}
}
Everything(validation, display) goes fine with this form until I try to save it. Part of action which trying to save the form:
...
$this->form = new BbQuestionAnswersForm(null, array('questions' => $this->questions, 'taker' => $this->taker, 'user' => $this->getUser()->getGuardUser()));
if($request->isMethod('post')) {
$this->form->bind($request->getParameter($this->form->getName()));
if($this->form->isValid()) {
if($this->form->save()) {
$this->getUser()->setFlash('success', 'Save goes fine.');
$this->redirect($this->generateUrl('#bb'));
} else {
$this->getUser()->setFlash('error', 'Upps an error occurred.');
}
}
}
When I send valid form I receive "Call to undefined method BbQuestionAnswersForm::save()" error.
I tried to write this method like this:
public function save() {
$conn = Propel::getConnection(ZlecPeer::DATABASE_NAME);
$conn->beginTransaction();
try{
foreach($this->getEmbeddedForms() as $form) {
$form->save();
}
$conn->commit();
} catch(Exception $e) {
$conn->rollback();
echo 'upps something goes wrong';
die($e->getMessage());
return false;
}
return true;
}
but it doesnt work, I receive exception without any message.
What am I doing wrong, how to make save method work?
I believe your BbQuestionAnswersForm is extending the wrong object. It should extend BaseFormDoctrine or possibly BaseBbQuestionAnswersForm if you are using the framework correctly.
Edit: Just noticed you are using propel but it should be the same thing. Try:
class BbQuestionAnswersForm extends BaseBbQuestionAnswersForm
and less likely:
class BbQuestionAnswersForm extends BaseFormPropel
Save method should looks like this:
public function save($con = null) {
if (null === $con) {
$con = Propel::getConnection(BbExamAnswerPeer::DATABASE_NAME);
}
$con->beginTransaction();
try{
foreach($this->embeddedForms as $name => $form) {
if(!isset($this->values[$name]) || !is_array($this->values[$name])) {
continue;
}
if($form instanceof sfFormObject) {
$form->updateObject($this->values[$name]);
$form->getObject()->save($con);
$form->saveEmbeddedForms($con);
} else {
throw new Exception('Embedded form should be an instance of sfFormObject');
}
}
$con->commit();
} catch(Exception $e) {
$con->rollBack();
throw $e;
return false;
}
return true;
}
I have a save method in my User class.
If the save method encounters validation errors it returns an array of errors that I display to the user. However this means in my code I have to write:
if (!$user->save()) {
//display success to user
}
Surely my save method should return true on success. But how do I handle errors in that case?
Use try ... catch syntax.
For example:
try {
$user->save();
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
http://php.net/manual/en/language.exceptions.php
I would throw an exception in the event that save() runs into any problems.
If you want to provide an array of validation errors, your could subclass Exception and provide a mechanism for storing the validation errors.
A custom Exception subclass will also help you differentiate between exceptions your code throws explicitly (which you'd like to catch) and exceptions that you didn't expect (which should be fatal).
Here's the subclass:
class UserException extends Exception
{
private $userMessages;
public function __construct($message = "", $code = 0, Exception $previous = null, array $userMessages = null)
{
parent::__construct($message, $code, $previous);
if ($userMessages === null) {
$this->userMessages = array();
} else {
$this->userMessages = $userMessages;
}
}
public function getUserMessages()
{
return $this->userMessages;
}
}
Here's a silly version of a User class that always throws an Exception on save().
class User
{
public function save()
{
$userMessages = array(
'Your password is wrong',
'Your username is silly',
'Your favorite color is ugly'
);
throw new UserException('User Errors', 0 , null, $userMessages);
}
}
To use it:
$user = new User();
try {
$user->save();
} catch (UserException $e) {
foreach ($e->getUserMessages() as $message) {
print $message . "\n";
}
}
You could also accomplish something like this by populating the Exception's $message with, say a semi-colon-delimited list of messages. You could even build a list of constants for error types, then combine them as a bitmask and use that for the Exception's $code. The advantage of these options is you would be using the built in members and not adding anything extra.
More on exceptions:
http://php.net/manual/en/language.exceptions.php
A (bad?) habit I picked up after playing a good bit with erlang is to return tuple values (as a php array).
function my_func() {
$success = true;
$errors = array();
if ( something_fails() ) {
$success = false;
$errors[] = 'something failed..';
}
return array( $success, $errors );
}
list($success, $errors) = my_func();
if ( ! $success ) {
do_somthing_with( $errors );
}
In my experience, this has been really handy when the wild modify legacy code tickets appear and you don't really dare modify anything but could more easily add more legacy to it.
Cheers -
Return either true, or the error array.
And when you check for it, use this:
if ($user->save()===true) {
// display success to user
} else {
// display error to user
}
the === operator performs a typesafe comparison, meaning that it not only checks if the value is true, but also if the type is a boolean. If the array is being returned it's handled as false.
Would be good to return array from validation function like this
$result['ACK'] = 'true';
$result['message'] = 'Success validation'];
on failure
$result['ACK'] = 'false';
$result['message'] = 'validation error message';
Now you can use this array in front end like this
if ($result['ACK']) {
//No Error
} else {
echo $result['message'];
}
Change your condition to, If true then success else return array of errors.
if ($user->save() === true) {
//display success to user
}
function put($id, $name)
{
try
{
product::put($id, $name);
}
catch(\util\BadNameException $e)
{
throw new RestException(400, "Please supply a better name.");
}
}
When returning the error message I also want to include the result of (array)product::getNamingConvention() in the error. How can I do that?
I could just return a custom array with the error message and data, but I don't know how to set the status code to 400 in that case?
I'm using Restler 3.
Responder class is responsible for giving a structure to error response and success response
You can extend the Responder class as shown below to add data property
use \Luracast\Restler\Responder;
use \Luracast\Restler\Defaults;
class MyResponder extends Responder
{
public static $data = null;
public function formatError($statusCode, $message)
{
$r = array(
'error' => array(
'code' => $statusCode,
'message' => $message
)
);
if (isset(self::$data)) {
$r['data'] = self::$data;
}
return $r;
}
}
Defaults::$responderClass = 'MyResponder';
And then set the data from your class as shown below
function put($id, $name)
{
try
{
product::put($id, $name);
}
catch(\util\BadNameException $e)
{
MyResponder::$data = product::getNamingConvention();
throw new RestException(400, "Please supply a better name.");
}
}