I'm encountering a tricky problem with Inheritance and the hierarchy of Exceptions offered by the Standard PHP Library (SPL).
I'm currently building a helper library in PHP for REST-based APIs. These APIs can return their own error messages in the form of JSON objects, and these objects include information beyond the properties offered by a PHP Exception. Here's a quick example:
{"error":{"time":"2011-11-11T16:11:56.230-05:00","message":"error message","internalCode":10}}
Occasionally, "message" includes internal structure that could benefit from additional parsing. I like the idea of throwing a particular subclass of Exception, like so:
$error = $json->error;
throw new UnexpectedValueException($error->message, $error-internalCode);
Which later can be selectively caught:
catch (UnexpectedValueException $e)
{
...
}
And now we arrive at my dilemma: I'd like to extend the SPL Exception objects so that they can have a "time" attribute, and also perform the extra parsing of "message." However, I'd like to extend them at their level as opposed to creating an extension of the base Exception class, so that the ability to selectively catch exceptions is preserved. Lastly, I'd like to avoid creating thirteen different child classes (the number of exception types defined in the SPL), if at all possible.
Ideally, I could begin with a parent customException object:
class customException
{
public $time;
public $message;
public $internalCode;
public function __construct($time, $message, $internalCode)
{
$this->time = $time;
$this->message = $message;
$this->internalCode = $internalCode;
}
public function parseMessage()
{
// Do some parsing of message
return $parsedMessage;
}
}
Then, I'd have a Factory Class that would be able to be invoked like so:
class ExceptionFactory
{
static public function createException(Exception $e, $exceptionParent)
{
$json = json_decode($e->message);
return new customException($json->time, $json->message, $json->internalCode) extends $exceptionParent; // Won't work, but hopefully you get the idea
}
}
After reading php dynamic class inheritance, I can probably get there by using eval(), but that just feels wrong to me. If I have to write the thirteen child classes, then I'll find myself wanting to use multiple inheritance for the desired parent class $exceptionParent and customException. How would you recommend I solve this dilemma? Thank you in advance for your ideas!
Having something like:
class MyException extends \Exception {
const EXCEPTION_TYPE_FOO = 1;
const EXCEPTION_TYPE_BAR = 2;
const EXCEPTION_TYPE_JSON_MESSAGE = 3;
$protected $_data = array();
$protected $_exceptionType = null;
public function __construct( $type = null ) {
if( null !== $type )
$this->_exceptionType = $type;
}
public function __get( $name ) {
if( isset($this->_data[$name]) ) {
if( $name == 'message' ) {
switch( $this->_exceptionType ) {
case MyException::EXCEPTION_TYPE_JSON_MESSAGE:
return json_decode($this->_data[$name]);
// other exception types
default:
return $this->_data[$name];
}
}
return $this->_data[$name];
}
return null;
}
public function __set( $name, $value ) {
$this->_data[$name] = $value;
}
}
So now you could have:
$e = new MyException(MyException::EXCEPTION_TYPE_JSON_MESSAGE);
$e->time = time();
$e->code = '404';
$e->message = json_encode(array('testing'));
And when you catch it
catch( MyException $e ) {
print_r( gettype($e->message) );
}
Should return array.
I haven't tested the code, I just wrote it but you get the idea.
One common solution is to use "marker interfaces" to indicate "their level"
interface MyExceptionLevel extends ParentExceptionLevel {}
class MyException extends Exception implements MyExceptionLevel{}
try {
// code
} catch (MyException $e) {}
// or
try {
// code
} catch (MyExceptionLevel $e) {}
I recommend not to use too much magic, especially in such a sensible point like error/exception handling.
Related
I want to create an object of a class from a returned string but I am getting error Class **test_report** not found. My code:
public function display_report_builder($report_name = null)
{
$column_listing = new $report_name;// gets the test_report
return view('column_list')->with(['column_list_names' => $column_listing->columns]);
}
This isn't the better approach here. What you should do is to use a Factory design pattern:
class ReportFactory
{
public static function create($report_name)
{
switch($report_name) {
case 'test_report': return new TestReport();
default: throw new Exception('report not found');
}
}
}
Then you call with $column_listing = ReportFactory::create($report_name);
Why? Because you avoid "magic variables" with unknown data; you can trace errors properly; you can use namespace; you can extend functionalities easily, and easily activate or deactivate objects (or reports in this case); you have a cleaner code, and so on...
test if the class name (string) really is a valid class :
public function display_report_builder($report_name = null)
{
$column_list_names = null;
if (class_exists($report_name) && is_a($report_name, App\reports\test_report::class, true)) {
$column_listing = new $report_name;
$column_list_names = $column_listing->columns;
}
return view('column_list', compact('column_list_names'));
}
is_a() : Checks if the given object is of this class or has this class
as one of its parents.
I have a reoccuring problem that I am currently tackling like so -
a POST variable coming in to the script which has a platform, the platform is from a list such as: xbox,ps3,pc,mobileapp,mobilegame etc
for each different platform I want to be able to do something different in my script but in some cases I want code to do very similar things at the moment I do something like this:
$platformArray = array(
'ps3'=>array('displayName'=>'playstation 3','function'=>'funcPS3'),
'xbox'=>array('displayName'=>'Xbox','function'=>'funcXbox')
)
//similar amongst all platforms code on line below
echo 'you have a :'.$platformArray[$_POST['platform']]['displayName'].' for playing games';
call_user_func($platformArray[$_POST['platform']['function']);
function funcPS3(){
echo 'ps3 specific code';
}
function funcXbox(){
echo 'xbox specific code';
}
I want to move towards a OOP approach in my code, I want to use objects as my data storage medium rather than arrays as I'm doing now, but I do sometimes need to define attributes in the code ahead of time, how could I do the above but with objects?
I would recommend for you to start by understanding polymorphism. This lecture should be good start.
When you are trying to create behavior, based on some flag, you should implement two classes with same interface:
class Xbox
{
private $displayName = 'XBox 360';
public function identify()
{
// Xbox-specific stuff
return ':::::::::::'. $this->displayName;
}
}
class PS3
{
private $displayName = 'Playstation 3';
public function identify()
{
// playstation-specific stuff
return '+++'. $this->displayName . '+++';
}
}
The two classes have method with same name that would do different things;
$platform = $_POST['platform'];
// classes in PHP are case-insensitive
// expected values would be: xbox, Xbox, ps3, pS3
if ( !class_exists($platform) )
{
echo "Platform '{$platform}' is not supported";
exit;
// since continuing at this point would cause a fatal error,
// better to simply exit
}
$object = new $platform;
echo $object->identify();
Basically, in this case you really do not care, which type of platform you are working with. All you need to know is that they both have same public interface. This is called "polymorphic behavior".
I'm going to work from a very naive OO version, to what is considered "good" OO code, using polymorphic behavior and avoiding global state.
1. Not polymorphic and has global static data
This is pretty bad because it is really just a wrapper object over procedural code. It needs a map of functions to call for each type of platform.
class Platform {
private static $platformArray = array(
'ps3' => array(
'displayName'=>'playstation 3',
'function'=>'funcPS3'
),
'xbox' => array(
'displayName'=>'Xbox',
'function'=>'funcXbox'
)
);
private $type;
public function __construct($type) {
if (!array_key_exists($type, self::$platformArray)) {
throw new Exception("Invalid Platform type $type" );
}
$this->type = $type;
}
public function printCode() {
// This was a question embedded within your question, you can use
// http://php.net/manual/en/function.call-user-func.php
// and pass an instance with a method name.
return call_user_func( array($this, self::$platformArray[$this->type]) );
}
private function funcPS3(){
echo 'ps3 specific code';
}
private function funcXbox(){
echo 'xbox specific code';
}
}
$plat = new Platform($_POST['platform']);
$plat->printCode();
2. Polymorphic... but it still uses global data
By creating a base class you can implement behavior in subclasses, creating separate class for each concern. The big problem here is that subclasses need to register with a global registry.
abstract class Platform {
abstract protected function getCode();
public function printCode() {
echo $this->getCode();
}
private function __construct() {} // so only factory can instantiate it
private static $platformArray = array();
public static function create($type) {
if (!array_key_exists($type, self::$platformArray)) {
throw new Exception("Invalid Platform type $type" );
}
return new self::$platformArray[$type];
}
public static function addPlatform($type, $ctor) {
if (!is_subclass_of($ctor, 'Platform')) {
throw new Exception("Invalid Constructor for Platform $ctor" );
}
self::$platformArray[$type] = $ctor;
}
}
class PlatformXBox extends Platform{
protected function getCode() {
return 'xbox specific code';
}
}
Platform::addPlatform('xbox', 'PlatformXBox');
class PlatformPs3 extends Platform {
protected function getCode() {
return 'ps3 specific code';
}
}
Platform::addPlatform('ps3', 'PlatformPs3');
$plat = Platform::create($_POST['platform']);
$plat->printCode();
3. Polymorphic, no global data
By putting your code into a namespace, you avoid the static code in the base class and avoid the dangers of mapping post parameters directly into classes.
namespace platform {
interface IPlatform {
public function getDisplayName();
public function getCode();
}
class PlatformFactory {
static public function create($platformType) {
$className = "\\platform\\$platformType";
if ( !is_subclass_of($className, "\\platform\\IPlatform") ){
return null;
}
return new $className;
}
}
class Xbox implements IPlatform {
public function getDisplayName(){
return 'xbox';
}
public function getCode(){
return 'xbox code';
}
}
class Ps3 implements IPlatform {
public function getDisplayName(){
return 'ps3';
}
public function getCode(){
return 'ps3 code';
}
}
}
Now you can use those classes like the following
$platform = platform\PlatformFactory::create('xbox');
echo $platform->getCode() ."\n" ;
$platform2 = platform\PlatformFactory::create('ps3');
echo $platform2->getDisplayName()."\n";
$noPlatform = platform\PlatformFactory::create('dontexist');
if ($noPlatform) {
echo "This is bad, plaftorm 'dontexist' shouldn't have been created";
} else {
echo "Platform 'dontexist' doesn't exist";
}
You might want to create a class called platforms and within the class a different method for each platform:
class platforms {
//Create your variables here, also called properties.
public $displayName;
//Create a function, also called a method for each platform you intent to use.
public function xboxPlatform(){
//Code comes here what you want to do.
}
}
Hope this helps.
If I want to create a new object that needs certain informations like a product id or something like that but the input is bad how can I elegant manage such a case?
class Product
{
function __construct($id)
{
if(is_invalid_id($id))
{ return false; }
}
}
If I initialize it this way I still get an object (since return inside a constructor doesn't return anything). Another way would be to use exceptions which I then can catch but that's kinda unelegant. 3rd option is to use a static function which then checks the input and then returns the object.
class Product
{
static function init($id)
{
if(is_invalid_id($id))
{ return false; }
return new self($id);
}
private function __construct($id)
{
$this->id = $id;
}
}
$product = Product::init($productId);
The problem here is when I try to extend the class. Either I have to create a init() method for every class I extend (even if it is the exact same code) or return new self() always returns an instance of the parent class.
Throw an exception. Not sure why you consider it unelegant. Man, things were unelegant before exceptions (FALSE, -1, null)
For the problem with self:: you might be able to use late static binding (PHP5.3+):
<?php
class Product
{
static function init($id)
{
if(false)
{ return false; }
$s = get_called_class();
return new $s($id);
}
private function __construct($id)
{
$this->id = $id;
}
function getId()
{
return "Product-$this->id";
}
}
class Headphones extends Product
{
function getId()
{
return "Headphones-$this->id";
}
}
$c1 = Product::init(1);
$c2 = Headphones::init(1);
printf("c1 is %s, c2 is %s\n", $c1->getId(), $c2->getId());
// Prints: c1 is Product-1, c2 is Headphones-1
?>
Your third option is the Factory Pattern.
As you've noticed, the downside is that each class that needs this kind of check generally needs it's own factory method.
I'll give you a non-standard workaround that's universally frowned upon by purists: the hybrid constructor
And it's even more evil than it sounds, because it's actually just a wrapper procedure:
function Product($i) {
$prod = new Product($i);
return $prod->valid() ? $prod : new InvalidProduct();
}
class Product {
function __construct() { ... }
}
class InvalidProduct extends Product implements Stub { }
$new_prod = Product("oops123"); // what will it be?
It simply verifies the object instantantly. If there is something wrong, and now here comes the trick, return a specific stub or NULL object. It might implement a few of the interfaces, but generally cause no side-effects or print an error message once it's inevitable. This concept basically hinges on the viability of carrying a stub object around. It's sometimes more sensible to the application logic to have such a specialized instance than to use decorative test logic.
And then there's the second alternative: just bail with an exception.
I had:
in a class implementing Validator:
an $errormessages property
an isCorrect() method
In the isCorrect method, I had:
switch ($type):
case 'email':
isEmailCorrect();
case 'password':
isPasswordCorrect();
case 'x':
isXCorrect();
isEmailCorrect(), isPasswordCorrect() and isXCorrect() had access to the same property with all error messages
Now, I have:
in Validator:
an $errormessages property
in an EmailValidator class extending Validator:
an isCorrect() method
in a PasswordValidator class extending Validator:
an isCorrect() method
in a XValidator class extending Validator:
an isCorrect() method
Now, in a file calling the isCorrect() methods, I have:
$EmailValidator = new EmailValidator();
$PasswordValidator = new PasswordValidator();
$XValidator = new XValidator();
$EmailValidator->isCorrect(), $PasswordValidator->isCorrect() and $XValidator->isCorrect() don't have access to the same property with all error messages
$errormessages are in different instances of different classes. They should be one, but are three.
What now?
I think you should develop another class: a ValidatorChain, which takes an arbitrary amount of validators, and that aggregates the errormessages of all validators that it has tested
For reference see the docs on Zend Framework's Validator Chain
EDIT
Now that I re-evaluate your question (thanks to Bryan M's comment); why do you want each individual Validator to have access to other Validators' error messages? I would say that collecting each individual Validators' error messages is the responsibility of an object higher in the hierarchy.
If, however, you want individual Validators to be able to act based on context, in other words, based on what the results of other Validators are, then I suppose you could add a $context parameter to the isCorrect method. This could for instance accept an arbitrary amount of Validators or something similar.
Something like:
interface ValidatorInterface
{
public function isCorrect( array $context );
public function getMessages();
}
abstract class ValidatorContextOptions
{
const SHOULD_BE_PRESENT = 'shouldBePresent';
const SHOULD_NOT_BE_PRESENT = 'shouldNotBePresent';
const SHOULD_BE_VALID = 'shouldBeValid';
}
class EmailValidator implements ValidatorInterface
{
protected $_field;
protected $_contextOptions = array();
protected $_messages = array();
public function __construct( $field, array $contextOptions )
{
$this->_field = $field;
$this->_contextOptions = $contextOptions;
}
public function isCorrect( array $context = null )
{
foreach( $this->_contextOptions as $field => $options )
{
foreach( $options as $option )
{
switch( $option )
{
case ValidatorContextOptions::SHOULD_NOT_BE_PRESENT:
if( isset( $context[ $field ] )
&& $context[ $field ] instanceof ValidatorInterface )
{
$this->_messages[] = $field . ' should not be present';
return false;
}
break;
case ValidatorContextOptions::SHOULD_BE_PRESENT:
if( !isset( $context[ $field ] )
|| !$context[ $field ] instanceof ValidatorInterface )
{
$this->_messages[] = $field . ' should be present';
return false;
}
break;
case ValidatorContextOptions::SHOULD_BE_VALID:
if( !isset( $context[ $field ] )
|| !$context[ $field ] instanceof ValidatorInterface
|| !$context[ $field ]->isCorrect() )
{
$this->_messages[] = $field . ' should be valid';
return false;
}
break;
}
}
}
// some dummy function which you should replace with real validation
return isAnEmailAddress( $this->_field );
}
public function getMessages()
{
return $this->_messages;
}
}
Usage:
$emailValidatorContextOptions = array
(
'phone' => array(
ValidatorContextOptions::SHOULD_BE_PRESENT,
ValidatorContextOptions::SHOULD_BE_VALID
)
);
$phoneValidator = new PhoneValidator( $phoneString );
$emailValidator = new EmailValidator( $emailString, $emailValidatorContextOptions );
if( !$emailValidator->isCorrect( array( 'phone' => $phoneValidator ) ) )
{
print_r( $emailValidator->getMessages() );
}
What I've shown here, needs a lot more thinking (and I really mean A LOT), is buggy as hell and definately not bulletproof. But I hope you catch my drift of where I'm going with this.
Moreover, where do you insert the values in your validator that need to be validated anyway?
Well you could make an external properties factory to control access to your property, assuming you are talking about properties files that is the approach I usually take.
If you are referring to a shared field then you can place it in your base class and access it that way.
I'll often use Zend_Validate classes to perform the validation, and aggregate any error message to a property on the object that's being validated (as well as a flag that control valid status).
My setup would be similar to this:
class User {
public $email;
protected $_errorMessages = array();
public function validate()
{
$valid = true;
$emailValidator = new EmailValidator();
if (!$emailValidator->isCorrect($this->email)) {
$valid = false;
// validation message are added to the $errormessages property in
// the validator class upon failure of isCorrect()
$this->_errorMessages[] = $emailValidator->getMessages();
}
// repeat this for all your validators
return $valid
}
public function getErrorMessages()
{
return $this->_errorMessages();
}
}
// in your page....
if (!$user->validate()) {
$messages = $user->getErrorMessages();
}
If I read you right, you want multiple instances to share the same error messages property, such that you can instantiate several validators and have them all contribute to a single array.
If this is the case, there are a few ways to do it. One would be to create a validator manager class which has responsibility for instantiating and registering validators. Then once validation is complete you could call $validator_manager->getErrors() which would aggregate the errors present in all the validators registered with it.
Another way you could do it would be to use a singleton error store class, which you acquire in the constructor of each validator. Each validator's addError() method would then delegate the job to the singleton.
There are other methods still, but basically you're going to have to use another object, either for managing the validators or storing the errors.
Someone below mentioned using a singleton for this.
I am not convinced that it's a great use of that design pattern, especially since it's commonly held that singletons are the "anti-pattern" and often over/mis-used.
Nonetheless, keeping that in mind, here's an example along those lines:
<?php
//Error Class implemented as a Singleton
class ErrorClass
{
static private $instance = false;
static private $errorMessages;
function getInstance() {
if (!self::$instance) {
self::$instance = new ErrorClass();
self::$errorMessages = "No errors;";
}
return self::$instance;
}
public function setError($errorMessage){
self::$instance->errorMessages .= $errorMessage;
}
public function getError(){
return self::$instance->errorMessages;
}
}
abstract class AbstractClass
{
// Force Extending class to define this method
abstract protected function isCorrect($b);
// Common Method for setting error
public function setError($errorMessage) {
ErrorClass::getInstance()->setError($errorMessage);
}
// Common Method for getting error
public function getError() {
return ErrorClass::getInstance()->getError();
}
}
class EmailValidator extends AbstractClass
{
public function isCorrect($b) {
if(!$b) {
$this->setError('EmailValidator->isCorrect();');
}
}
}
class PasswordValidator extends AbstractClass
{
public function isCorrect($b) {
if(!$b) {
$this->setError('PasswordValidator->isCorrect();');
}
}
}
// Then in your code
$errorState = 1; // used for testing purposes
$EmailValidator = new EmailValidator();
$EmailValidator->isCorrect($errorState);
$PasswordValidator = new PasswordValidator();
$PasswordValidator->isCorrect($errorState);
echo $EmailValidator->getError();
echo $PasswordValidator->getError();
Is there a way to throw exceptions from an SPL Autoloader in PHP in case it fails? It doesn't seem to work under PHP 5.2.11.
class SPLAutoLoader{
public static function autoloadDomain($className) {
if(file_exists('test/'.$className.'.class.php')){
require_once('test/'.$className.'.class.php');
return true;
}
throw new Exception('File not found');
}
} //end class
//start
spl_autoload_register( array('SPLAutoLoader', 'autoloadDomain') );
try{
$domain = new foobarDomain();
}catch(Exception $c){
echo 'File not found';
}
When the above code is called, there is no sign of an exception, instead I get a standard "Fatal error: Class 'foobarDomain' not found in bla". And the execution of the script terminates.
This is not a bug, it's a design decision:
Note: Exceptions thrown in __autoload function cannot be caught in the catch block and results in a fatal error.
The reason is that there may be more than one autoload handlers, in which case, you don't want the first handler to throw an Exception and bypass the second handler. You want your second handler to have a chance at autoloading its classes. If you use a library which makes use of the autoloading feature, you don't want it bypassing your autoload handler because they throw Exceptions inside their autoloader.
If you want to check whether or not you can instantiate a class, then use class_exists and pass true as the second argument (or leave it out, true is the default):
if (class_exists('foobarDomain', $autoload = true)) {
$domain = new foobarDomain();
} else {
echo 'Class not found';
}
According to the comments in the documentation for spl_autoload_register, it's possible to call another function from the autoloader, which in turn would throw the exception.
class SPLAutoLoader{
public static function autoloadDomain($className) {
if(file_exists('test/'.$className.'.class.php')){
require_once('test/'.$className.'.class.php');
return true;
}
self::throwFileNotFoundException();
}
public static function throwFileNotFoundException()
{
throw new Exception('File not found');
}
} //end class
//start
spl_autoload_register( array('SPLAutoLoader', 'autoloadDomain') );
try{
$domain = new foobarDomain();
}catch(Exception $c){
echo 'File not found';
}
Here's a full-fledged factory object which demonstrates auto-loading, namespaces support, callables from non-static instances (with variable paths), handling of loading errors and custom exceptions.
abstract class AbstractFactory implements \ArrayAccess
{
protected $manifest;
function __construct($manifest)
{
$this->manifest = $manifest;
}
abstract function produce($name);
public function offsetExists($offset)
{
return isset($this->manifest[$offset]);
}
public function offsetGet($offset)
{
return $this->produce($offset);
}
//implement stubs for other ArrayAccess funcs
}
abstract class SimpleFactory extends AbstractFactory {
protected $description;
protected $path;
protected $namespace;
function __construct($manifest, $path, $namespace = "jj\\") {
parent::__construct($manifest);
$this->path = $path;
$this->namespace = $namespace;
if (! spl_autoload_register(array($this, 'autoload'), false)) //throws exceptions on its own, but we want a custom one
throw new \RuntimeException(get_class($this)." failed to register autoload.");
}
function __destruct()
{
spl_autoload_unregister(array($this, 'autoload'));
}
public function autoload($class_name) {
$file = str_replace($this->namespace, '', $class_name);
$filename = $this->path.$file.'.php';
if (file_exists($filename))
try {
require $filename; //TODO add global set_error_handler and try clause to catch parse errors
} catch (Exception $e) {} //autoload exceptions are not passed by design, nothing to do
}
function produce($name) {
if (isset($this->manifest[$name])) {
$class = $this->namespace.$this->manifest[$name];
if (class_exists($class, $autoload = true)) {
return new $class();
} else throw new \jj\SystemConfigurationException('Factory '.get_class($this)." was unable to produce a new class {$class}", 'SYSTEM_ERROR', $this);
//an example of a custom exception with a string code and data container
} else throw new LogicException("Unknown {$this->description} {$name}.");
}
function __toString() //description function if custom exception class wants a string explanation for its container
{
return $this->description." factory ".get_class($this)."(path={$this->path}, namespace={$this->namespace}, map: ".json_encode($this->manifest).")";
}
}
and finally an example:
namespace jj;
require_once('lib/AbstractFactory.php');
require_once('lib/CurrenciesProvider.php'); //base abstract class for all banking objects that are created
class CurrencyProviders extends SimpleFactory
{
function __construct()
{
$manifest = array(
'Germany' => 'GermanBankCurrencies',
'Switzerland' => 'SwissBankCurrencies'
);
parent::__construct($manifest, __DIR__.'/CurrencyProviders/', //you have total control over relative or absolute paths here
'banks\');
$this->description = 'currency provider country name';
}
}
now do
$currencies_cache = (new \jj\CurrencyProviders())['Germany'];
or
$currencies_cache = (new \jj\CurrencyProviders())['Ukraine'];
LogicException("Unknown currency provider country name Ukraine")
If there is no SwissCurrencies.php file in /CurrencyProviders/,
\jj\SystemConfigurationException('Factory jj\CurrencyProviders was unable to produce a new class banks\SwissCurrencies. Debug data: currency provider country name factory jj\CurrencyProviders(path=/var/www/hosted/site/.../CurrencyProviders/, namespace=banks\, map: {"Germany": "GermanBankCurrencies", "Switzerland":"SwissBankCurrencies"}')
With enough effort this factory can be extended to catch parse errors (How to catch error of require() or include() in PHP?) and pass arguments to constructors.