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.
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'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
}
I've used exceptions in Java and like the way it won't let you call a method unless you catch or throw the exceptions that it might throw.
I'm looking for something similar in PHP. I realise PHP is more dynamic than Java, and doesn't even let you define the exceptions that it throws, but what is the closest I can get?
We document our methods using PHP Doc, so something that triggered an E_WARNING if you called a method without the correct try/catch block, or threw an exception without the correct #thows comment, would be perfect.
There's no way to do it in PHP itself. You will have to parse PHP and figure it out yourself. Try writing phc plugin for this.
I don't think you can reasonably get very close at all, because the language core provides just nothing whatsoever for you to work with. At best, you'd wind up creating some kind of entirely user-space funcall/exception validation mechanism that would have an absolutely horrific impact on performance.
I'm not sure that you can accomplish your stated goal. The PHP environment doesn't analyze what a function might or might not do, which would generally be a compile-time operation for other languages (I would think). I don't think you can even find that sort of thing via Reflection.
You are wrong however when you say that you can't define the exceptions that get thrown, as the Exception base class is fully extendable. PHP doesn't throw any exceptions by default though, it triggers errors. There is a fundamental difference between triggered errors and Exceptions, the latter being a user-land construct for the most part.
This isn't your question, but I'd put out a suggestion that if you wanted to move to a fully Exception-oriented environment, you could write your own error handler using set_error_handler() and manage the PHP triggered errors, and have it throw out an Exception.
I think you can simply reproduce this behavior in PHP using exception handlers and reflection.
class YourException extends Exception {
public function __toString() {
return __CLASS__;
}
}
class MyObject {
public function __construct(){}
/**
* #throws MyException
*/
public function myMethod() {
return 'foo';
}
}
try {
$o = new MyObject();
$o->myMethod();
}
catch(YourException $e) {
$method = new ReflectionMethod('MyObject', 'myMethod');
$method->getDocComment();
$throws = // Get the #throws comment (foreach, regexp, whatever);
if($e === $throws) {
// do something
}
}
Setting your own exception handler.
Grab and analyse the comments with Reflection mechanism (see getDocComment)
I used to use this when I wanted to trigger errors in PHP, coming from a PHP4 background. Note I had my own set_error_handler() for handling these errors.
if ($error) {
trigger_error('Sorry, error has occured');
}
I can't remember where, but sometime ago someone told me I should be 'using exceptions'. As I'm re factoring a lot of my old code, I figured now is the time to get some good advice on my error handling implementation.
Now that I'm using PHP5 (and a bit smarter than I was when I wrote the older code), is my trigger_error() just an old way of doing things, and if so, what is the best way to handle errors in PHP5?
Yes, you may want to start looking into the PHP 5 exception model. Remember though that just because something is new doesn't mean that you must adopt it. Only adopt those features that you need and make sense in your domain.
That being said, I feel that exceptions are a good concept to grasp and even if you decide not to adopt them you will be all the better for the experience.
I would like to suggest that you read PHP: Exceptions - Manual:
PHP 5 has an exception model similar
to that of other programming
languages. An exception can be thrown,
and caught ("catched") within PHP.
Code may be surrounded in a try block,
to facilitate the catching of
potential exceptions. Each try must
have at least one corresponding catch
block. Multiple catch blocks can be
used to catch different classes of
exeptions. Normal execution (when no
exception is thrown within the try
block, or when a catch matching the
thrown exception's class is not
present) will continue after that last
catch block defined in sequence.
Exceptions can be thrown (or
re-thrown) within a catch block.
I would also encourage you to read What Is an Exception? (Note this is a Java tutorial but the concepts are universal)
When an error occurs within a method, the method creates an object and hands it off to the runtime system. The object, called an exception object, contains information about the error, including its type and the state of the program when the error occurred. Creating an exception object and handing it to the runtime system is called throwing an exception.
Edit: In order to implement a global exception handler (basically in order to establish a default exception handler that will handle previously unhandled exceptions) you will want to us the set_exception_handler function.
Using exceptions is the object-oriented way to trigger and handle your own application errors.
The PHP manual topic on exceptions is probably a good place to start.
Here is a small example:
function doSomething() {
if ($error) {
throw new Exception('Some descriptive error message.');
}
}
try {
doSomething();
}
catch (Exception $e) {
die('<p class="error">' . $e->getMessage() . '</p>');
}