With PHP I am using the following class structure for exceptions:
class ProjectException extends Exception {
... logical code for handling exceptions, functions that are relevant to every exception my project throws go here
}
class SpecificException extends ProjectException {
protected $codePrefix = 'SE';
const ERROR_SOMETHING_BAD_HAPPENED = 100;
const ERROR_SOMETHING_SIMMILARLY_BAD_HAPPENED = 101;
/* no functions go in this class, this class is only to specify the code prefix of the exception, and the error codes used by it */
}
If I want to catch an exception, I usually use the following type of exception catching:
try {
/** try something here **/
} catch (SpecificException $e) {
switch ($e->getCode()) {
case SpecificException::ERROR_SOMETHING_BAD_HAPPENED:
/** react to the exception based on it's code, and try to recover **/
default:
/** react to every unexpected code, which may appear if the tried function is modified,
and throws a new exception code of the same type, and stop the execution of the
script, storing the error in the database **/
}
}
My problem is the following. I would like to "lock" some files from editing, for example some exception files. Why I would want to do this, is because I am trying to make part of my code portable, so that it can be used in other projects that we are working on also.
My idea was, that later on, in new projects, if something new comes up, and a new exception code should be made, I will extend the "locked" exception, and write the new error code as a const in the extending class. A real life scenario that could happen is the Account class. A bare Account class is already designed in the portable code, with login and register functionalities, but on each different project, these accounts will receive new functionalities which are project specific. Thus, in the new project, I will extend the portable projects Account class, and write the related functionalities there.
Theoretically, this could work, but I realized, that managing this later on will be a real pain I think, because:
when catching exceptions, I now either need to catch the parent exception, to catch every exception thrown, or I would have to write two (three, four, etc.) separate catch blocks, depending on how many times I extended the original parent exception
if I catch the parent exception, I can't just throw in all codes, without knowing the origin of the code (parent or child class)
Basically I would have to write simmilar try catch blocks to these:
try {
/** some code to try **/
} catch (SpecificException $e) {
/** this will catch everything that is SpecificException or SpecificExtendedException or SpecificDoubleExtendedException **/
switch ($e->getCode()) {
case SpecificException::ERROR_IN_SPECIFIC:
/** do something **/
break;
case SpecificException::ERROR_TWO_IN_SPECIC:
/** do something **/
break;
case SpecificDoubleExtendedException::ERROR_IN_SECOND_EXTENDED_EXCEPTION:
/** do something **/
break;
default:
/** as it was originally written **/
break;
}
}
Any one has any idea on how should I proceed? What would be the most natural way to "lock" some portable files, and add new Exception codes to the exceptions that are defined in those "locked" files?
You could simply add a method like this in your ProjectException class:
public function getCodeFor(string $name): int
{
return (int)constant("static::ERROR_$name");
}
This uses the constant helper function along with the static keyword to retrieve a constant defined in the actual class of the object that method was called on.
Then you could have your try block this way, which would work for any class extending SpecificException:
switch ($e->getCode()) {
case $e->getCodeFor('SOMETHING_BAD_HAPPENED'):
/** do something **/
break;
case $e->getCodeFor('SOMETHING_SIMILARLY_BAD_HAPPENED'):
/** do something **/
break;
case $e->getCodeFor('SOMETHING_SPECIFICALLY_BAD_HAPPENED'):
/** do something **/
break;
}
Demo: https://3v4l.org/sAQdE
Note: obviously, using strings makes you lose the IDE refactoring capability of your constants. But since you can't know in advance which classes will define a given code (or you can, but would like not to have to), then this is a reasonable workaround.
Note 2: there might be better ways to do things design-wise (and as #Barmar pointed out, asking Software Engineering might be more appropriate for architectural talk). This answer is merely attempting to provide a programming solution to your current issue, without altering said design.
Related
I develop a pretty big web application using laravel. Logging to a single file or daily files seems too messy for me.
My purpose is to create error logs which will be categorised and easy for retrieval to show on front-end for tech support. E.g. user entered wrong api-key, email, whatever.
P.S. Users don't always understand meaning of the error showed so they contact tech support.
Example for better understanding:
Suppose I have model relations: User->hasMany->Project and in some
project appears an exception I want it to be written and related to
the project. e.t.c. So I could do something like $some_project_model->logs()->get() and tech support got all logs related to
certain project.
What I have in my mind is separate table for logs which is related to certain Eloquent models. But to do so ExceptionHandler needs to know to which model an Exception is related to.
So how this can be implemented? Maybe I could assign custom ExceptionHandler to certain class?
Thank you for any suggestions.
So I've actually found a proper solution:
First of all it's needed to create Log table with polymorphic relation. Then to catch all the exceptions in certain context __call magic method can be used. __call method allows to wrap every object method call with custom code. Try-catch in this case.
Example:
class Loggable
{
public function __call($method, $arguments)
{
try {
return call_user_func_array([$this->certain_obj, $method], $arguments);
} catch (Exception $e) {
//creating new $log orm
$this->get_orm()->loggable()->save($log);
}
}
protected function do_something() {
//something is going on
throw new \Exception();
}
}
$loggable_obj = new Loggable();
$loggable_obj->do_something();
To make it work, you must make all loggable methods private or protected.
In case if you think that 'encapsulation' is not just another difficult word you can achieve the same result using proxy class.
Example:
class Proxy
{
private $some_obj;
public function __construct($some_obj)
{
$this->some_obj = $some_obj;
}
public function __call($method, $arguments)
{
try {
return call_user_func_array([$this->some_obj, $method], $arguments);
} catch (Exception $e) {
//creating new $log orm
$this->some_obj->get_orm()->loggable()->save($log);
}
}
}
$proxy = new Proxy($some_obj);
$proxy->whatever_method($foo, $bar);
So, in both cases I can get all logs related to some exact orm by calling ->logs()->get() on it.
Not a rocket science at all.
One approach might be to create your own custom exception (sub)class which you could add model and/or project information to at the point of throwing the exception. Then this information would be available in the ExceptionHandler.
You wouldn't have it for built in or third-party exception types though. For those you'd end up having to catch and rethrow where possible (and necessary), wrapping in your custom exception class, which probably isn't ideal.
Hi devs
I have a simple PHP OO project. I have an exception who appears 2 times.
Something like :
if (!$x) {
throw new Exception("Don't use this class here.");
}
I want to dev a class in order to edit this code like that :
if (!$x) {
throw new ClassUsageException();
}
How to dev the Excpetion class with default Exception message ?
Thanks
I'd advise creating new exception classes sparsly. It is no fun to check a multitude of exceptions left and right. And if you really feel the need, check what kinds of exceptions are already defined and where in that hierarchy your exception will fit and then extend that class, i.e. give the developers a chance to catch a (meaningful) range of exceptions without having to explicitly write one catch-block after the other.
I'm really not sure what you're trying to achieve here with Don't use this class here. but it could be an InvalidArgumentException (or something derived from that exception, if you really must). There are other mechanisms to prevent an instance of a certain class at a specific place though.
You can extend the Exception class
<?php
Class ClassUsageException extends Exception {
public function __construct($msg = "Don't use this class here.", $code = 0) {
parent::__construct($msg, $code); //Construct the parent thus Exception.
}
}
try {
throw new ClassUsageException();
} catch(ClassUsageException $e) {
echo $e->getMessage(); //Returns Don't use this class here.
}
I'm writing a PHP library and I have a concern. I have something similar to the following in my interfaces:
<?php
/**
* My interface
*
* ...
*/
interface MyInterface
{
/**
* This method does foo.
*
* #throws \RuntimeException If foo can't be done.
*/
public function fooAndBar();
}
?>
Now, the #throwsentry isn't perfectly right, since an interface doesn't actually do anything, and is used purely to abstract implementation details. However, I've always used it because all my implementations of the interface thrown the exception when something goes wrong.
But another developer might write an implementation that can't fail (so it can't throw an exception), or he/she might want to use another exception class.
In this situation, how should I document #throws in interface declarations? Should it even be documented?
Consider code where you consume the interface:
public function doSomething(MyInterface $my) { ... }
If even one of the implementations can throw an exception, you'll want to make sure you handle the possibility of exceptions.
So, yes, it should be documented.
Even if only one implementation throws an exception, exception handling still needs to be in place. Of course this doesn't mean that every method should have a #throws slapped on it. It should still only be used where appropriate (where you're expecting an implementation to legitimately need to throw an exception).
As a more concrete example, consider the following:
interface LogWriter
{
/**
* #throws LogWriterException
*/
public function write($entry);
}
class DbLogWriter
{
public function __construct(PDO $db)
{
//store $db somewhere
}
public function write($entry)
{
try {
//store $entry in the database
} catch (PDOException $e) {
throw new LogWriterException(...);
}
}
}
class NullLogWriter
{
public function write($entry) { }
}
Certain things could be done to attempt to lower the probability of an exception when writing to the database, but at the end of the day, it's not an exception safe operation. Therefore, DbLogWriter::write should be expected to throw exceptions.
Now consider the null writer though, that just discards entries. There's absolutely nothing at all that could ever go wrong there, therefore, no need for exceptions.
Yet what if you have some $log and all you know about it is that it's an implementation of LogWriter. Do you assume it doesn't throw exceptions and potentially accidentally let one bubble up, or do you assume that it can throw a LogWriterException? I would stay on the safe side and assume that it can throw a LogWriterException.
If all the user knows is that the $log is a LogWriter but only DbLogWriter is documented as throwing an exception, the user may not realize that $log->write(...) can throw an exception. Also, when FileLogWriter is later created, it would mean the expectations of what exceptions that implementation can and possibly will throw will already be set (no one would expect the FileLogWriter to throw a RandomNewException).
Interfaces define contracts. Whether an implementing class throws an Exception is an implementation detail in PHP because there is no throws keyword in the method signature (like in Java). Adding a #throws annotation cannot enforce the contract technically, but it can indicate convention (same for return values btw). Whether that is good enough is up to you to decide.
On a sidenote, if a developer comes up with an implementation that doesn't throw you dont have a problem because you will have to add a try/catch block anyway for those implementations that do throw (by convention). It would be a problem if an implementation starts throwing a different Exception than indicated in the DocBlock because then it wouldn't be catched.
I have a class named DataBoundObject which is quite big. And works good. Its an ORM class i have developed. One of its functions is to automatically do the setXXX() and getXXX() functions so that no coding is required. The following function does that
public function __call($strFunction, $arArguments) {
$strMethodType = substr ( $strFunction, 0, 3 );
$strMethodMember = substr ( $strFunction, 3 );
if(!is_callable(array($this, $strFunction)))
throw new Exception("Function cannot be called!");
switch ($strMethodType) {
case "set" :
return ($this->SetAccessor ( $strMethodMember, $arArguments [0] ));
break;
case "get" :
return ($this->GetAccessor ( $strMethodMember ));
break;
default :
throw new Exception ( "Non existent method call dude!" );
}
return false;
}
Now in a class which derives this i override one function like this:
<?php
require_once ('DataBoundObject.php');
/**
* ORM extension of BUBBLES_HOTEL_REVIEWS Tabel
* #author footy
* #copyright Ajitah
*
*/
class reviews extends DataBoundObject {
protected $ReviewID;
//Other codes
private function setReviewID($ReviewID) {
throw new Exception ( "Cannot set the review ID Explicitly" );
}
//Other codes
//Other codes
//Other codes
//Other codes
//Other codes
}
$x = new reviews();
$x->setReviewID(5);
?>
Thus finally i create a new object and try to call setReviewID() function which is private. Why is it not generating any Exception? Besides is_callable() is returning true!
EDIT
Mainly i need help to correct this problem so that it throws an Exception
You can't override private methods using __call magic in PHP. I allow myself to quote from php.net website http://php.net/language.oop5.visibility#92995, where your question is perfectly answered in a comment:
In the overriding, the method names and arguments (arg’s) must be
same.
final methods can’t be overridden.
private methods never participate in the in the overriding because
these methods are not visible in the child classes.
While overriding decreasing access specifier is not allowed
If you desperately need this feature - your (unconventional) options will be:
Use public scope for the method, documenting the function of its restrictions for other developers in PHPDoc string.
You can use PECL extensions like runkit sandbox http://php.net/book.runkit
Go for code generation or preprocessor.
Choose a different language.
EDIT
Note that, protected child method is also hidden form the parent, but there is always an option of overriding __call() in a child. Generally, making too much "magical" overrides may be a bad taste for such a serious undertaken as ORM design.
PS
I believe eventually developing a DSL could be an ultimate goal for your project. Before doing so, continuing your project is a nice way to gather respective experience. GL!
Working on a symfony application that uses nusoap (is this the best method for integrating soap work with php/symfony?) for taking credit card payments.
I've simplified an example of my code below.
What I'm struggling with is the best way to handle exceptions. The example below only has 1 custom exception (where should my custom exceptions reside within the directory structure of symfony? (lib/exception?)) But what happens when there are several different types of exceptions that handle a specific error? It's not very elegant to have a try/catch block with 20 odd exceptions.
I'm also not sure of where I should be throwing and catching. I need to set some user flashes to alert the user of any problems, so I figure the catching should be done in the actions controller rather than within the class that handles the soap call.
Could anyone please advise where I might be going wrong?
I hate messy code/solutions and want to stick to the DRY principle as much as possible. I think I might also be missing some built in symfony functionality that might help with this but whenever I search I usually find examples that are for symfony 1.2, I'm using 1.4.
Some examples would be great, thanks.
lib/soap_payment.class.php
class SoapPayment
{
public function charge()
{
/*assume options are setup correctly for sake of example*/
try
{
$this->call();
}
catch (SoapPaymentClientFaultException $e)
{
/* should this be caught here? */
}
}
private function call()
{
$this->client->call($this->options);
if ($this->client->hasFault())
{
throw new SoapPaymentClientFaultException();
}
}
}
apps/frontend/payment/actions/actions.class.php
class paymentActions extends sfActions
{
public function executeCreate(sfWebRequest $request)
{
/* check form is valid etc */
$soap_payment = new SoapPayment();
try
{
$soap_payment->charge();
}
catch (SoapPaymentClientFaultException $e)
{
/* or throw/catch here? */
$this->getUser()->setFlash('error', ...);
$this->getLogger()->err(...);
}
/* save form regardless, will set a flag to check if successful or not in try/catch block */
}
}
One not very well known feature of Symfony is that exceptions can manage the content sent in a response. So you could do something like this:
class SoapException extends sfException
{
public function printStackTrace() //called by sfFrontWebController when an sfException is thrown
{
$response = sfContext::getInstance()->getResponse();
if (null === $response)
{
$response = new sfWebResponse(sfContext::getInstance()->getEventDispatcher());
sfContext::getInstance()->setResponse($response);
}
$response->setStatusCode(5xx);
$response->setContent('oh noes'); //probably you want a whole template here that prints the message that was a part of the SoapException
}
}
If you need a cleaner handling of SOAP exceptions, like setting flashes, etc. you'll probably have to catch each exception. One idea here might be to create a generic SoapException class that is extended by more specific SoapExceptions so you don't have to catch a bunch of different types. The above code may be a useful fallback mechanism as well.
Finally, yes, you should place custom exceptions in lib/exception.