Throwing Exceptions in an SPL autoloader? - php

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.

Related

PHP won't catch error inside class, only on caller

I'm trying to catch an error from my JWT class but i can't do it inside the class, the only place i can get it is from my main caller.
I'm calling this class with the error from my "API" where i start with the routing:
$router = new Router();
$router->all('/users', function()
{
$controller = new Controllers\UserController();
$controller->start();
});
$router->run();
After that i have my controller that will call my "API" class:
class UserAPI extends BaseAPI
{
protected $user;
protected $apiBase = "user";
function __construct($request, $origin)
{
parent::__construct($request);
$this->user = new User();
}
protected function logout()
{
if( isset($this->request[$this->apiBase . 'Data']) )
{
return $this->usuario->login($this->request[$this->apiBase . 'Data']);
}
else
{
return Helpers::errorResponse("User data not informed", 200);
}
}
}
And finally i have the problem, the User class where i want to catch an error but it wont work:
class User extends SimpleModel
{
public function logout($userData)
{
try
{
//At this point i will get an error since the provided token is invalid on purpose
$jwt = JWT::decode($userData['token'], SECRET_KEY, ['HS512']);
}
//Wont hit here even for a miracle
catch (Exception $exception)
{
echo "Caught ExceptFoo\n";
echo "Message: {$exception->getMessage()}\n";
}
}
}
The only place i could catch this error was on the routing file, wich is my index.php file.
For the JWT class i'm using Firebase JWT.
Relative class names (like Exception in your example) are always rooted to the namespace you are within. If you don't define a namespace, \ is used. Consider:
<?php
namespace Foo;
use Vendor\Package\Bar;
try {
Bar::throwAnException();
} catch (Exception $ex) {
die((string)$ex);
}
Here we have two relative class paths: Bar and Exception. PHP resolves Bar via the use statement to the absolute class path \Vendor\Package\Bar. PHP doesn't have a use statement corresponding to Exception, so PHP assumes you mean \Foo\Exception.
Clearly this isn't your intent. Unfortunately, PHP is silent when this situation occurs. It's bitten me a few times.

Unit testing the instance from a factory

