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.
Related
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.
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';
I have run into an interesting dilema. In a DataMapper class, I am generating a class name to be used for returned rows from a database.
The thing is, all of my classes are autoloaded, and can come from many places (library, application/models, etc.) and I wanted to check if the class name generated actually exists. Now, one would think that:
try
{
$test = new $className();
}
catch(Exception $ex)
{
// Class could not be loaded
}
But of course, php errors (instead of throwing an exception) saying the class could not be found... Not very helpful. Short of rewriting the autoloader in Zend_Loader to search all directories to see if the class could be loaded, is there anyway to accomplish this?
For anyone wondering why I would need to do this instead of just letting the Class Not Found error show up, if the class isn't found, I want to generate a class in a pre-determined location to make my life easy as this project goes along.
Thanks in advance!
Amy
P.S. Let me know if you guys need any more info.
PHP's function class_exists() has a flag to trigger the autoloader if the class should not be loaded yet:
http://www.php.net/class_exists
So you simply write
if (!class_exists($className)) {
// generate the class here
}
I'd like to use constants as config variables within my PHP applications, only when a constant doesn't exist (for whatever reason) it throws me a notice but I'd like that to be an error (or exception), just like when I try to echo a not existing variable.
Is that possible without using a separate function for getting constant values, something like this:
echo $non_existing_variable; // Error
echo NON_EXISTING_CONSTANT; // Now also an error
Been searching around a bit but couldn't find anything.
Seems kind of logical to me, when I try to use a variable that doesn't exist code execution quits immediately and throws an error, why isn't that with constants?
I'd suggest using defined with it you should be able to do something like
if (defined('CONSTANT')) {
echo CONSTANT;
} else {
throw new Exception('Undefined Constant.');
}
Edit:
The alternative to using this method as I stated in the comment below is to use a custom error handler, by using set_error_handler you should be able to catch the notice and take an action.
You could create a custom error handler that would
Check if the error was a Notice
Check if the error string contained 'Undefined class constant'
If so on both counts, throw your exception
This will get you what you want. The problem with this approach is you're now responsible for handling ALL errors. PHP's error handling will be completely bypassed.
Another approach would be to define a config class, and use its constants
class Config{
const CONFIG_ONE = 42;
}
//will produce a fatal error
if(Config::CONFIG_TWO){
//...
}
Instead of a notice you'll get a fatal error, which seems more correct. Unfortunately, you can't catch fatal errors without similar hijinks (see comments in the manual's set_error_handler entry).
A final option, which is way off the beaten path from where we started is to create a configuration singleton, with private class variables holding you values. This gives you full programatic control over what happens when someone tries to access an undefined configuration value, but you do loose the benefits of constants.
You might to write your own error handler.
More info here:
http://php.net/manual/en/errorfunc.examples.php
http://www.w3schools.com/php/php_error.asp
I'd suggest that you install an error-handler that turns all errors (regardless of level) into exceptions. Your code shouldn't have any errors, whether they are warnings or notices.
See: handling-errors-as-exceptions-best-methods
Use the defined function to check if there is a constant with the given name.
Yes i know about the defined function, but to check every time if a constant exists is quite a work, so i'm looking for some kind of php setting or trick without using a extra function to automaticly throw an error or exception in stead of a notice when a constant doesn't exist.
Seems kind a logical to me, when i try to use a variable that doesn't exist code execution quits immediatly and throws a error, why isn't that with constants?
Thanks so far (-:
I had the same problem and the only way I had to catch the error was:
try {
echo CONSTANT;
} catch(\Exception $e) {
// do something else
}
if (defined('CONSTANT')) { ... does not work for me!
if you are within the same class you can use this to access the constant
self::CONSTANT
basically add the self::
if you are in another class use the class name to access it
EXAMPLE::CONSTANT