views:

40

answers:

1

I'm building a PHP library that throws various custom Exceptions when it encounters errors. I need to log those exceptions and provide various implementations of the Logger so they could be logged in a file or a database.

The exceptions need to be logged whether they are caught or not, so that excludes implementing any of this in a custom exception handler. Therefore logging needs to be triggered in the constructor of the custom Exception classes themselves.

I also need to unit test, with phpUnit, that the Exception triggers the Logger.

What is the best way of the Exception objects having access to the Logger object?

Ideas/suggestions I've had include:

1) Inject the chosen logger implementation object (or it's Mock object when testing the Exception classes) into the objects that throw the Exceptions, and these objects will then inject the Logger object into the constructor when throwing each new Exception. This could get a bit messy though with the Logger object being passed around all over the place.

2) Use the strategy or a factory pattern (I've read up on the concepts but I'm not sure how to implement these in this case).

+1  A: 

That would be

I think the approach of Exceptions having the logger instead of using an Exception Handler is flawed. IMO, it violates the separation of concerns and exceptions should not contain logging logic. However, if you have to have that you could create a custom Exception that has a logger composite as a static member, but that would still leave you with no logging for all Exceptions not subclassed from this Exception. You could change the behavior of PHP's base Exception with runkit but that is dirty black voodoo magic monkeypatching.

Gordon
Exception Handlers only get triggered if the Exception is not caught don't they? I need to log the exception *even* if it is caught.
neilcrookes
@neilcrookes yes, correct. I understand why you want the logger to be implemented at the Exception level, but like I said, IMO this will only lead to trouble. If you need to log a catched exception, do so in the catch block.
Gordon
+1 for suggesting those logging libraries though. Now I just need to figure out how to utilise them.
neilcrookes
@Gordon thanks for your advice, can you expand on how implementing the logging at Exception will lead to trouble. It is a requirement/recommendation of the platform this library deals with that all exceptions be logged. And this library will be open sourced and used by devs other than myself, and I would like the integration of the library to be as quick and easy as possible.
neilcrookes
@neil think about it for a second. You want to log whenever an exception occurs. Since all Exceptions subclass Exception, the only feasible place to implement this is at the Exception class level. This is a core PHP class which you cannot change other than by modifying PHP itself or by using runkit (which is a no-go in production code). What if I want to use your lib but dont want to log every exception? Let the developer decide whether he wants to log catched exceptions.
Gordon
@Gordon thanks for your reply, but all exceptions in this library extend a my own custom base Exception class (which extends Exception) and if I put the Logging code in there I don't need to hack PHP. Furthermore as I said the platform that the library interacts with says the client should log every exception, so it's not really up to the developer whether they log catched Exceptions or not. He can catch and deal with them however he likes, but they still must be logged.
neilcrookes
@neil and what happens when a developer decides to throw an InvalidArgumentException or when PDO or any other extension or library used along with your platform throws an exception not subclassed from your exception? It's not feasible. Check with your requirements if that really needs to be every exception or every uncatched exception. Exceptions are well defined in PHP and changing how they behave is dirty in my opinion. That's really all I can say about that matter.
Gordon
@Gordon they don't need to be logged. Sorry, should have made it clear that the platform the library integrates with specifies all *it's* Exceptions must be logged. So those Exceptions in userland or PHP's own Exceptions don't need to be logged. The library handles a session exchange mechanism between the platform and a vendor application via encrypted message and signature URL paramters, so exceptions that my library throws that the platform recommends and specifies ought be logged include ones like MyLibraryInvalidSignatureException, MyLibraryInvalidMessageTimestampException etc.
neilcrookes
@Gordon (cont...) and these extend MyLibraryException, the constructor of which is where I am proposing the trigger to the Logger sits. Does that sounds OK?
neilcrookes
@neil I still think this is better handled in the catch block but in that case, you could add the logger as a static property to your custom MyLibraryException class on bootstrap and modify the constructor to log when one of the platform exceptions is created. This could be mocked during unittests by changing the static member.
Gordon
@Gordon, thanks for your help, tested, working and accepted ;-)
neilcrookes