I'm on a project where I don't want to throw errors directly at the user. Instead I want customized messages for the error that accur.
For later I also need to keep an error number in order to customize the error messages from outside the class, like an array of error messages.
So I made my own thing where I set $error = null and then later set error to a number that later becomes a message.
Question
Is there any disadvantages with this approach? Am I better of with try/catch or something else? I would like to keep the code short and tidy if possible.
In this short code example the error handling seems to be a big part of the class. In my real code which is a few hundred lines, it's not a big part of the whole code
http://sandbox.onlinephpfunctions.com/code/623b388b70603bf7f020468aa9e310f7340cd108
<?php
class Project {
private $error = null;
public function callMeFirst($num) {
$this->nestedLevelOne($num);
$this->nestedLevelTwo($num);
$this->setResults();
}
public function callMeSecond($num) {
$this->nestedLevelTwo($num);
$this->setResults();
}
private function nestedLevelOne($num) {
// Do stuff
if($num !== 1) {
$this->error = ['id' => 1, 'value' => $num];
}
}
private function nestedLevelTwo($num) {
// Do stuff
if($num !== 20) {
$this->error = ['id' => 2, 'value' => $num];
}
}
private function message($args) {
extract($args);
$message = [
1 => "Nested level one error: $value",
2 => "Another error at level two: $value",
];
return ['id' => $id, 'message' => $message[$id]];
}
private function setResults() {
$results['success'] = ($this->error === null) ? true : false;
if($this->error !== null) {
$results['error'] = $this->message($this->error);
}
$this->results = $results;
}
}
$project = new Project();
$project->callMeFirst(1);
$project->callMeFirst(2);
print_r($project->results);
It will output
Array
(
[success] =>
[error] => Array
(
[id] => 2
[message] => Another error at level two: 2
)
)
The reason I'm asking is that I have a feeling of that I may reinvent the wheel in this case. Am I?
If there is a better solution, I would be thankful to see how that code looks like.
I would probably separate the business logic from the error handling to simplify each part more. By using exceptions, you keep your business logic simpler; you simply throw an exception whenever you encounter a case that is not permitted, thereby preventing getting into any sort of inconsistent state at all. The business logic class doesn't have to care about how this error will be processed further, it just needs to raise the error. You should then create a separate wrapper around that business logic class which simply cares about handling any errors and formatting them into an array or other sort of response which will be handled elsewhere. Something along these lines:
class ProjectException extends Exception {
public function __construct($num) {
parent::__construct(get_called_class() . ": $num");
}
}
class NestedLevelOneException extends ProjectException {
// customise __construct here if desired
}
class NestedLevelTwoException extends ProjectException {}
class Project {
public function callMeFirst($num) {
$this->nestedLevelOne($num);
$this->nestedLevelTwo($num);
}
public function callMeSecond($num) {
$this->nestedLevelTwo($num);
}
protected function nestedLevelOne($num) {
if ($num !== 1) {
throw new NestedLevelOneException($num);
}
// do stuff
}
protected function nestedLevelTwo($num) {
if ($num !== 20) {
throw new NestedLevelTwoException($num);
}
// do stuff
}
}
class ProjectService {
protected $project;
public function __construct(Project $project = null) {
$this->project = $project ?: new Project;
}
public function process($a, $b) {
try {
$this->project->callMeFirst($a);
$this->project->callMeSecond($b);
return ['success' => true];
} catch (ProjectException $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
}
$api = new ProjectService;
print_r($api->process(1, 2));
By defining three separate exceptions, you get a lot of flexibility in how and where you want to handle errors. You can specifically catch NestedLevel*Exception, or you catch either of them with ProjectException.
By having your methods throw exceptions, you gain flexible error handling possibilities. You are free to not catch the exception and have the program terminate, as would be entirely reasonable if one of your business requirements isn't met. Alternatively, you can catch the exception at a level up that is prepared to deal with that error and turn it into something meaningful that can be acted upon.
By moving the generation of the error message into the exceptions, you keep the error type and its message self-contained. There's exactly one place where you define what kind of error may happen and what its error message will look like; instead of spreading that out over your entire codebase. And you're still free to choose some other error message in the UI, e.g. for localising different kinds of errors into multiple languages; just check the type of the exception object.
By using a separate ProjectService which cares about handling those exceptions and turning it into an array response, you narrow each class' responsibilities and make each class more flexible and simpler.
Related
The exception is thrown correctly in the program, but the test does not detect it. Why??
public function null_user_send()
{
$message = 'Hi';
$response = $this->post(route('dialogSend'), ['to' => -1, 'message' => $message]);
$this->assertDatabaseMissing('messages', ['to' => -1, 'message' => $message]);
$this->expectException(Exception::class);
$response->assertStatus(500);
}
MailController:
/** #throws \Exception */
public function dialogSend(Request $request)
{
$handler = app(MailHandler::class);
if ($request->input('to') <= 0) {
throw new \Exception('Параметр "to" имел отрицательное значение', 500);
}
...
}
Test response: 'Failed asserting that exception of type "Exception" is thrown.'
And it doesn't matter what class of exceptions, nothing works. Pls help
Check the namespace of the exception you are testing
$this->expectException(Exception::class);
probably should be
$this->expectException(\Exception::class);
This is because your test itself sits in its own namespace (depend on the type of test). What your test is may actually be seeing is
$this->expectException(\Tests\Exception::class);
which will of course never be thrown since it doesn't exist.
One of the reasons you are having trouble diagnosing this is the nature of your tests. There are a few things there, while not strictly wrong, are going to make your testing that much harder to determine the cause of your problem.
You're trying to test too much at once. Separate each concern into its own test so that you can first determine if the database is empty, then if it throws an exception, then if it returns a 500 error. You are essentially testing three different aspects of your application here.
public function null_user_send()
{
// Arrange
$message = 'Hi';
// Act
$response = $this->post(route('dialogSend'), ['to' => -1, 'message' => $message]);
// Assert
$this->assertDatabaseMissing('messages', ['to' => -1, 'message' => $message]);
}
public function does_an_exception()
{
// Arrange, Act
...
// Assert
$this->expectException(Exception::class);
}
public function returns_server_erro()
{
// Arrange, Act
...
// Assert
$response->assertStatus(500);
}
Give your tests meaning in their names. This will help you immensely in the future when you can't remember what something is meant to do verus what it is actually doing.
public function it_should_not_have_a_message_if_the_input_is_wrong()
{
// database test
}
public function it_should_throw_an_exception_if_the_input_is_wrong()
{
// exception test
}
public function it_should_return_a_500_error_if_the_input_is_wrong()
{
// http code test
}
A good test should be able to answer the following question:
If I set up my application like this, and then I do that, then my application should now look like this.
This is where the Arrange (organise my application), Act (do something) and Assert (check my application) comes from in the code above.
There are couple of other things you might to look at, although this will be up you depending on your needs.
You look like you're doing validation on input here. You should probably throw a 400 or 422 error if your input is wrong
The 500 error code on your exception probably won't really do anything, it won't be related to the 500 HTTP status code, unless you have something in your error handling that does that.
I don't understand how to properly create and return useful error messages with PHP to the web.
I have a class
class Foo {
const OK_IT_WORKED = 0;
const ERR_IT_FAILED = 1;
const ERR_IT_TIMED_OUT = 3;
public function fooItUp(){
if(itFooed)
return OK_IT_WORKED;
elseif(itFooedUp)
return ERR_IT_FAILED;
elseif(itFooedOut)
return ERR_IT_TIMED_OUT;
}
}
And another class that uses this class to do something useful, then return the result to the user. I am just wondering where I put the string value for all my error messages.
class Bar {
public function doFooeyThings(stuff){
$res = $myFoo->fooItUp();
// now i need to tell the user what happened, but they don't understand error codes
if($res === Foo::OK_IT_WORKED)
return 'string result here? seems wrong';
elseif ($res === Foo::ERR_IT_FAILED)
return Foo::ERR_IT_FAILED_STRING; // seems redundant?
elseif($res === Foo:ERR_IT_TIMED_OUT)
return $res; // return number and have an "enum" in the client (js) ?
}
}
You should avoid returning error states whenever possible. Use exceptions instead. If you've never used exceptions before you can read about them here
There multiple ways you can utilize exceptions in your example. You could create custom exceptions for every error or for every category of error. More on custom exceptions here or you could create an instance of the default Exception class supplying it the error messages as strings.
The code below follows the second approach:
class Foo {
const OK_IT_WORKED = 0;
const ERR_IT_FAILED = 1;
const ERR_IT_TIMED_OUT = 3;
public function fooItUp(){
if(itFooed)
return OK_IT_WORKED;
else if(itFooedUp)
throw new Exception("It failed")
else if(itFooedOut)
throw new Exception("Request timed out");
}
}
I'm sure you can think of some more elegant messages than the ones I used. Anyway, you can then go ahead and handle those exceptions on the caller method using try/catch blocks:
class Bar {
public function doFooeyThings(stuff){
try
{
$res = myFoo->fooItUp();
}
catch(Exception $e)
{
//do something with the error message
}
}
}
Whatever exception is thrown from fooItUp will be "caught" by the catch block and handled by your code.
Two things you should also consider are:
It's best not to show your users detailed information about errors because those information could be used by users with malicious intent
Ideally you should have some kind of global exception handling
One solution is to use exceptions in conjunction with set_exception_handler().
<?php
set_exception_handler(function($e) {
echo "Error encountered: {$e->getMessage()}";
});
class ErrorMessageTest
{
public function isOk()
{
echo "This works okay. ";
}
public function isNotOkay()
{
echo "This will not work. ";
throw new RuntimeException("Violets are red, roses are blue!! Wha!?!?");
}
}
$test = new ErrorMessageTest();
$test->isOk();
$test->isNotOkay();
The set_exception_handler() method takes a callable that will accept an exception as its parameter. This let's you provide your own logic for a thrown exception in the event it isn't caught in a try/catch.
Live Demo
See also: set_exception_handler() documentation
My question is about the correct usage of Php Exceptions.
My Php app is an API server.
My code base is quite articulated, to a depth of 5-6 levels of nested calls.
Of course exceptions are a very handy way to handle an error,
since you don't have to handle all error conditions on every level of your function calls...
To better explain what I mean, I make an exaple below.
This is a simplified excerpt from my top level code (the "router"):
...
$this->app->get("/persons/get", function() {
try {
$persons = new PersonsController($this);
$this->success($persons->get());
} catch (Exception $e) {
$this->error($e);
});
};
...
private function error($error) {
$response = $this->app->response();
$response->body(json_encode([
"error" => [
"message" => $error->getMessage(),
]));
}
And this is a simplified excerpt from my bottom level code (the "db"):
...
public function get($table) {
try {
$sql = "SELECT * FROM '$table'";
$statement = $this->db->prepare($sql);
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
return $result;
} catch (PDOException $e) {
throw new Exception("Error getting persons: ", 0, $e);
}
}
I think this is a typical and effective example of useful exception handling: on a fatal error in the bottom level of the code base, the error automatically pops up to the main error handling function. The real advantage here is I don't have to mess up with handling the error condition all along the chain of functions code, from the router level through the db level...
But, what if I should use Exceptions more extensively, in the middle of my functions stack... For example:
class PersonsController {
...
public function sync() {
if (!mkdir($d, 0777)) {
throw new Exception("Can't create folder $d");
}
}
...
}
Instead of, for example:
class PersonsController {
...
public function sync() {
if (!mkdir($d, 0777)) {dir: DEBUG ONLY!
return -1; # this error code will have to be handled upper in the stack...
}
}
...
}
The question is: How extensive can be the use of Exceptions in a Php non-trivial project?
What are best way of error handling? This is what I came up with:
class test {
public static function Payment($orderid, $total) {
if (empty($orderid) && empty($total)) {
return array('status' => 'fail', 'error' => 'Missing Data');
}
}
}
I heard about Try/Exceptions but how to fit that into my code? If you could provide example that would be great!
If you use PHP 5, you can handle error with exception :
http://fr2.php.net/manual/en/class.exception.php
This way is cleaner than manual set exception message, because you have access to a try catch system and you can isolate exception handling
As mentioned, use Exceptions. Specific to your example, you throw an exception if some condition fails. Then when you envoke the method that can throw an exception, you wrap it with a try/catch handling block.
class test {
public static function Payment( $orderid, $total ) {
if (empty( $orderid ) && empty( $total )) {
throw new Exception('Missing Data');
}
}
}
try {
test::Payment("1", "2"); //should be fine
test::Payment(); //should throw exception
} catch (Exception $e){
echo $e;
//do other things if you need
}
You could use exceptions.
However, in the use case you've posted, simply doing the checks at the controller level should suffice.
I also think that explicitly checking the return type for array (on fail) is counter intuitive.
Here is how you might modify your code to use an exception. It also helps to document the circumstances under which the exception is thrown.
class test {
/**
* [method description]
* #throws Exception if the order ID or total is empty
*/
public static function Payment($orderid, $total) {
if (empty($orderid) && empty($total)) {
throw new Exception("fail: Missing Data");
}
}
}
You can also create your own exception class if you want to include extra data in the exception.
class MyException extends Exception{
public $status, $error;
public function __construct($status, $error){
parent::__construct("$status: $error");
$this->status = $status;
$this->error = $error;
}
}
I tend to lean towards throwing exceptions, and then using the try/catch mechanism to deal with the aftermath. The man page is here: http://php.net/manual/en/language.exceptions.php
The best practice is to use Exceptions.
http://php.net/manual/en/language.exceptions.php
I'm writing a web application (PHP) for my friend and have decided to use my limited OOP training from Java.
My question is what is the best way to note in my class/application that specific critical things failed without actually breaking my page.
My problem is I have an Object "SummerCamper" which takes a camper_id as it's argument to load all of the necessary data into the object from the database. Say someone specifies a camper_id in the query string that does not exist, I pass it to my objects constructor and the load fails. I don't currently see a way for me to just return false from the constructor.
I have read I could possibly do this with Exceptions, throwing an exception if no records are found in the database or if some sort of validation fails on input of the camper_id from the application etc.
However, I have not really found a great way to alert my program that the Object Load has failed. I tried returning false from within the CATCH but the Object still persists in my php page. I do understand I could put a variable $is_valid = false if the load fails and then check the Object using a get method but I think there may be better ways.
What is the best way of achieving the essential termination of an object if a load fails? Should I load data into the object from outside the constructor? Is there some sort of design pattern that I should look into?
Any help would be appreciated.
function __construct($camper_id){
try{
$query = "SELECT * FROM campers WHERE camper_id = $camper_id";
$getResults = mysql_query($query);
$records = mysql_num_rows($getResults);
if ($records != 1) {
throw new Exception('Camper ID not Found.');
}
while($row = mysql_fetch_array($getResults))
{
$this->camper_id = $row['camper_id'];
$this->first_name = $row['first_name'];
$this->last_name = $row['last_name'];
$this->grade = $row['grade'];
$this->camper_age = $row['camper_age'];
$this->camper_gender = $row['gender'];
$this->return_camper = $row['return_camper'];
}
}
catch(Exception $e){
return false;
}
}
A constructor in PHP will always return void. This
public function __construct()
{
return FALSE;
}
will not work. Throwing an Exception in the constructor
public function __construct($camperId)
{
if($camperId === 1) {
throw new Exception('ID 1 is not in database');
}
}
would terminate script execution unless you catch it somewhere
try {
$camper = new SummerCamper(1);
} catch(Exception $e) {
$camper = FALSE;
}
You could move the above code into a static method of SummerCamper to create instances of it instead of using the new keyword (which is common in Java I heard)
class SummerCamper
{
protected function __construct($camperId)
{
if($camperId === 1) {
throw new Exception('ID 1 is not in database');
}
}
public static function create($camperId)
{
$camper = FALSE;
try {
$camper = new self($camperId);
} catch(Exception $e) {
// uncomment if you want PHP to raise a Notice about it
// trigger_error($e->getMessage(), E_USER_NOTICE);
}
return $camper;
}
}
This way you could do
$camper = SummerCamper::create(1);
and get FALSE in $camper when the $camper_id does not exist. Since statics are considered harmful, you might want to use a Factory instead.
Another option would be to decouple the database access from the SummerCamper altogether. Basically, SummerCamper is an Entity that should only be concerned about SummerCamper things. If you give it knowledge how to persist itself, you are effectively creating an ActiveRecord or RowDataGateway. You could go with a DataMapper approach:
class SummerCamperMapper
{
public function findById($id)
{
$camper = FALSE;
$data = $this->dbAdapter->query('SELECT id, name FROM campers where ?', $id);
if($data) {
$camper = new SummerCamper($data);
}
return $camper;
}
}
and your Entity
class SummerCamper
{
protected $id;
public function __construct(array $data)
{
$this->id = data['id'];
// other assignments
}
}
DataMapper is somewhat more complicated but it gives you decoupled code which is more maintainable and flexible in the end. Have a look around SO, there is a number of questions on these topics.
To add to the others' answers, keep in mind that you can throw different types of exceptions from a single method and handle them each differently:
try {
$camper = new SummerCamper($camper_id);
} catch (NoRecordsException $e) {
// handle no records
} catch (InvalidDataException $e) {
// handle invalid data
}
Throwing an exception from the constructor is probably the right approach. You can catch this in an appropriate place, and take the necessary action (e.g. display an error page). Since you didn't show any code, it's not clear where you were catching your exception or why that didn't seem to work.
try {
$camper = new SummerCamper($id);
$camper->display();
} catch (NonexistentCamper $ex) {
handleFailure($ex);
}