I'm trying to create a unit test for a Factory class,
but PHPUnit is returning me a fatal error on the creation of the Product.
Fatal error: Class 'JsonStorage' not found
Files structure
-\
-libs
-My
-Storage
-IStorage.php
-IStorageFactory.php
-StorageFactory.php
-JsonStorage.php
-FileStorage.php
-tests
-StorageFactoryTest.php
This is the
Creator
use \tests;
namespace My\Storage;
class StorageFactory implements IStorageFactory
{
public static function build($type)
{
$classname = "Storage";
if (is_string($type)) {
switch($type = ucfirst(strtolower(trim($type)))) {
case "Json": // Fall-through
case "File":
$classname = $type . $classname;
break;
default:
throw new \Exception("Storage type not recognized or not yet implemented: please provide a valid type");
}
if (class_exists($classname)) {
try {
return new $classname;
} catch (Exception $e) {
throw new \Exception("Cannot create Storage object: please provide a valid type");
}
} else {
throw new \Exception("Class not recognized");
}
Product of the factory
can be
namespace My\Storage;
class FileStorage implements IStorage
{
private $filepath;
private $data;
private $handle = null;
public function __construct($filepath = null)
{
if (isset($filepath)) {
$this->setPath($filepath);
}
}
//....
}
or (the case I'm testing)
namespace My\Storage;
class JsonStorage extends FileStorage
{
// .... additional implementations
}
and I have a
Testing class
use \tests;
namespace My\Form\Storage;
require_once _DIR_ . "\..\My\Form\Storage\IStorage.php";
require_once .... <all the other classes>.....
require_once
class StorageFactoryTest extends \PHPUnit_Framework_TestCase
{
public function testCanBuildJSON()
{
$s = StorageFactory::build("JSON");
$this->assertInstanceOf($s, "IStorage"); // Or better directly 'JsonStorage'?
}
}
Maybe is something wrong with the namespaces, but I cannot understand why the JsonStorage class is not found.
The solution was easy (but took me a lot..)
I checked that the class was declared with get_declared_classes().
Then I read that assertInstanceOf needs a full qualified namespace if executed inside a namespace.
So I just changed the
Creator
$classname = __NAMESPACE__."\\".$classname;
if (class_exists($classname)) {
try {
return new $classname;
} //...
and the
Test
public function testCanBuildJSON()
{
$s = StorageFactory::build("JSON");
$this->assertInstanceOf(__NAMESPACE__."\\JsonStorage", $s);
}
Hope it could help someone.
PS - Feel free to add details or suggestions for better practices

Creating Class Inheritance Dynamically in PHP 5.3

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.

Why doesn't PHP catch a "Class not found" error?

In the following example, if the class does not exist, I want to catch the error and create a Null class instead.
But in spite of my try/catch statements, PHP simply tells me Class 'SmartFormasdfasdf' not found.
How can I get PHP to catch the 'class not found' error?
<?php
class SmartFormLogin extends SmartForm {
public function render() {
echo '<p>this is the login form</p>';
}
}
class SmartFormCodeWrapper extends SmartForm {
public function render() {
echo '<p>this is the code wrapper form</p>';
}
}
class SmartFormNull extends SmartForm {
public function render() {
echo '<p>the form "' . htmlentities($this->idCode) . '" does not exist</p>';
}
}
class SmartForm {
protected $idCode;
public function __construct($idCode) {
$this->idCode = $idCode;
}
public static function create($smartFormIdCode) {
$className = 'SmartForm' . $smartFormIdCode;
try {
return new $className($smartFormIdCode);
} catch (Exception $ex) {
return new SmartFormNull($smartformIdCode);
}
}
}
$formLogin = SmartForm::create('Login');
$formLogin->render();
$formLogin = SmartForm::create('CodeWrapper');
$formLogin->render();
$formLogin = SmartForm::create('asdfasdf');
$formLogin->render();
?>
Solution:
Thanks #Mchl, this is how I solved it then:
public static function create($smartFormIdCode) {
$className = 'SmartForm' . $smartFormIdCode;
if(class_exists($className)) {
return new $className($smartFormIdCode);
} else {
return new SmartFormNull($smartFormIdCode);
}
}
Because it's a fatal error. Use class_exists() function to check if class exist.
Also: PHP is not Java - unless you redefined default error handler, it will raise errors and not throw exceptions.
Old question, but in PHP7 this is a catchable exception. Though I still think the class_exists($class) is a more explicit way to do it. However, you could do a try/catch block using the new \Throwable exception type:
$className = 'SmartForm' . $smartFormIdCode;
try {
return new $className($smartFormIdCode);
} catch (\Throwable $ex) {
return new SmartFormNull($smartformIdCode);
}
php >= 7.0
php can catch 'class not found' as Throwable
try {
return new $className($smartFormIdCode);
} catch (\Throwable $ex) {
return new SmartFormNull($smartformIdCode);
}
You need to use class_exists to see if the class exists before you try and instantiate it.
Incidentally, if you're using a class autoloader, be sure to set the second arg to true.
Because php emits fatal error when you ty to create new object of non existing class. To make it work you will need php >= 5.3 and autoload function, where you should try to look for file with class definition or throw your custom exception.

PHP constructor to return a NULL

I have this code. Is it possible for a User object constructor to somehow fail so that $this->LoggedUser is assigned a NULL value and the object is freed after constructor returns?
$this->LoggedUser = NULL;
if ($_SESSION['verbiste_user'] != false)
$this->LoggedUser = new User($_SESSION['verbiste_user']);
Assuming you're using PHP 5, you can throw an exception in the constructor:
class NotFoundException extends Exception {}
class User {
public function __construct($id) {
if (!$this->loadById($id)) {
throw new NotFoundException();
}
}
}
$this->LoggedUser = NULL;
if ($_SESSION['verbiste_user'] != false) {
try {
$this->LoggedUser = new User($_SESSION['verbiste_user']);
} catch (NotFoundException $e) {}
}
For clarity, you could wrap this in a static factory method:
class User {
public static function load($id) {
try {
return new User($id);
} catch (NotFoundException $unfe) {
return null;
}
}
// class body here...
}
$this->LoggedUser = NULL;
if ($_SESSION['verbiste_user'] != false)
$this->LoggedUser = User::load($_SESSION['verbiste_user']);
As an aside, some versions of PHP 4 allowed you to set $this to NULL inside the constructor but I don't think was ever officially sanctioned and the 'feature' was eventually removed.
AFAIK this can't be done, new will always return an instance of the object.
What I usually do to work around this is:
Adding a ->valid boolean flag to the object that determines whether an object was successfully loaded or not. The constructor will then set the flag
Creating a wrapper function that executes the new command, returns the new object on success, or on failure destroys it and returns false
-
function get_car($model)
{
$car = new Car($model);
if ($car->valid === true) return $car; else return false;
}
I'd be interested to hear about alternative approaches, but I don't know any.
Consider it this way. When you use new, you get a new object. Period. What you're doing is you have a function that searches for an existing user, and returns it when found. The best thing to express this is probably a static class function such as User::findUser(). This is also extensible to when you're deriving your classes from a base class.
A factory might be useful here:
class UserFactory
{
static public function create( $id )
{
return (
filter_var(
$id,
FILTER_VALIDATE_INT,
[ 'options' => [ 'min_range' => 1, ] ]
)
? new User( $id )
: null
);
}
}
When a constructor fails for some unknown reason, it won't return a NULL value or FALSE but it throws an exception. As with everything with PHP5. If you don't handle the exception then the script will stop executing with an Uncaught Exception error.
maybe something like this:
class CantCreateException extends Exception{
}
class SomeClass {
public function __construct() {
if (something_bad_happens) {
throw ( new CantCreateException());
}
}
}
try{
$obj = new SomeClass();
}
catch(CantCreateException $e){
$obj = null;
}
if($obj===null) echo "couldn't create object";
//jaz303 stole my idea an wrap it into a static method

Categories