I'm building a custom autoloader based on Zend Framework's autoloading (related question here).
The basic approach, taken from that question, is
class My_Autoloader implements Zend_Loader_Autoloader_Interface
{
public function autoload($class)
{
// add your logic to find the required classes in here
}
}
and then binding the new autoloader class to a class prefix.
Now what I'm unsure about is how to handle errors inside the autoload method (for example, "class file not found") in a proper, ZF compliant way. I'm new to the framework, its conventions and style.
Do I quietly return false and let the class creation process crash?
Do I output an error or log message somehow (which would be nice to pinpoint the problem) and return false? If so, what is the Zend way of doing that?
Do I trigger an error?
Do I throw an exception? If so, what kind?
ZF itself uses two different approaches:
Zend_Loader (the old autoloading mechanism) throws a Zend_Exception in case something's wrong
Zend_Loader_Autoloader returns false when the used registered autoloader returns false
The Zend_Loader_Autoloader doesn't catch any exception thrown in the used autoloader to eventually your custom exception would bubble up through the Zend_Loader_Autoloader. I personally just return false in case I'm not able to load a requested class.
That depends on the kind of error. I'd consider it a fatal error if a class cannot be loaded. Thus I'd throw an Exception, e.g.
class My_Autoloader_Exception extends Exception {}
You will find that ZF uses a lot of custom Exceptions on the package level and also provides a class for this to extend from (though I'd consider this optional).
Incidentally, there is a usage example of Zend_Exception with their autoloader:
try {
// Calling Zend_Loader::loadClass() with a non-existant class will cause
// an exception to be thrown in Zend_Loader:
Zend_Loader::loadClass('nonexistantclass');
} catch (Zend_Exception $e) {
echo "Caught exception: " . get_class($e) . "\n";
echo "Message: " . $e->getMessage() . "\n";
// Other code to recover from the error
}
Related
A basic use case would be calling MyEventListener::class without having imported use MyNamespace\MyEventListener. The result would be a broken piece of code that's relatively hard to debug.
Does PHP 7 provide a directive to crash instead of returning the class name if no class exists? For example:
After calling use Foo\Bar;, Bar::class would return 'Foo\Bar'.
But if no import statement, PHP returns 'Bar', even though the class doesn't exist, not even in the global namespace.
Can I make it crash somehow?
The thing you need to keep in mind is that use Foo\Bar; is not "importing" anything. It is telling the compiler: when I say "Bar" I mean Bar from the namespace Foo.
Bar::class is substituted blindly with the string "Foo\Bar". It isn't checking anything.
Until you attempt to instantiate or interact with a class it will not check to see if it exists. That said, it does not throw an Exception, it throws an Error:
// this doesn't exist!
use Foo/Bar;
try {
$instanceOfBar = new Bar();
}
catch (Error $e) {
// catching an Exception will not work
// Throwable or Error will work
}
You can trap and check for non-existent classes at run time, but until you do it will happily toss around strings referring to classes that don't exist.
This is a blessing in the case of Laravel's IoC container and autoloader that abuses this to alias classes as convenient top-level objects. A curse, if you were expecting PHP to throw a fuss on ::class not existing.
Update:
My suggestion for anyone worried about this problem is to use PHPStan in your testing pipeline. It prevents a lot of mistakes, and unlike php -l it will catch if you were to try and interact with a non-existent class.
As far as I know you're going to get a nice error message when you try to instantiate a class that cannot be found through autoloading or explicitly added.
If you want to check if the class exists, first, try this:
$classOutsideNamespaceExists = class_exists('Bar');
$classInsideNameSpaceExists = class_exists('\\Foo\\Bar'));
Or you could try this syntax available since PHP 5.5:
class_exists(MyClass::class)
Finally, you can always use the tried and true method of a try-catch block.
try {
$instanceOfMyClass = new MyClass();
}
catch (Exception $e) {
// conclude the class does not exist and handle accordingly
}
PhpStorm proposes and generates hints like ArrayShape, Pure, etc.
But automatically it is adding
php use JetBrains\PhpStorm\ArrayShape;
or another.
Is not that dangerous that on some production server I will get error
'Class JetBrains\PhpStorm\ArrayShape not found'?
(c)LazyOne:
Well, just use composer require --dev jetbrains/phpstorm-attributes to add such classes to your project. See github.com/JetBrains/phpstorm-attributes
As long as instance of such a class is not actually gets instantiated (created) you should have no error because use statement is just a declaration.
I am useing a class in my code from the base framework. But it might not be available yet:
use BaseFramework\Libs\SpecialException;
So this use-Statement will result in an error. I.e. for frameworks, where this SpecialException is not available I would like to do:
use Exception as SpecialException;
so that I do not need to change my code.
I learned that the use is only creating an alias to the full named class.
I would like to use the originial SpecialException, if this is not possible I would like to use Exception.
I am wondering, what is the best practice or recommended way in PHP to solve this?
You can decide which one to throw using class_exists, it's going to be pretty nasty to actually use though.
Example:
try {
// do something
} catch (\Exception $e) {
// you'd still need to catch a common exception to all your custom types
if (class_exists('SomeCustomException')) {
throw new SomeCustomException; // or whatever
}
}
But you'd need to do that or something equally awful everywhere.
Your question suggests the actual answer here is to implement your own custom exception and throw that instead, as you have full control over it then.
Sometimes frameworks get around this kind of issue by having shared interoperability packages, so they can conform to common interfaces, throw the same exceptions and so on.
Since SpecialException might contains methods, variables and stuff that Exception doesn't contain, there is no rock-solid way to achieve what you need. Just replacing a class with a more generic one, might lead to trouble once you use some of the more dedicated methods.
You can see this post for working with class-aliases to achieve your desired behaviour, but for the reason meantioned above I wouldn't recommend it:
Why use class alisases?
You rather should use the factory-Pattern, just import the super-type of your eventually-custom-class and work with that super-type.
As soon, as you need to call a method on an instance, where you are not sure if that method is present (due to up-casting) - your class definition (or at least the method required) is placed into the wrong level inside the inheritance tree.
OK, thanks to some clues by #dognose and #bcmcfc this works for me:
use BaseFramework\Libs\SpecialException;
if (!class_exists("SpecialException")) {
class_alias("Exception", "SpecialException");
}
Why not just extend Exception? Something like this ...
namespace ProjectName\Exceptions\SpecialException;
class SpecialException extends Exception
{
// Implement custom properties and methods if required. Optional.
}
Here we have a custom class that uses SpecialException:
use \ProjectName\Exceptions\SpecialException;
class DocumentRepository
{
public static function fetchByID($docID)
{
throw new SpecialException("Document does not exist");
}
}
And now you don't need to worry about whether or not SpecialException exists or not. If calling code throws a regular Exception it will get caught, but if it throws a SpecialException it will still get caught as the new exceptions base class is Exception.
try
{
$doc = DocumentRepository::fetchByID(12);
}
catch(Exception $e)
{
die($e->getMessage());
}
Or, if you want to catch the SpecialException you can do (and I highly recommend this):
try
{
$doc = DocumentRepository::fetchByID(12);
}
catch(SpecialException $e)
{
die($e->getMessage());
}
Update to answer the problem in your comment
As a developer using a framework you have a location where you store your custom classes, files etc. right? Let me assume that this location is ProjectName/lib. And lets assume the framework you're using lives in the directory ProjectName/BaseFramework.
Your custom SpecialException will live in ProjectName/lib/Exceptions/SpecialException.php.
Currently, the framework doesn't include this exception. So in the files you wish to use SpecialException you use the following use line:
use \ProjectName\Exceptions\SpecialException
When the framework finally does implement this SpecialException you simply replace that use line with this one:
use \BaseProject\Exceptions\SpecialException
It's as simple as that.
If you try to do this in the way other users have suggested you will have dead code in your system. When SpecialException is finally implemented the checks on which type of Exception to use will be redundant.
This assumes you're using something like composer or something else that handles autoloading.
I just recently ugraded to php7 and had my first problem when upgrading some applications using try catch
PHP7 now implements its own error class to handle the errors so the old code that i had:
try {
dispatcher::run(new request);
} catch (Exception $e) {
require_once APP_PATH . 'error.php';
$error = new error($e);
}
now throws an error because error class is already defined:
Cannot declare class error, because the name is already in use in [...]
Now this got solved pretty easily just renaming my error class, but it had me wonder, is there a way to extend the error class of 7, and can be both compatible with php5?
Regards...
The short is you shouldn't do that, because it's a backwards incompatible change.
The long answer is yes, it's possible, but you still shouldn't do that, because it can still result in undesired behavior and still might require making changes to your existing PHP 5 implementation.
The Error class in PHP 7 implements the same Throwable interface that Exception implements. The idea was to just have a distinguishing way to identify those exceptions thrown by PHP itself and those thrown by your PHP code. So what you're doing here $error = new error($e) is basically the equivalent of $error = new Exception($e), which would be backwards compatible with PHP 5, assuming your custom Error class is compatible with the Throwable interface. Since you didn't provide your class implementation I can't say for sure, but generally speaking, if you hadn't already extended Exception in PHP 5, I somehow doubt it will be.
Recently I have been developing in Zend Framework. I came into a confusion during exception handling.
Can anybody let me know where is the best place to handle exception? Whether it is model or controller? It may be in terms of performance or usability or anything else.
If we want to catch all kinds of exceptions, we better enable our model to throws exception. You can read this post also link
You should handle exceptions in your controllers because sometimes the error messages of exceptions should be passed on to views. To avoid the dependency between models and views you should handle exceptions in your controllers.
Zend Framework all ready handles exceptions through the inbuilt errorController. You can enable it by placing the following line in your config file.
resources.frontController.throwExceptions = 0
Also, if you want to handle exceptions your self, rather then handling them at different places you can just handle them at one place. Something like below.
Tell Zend Framework to not handle exceptions. Do this in your application.ini
resources.frontController.throwExceptions = 1
Do following in your Bootstrap class.
Define a custom method to handle exceptions.
public function __handleExceptions(Exception $e){
//render a view with a simple error message for the user
//and send an email with the error to admin
}
Override the _bootstrap() and run() methods of Zend_Application_Bootstrap_Bootstrap in your Bootstrap class and catch the exceptions thrown during the bootstrap process or while running the application and call your exception handler as shown below.
protected function _bootstrap($resource = null)
{
try {
parent::_bootstrap($resource);
} catch(Exception $e) {
$this->__handleExecptions($e);
}
}
public function run()
{
try {
parent::run();
} catch(Exception $e) {
$this->__handleExecptions($e);
}
}
Now all your exceptions will be handled from a single place.
I'm using spl_autoload_register to load certain classes when they are needed, but how can I catch the error when the class is not found by my autoload method?
Right now the only solution I see is to display a cute error message in my autoload callback and stop the application so that error never gets to show.
But I don't want to stop the application. I want to continue and skip the instantiation of the missing class I needed (in my specific case, they are not strictly required for the app to continue to run)
Use class_exists() before loading and handle the result appropriately. If it exists, instantiate as per usual. If it doesn't, skip the instantiation.
In order to mute the error you could dynamically create the missing classes when they are called, though I do not recommend such approach.
The following code worked for me:
function __autoload($name) {
eval("class {$name} {}");
}
echo 'pass 1';
$a = new a();
echo 'pass 2';