I really like the PhpStorm inspection tools. They help me a lot to write better code. Now I have the following situation and I am asking myself what's the best way to deal with such situations.
I have a function f with some precondition, for example like in the following code:
/**
* #param int $x
* #throws PreconditionException x is negative
*/
public function f(int $x): int
{
if ($x < 0) {
throw new PreconditionException("the input x is negative");
}
}
Then I use this function somewhere lets say the following:
f(5);
Now PhpStorm warns me with "Unhandled exception". But in this case I know that the exception will not be thrown, so adding a try block makes not really sense. Should I simply ignore this warning or what's the best way to deal with that?
From phpStorm version 2018.1 you can exclude any exceptions from analysis. Go to preferences->Languages & Frameworks->PHP and open Analysis tab.
Here you can add exceptions to Unchecked Exceptions list
#noinspection tag can be used to instruct PhpStorm to suppress an inspection.
The tag can be used above the line, above the method or on top of the file after <?php word:
/** #noinspection PhpDocMissingThrowsInspection */
/**
*
* Cancels order.
*
* #return bool
*/
public static function cancel()
{
if (!self::inProgress()) return false;
/** #noinspection PhpUnhandledExceptionInspection*/
static::current()->delete();
return true;
}
List of inspections can be found in this gist: https://gist.github.com/discordier/ed4b9cba14652e7212f5
You can also disable it via interface. ALT+ENTER then right arrow and Suppress ...
The correct way is add #throws tag into documentation (PhpStorm manual).
For example:
/**
* #param $userId
* #return array|null
* #throws \Exception <---------------------------
*/
public static function send($userId)
{
if (empty($userId)) {
throw new \Exception("User ID is missing", 1);
}
// ...
}
What helps for me is to set #throws to Null
Example:
/**
* #return SomeObject
* #throws Null
*
*/
Related
I'am using PhpStorm and have started using #template annotations.
However, I have trouble understanding exactly how they work in relation to #throws tags.
Here are some examples (of fantasy) to explain what I did not understand (or that I am wrong ...).
Let's start with this:
/**
* A custom ErrorException
*/
class CustomErrorException extends ErrorException {}
/**
* This class can throw exceptions
*/
class CanThrowExceptions
{
/**
* Checks `$value` is `true` or throws `$exception`
* #template TrueValue
* #template PassedException as \ErrorException
* #param TrueValue $value Value you want to check
* #param class-string<PassedException> $exception
* #return TrueValue
* #throws PassedException
*/
public static function isTrueOrErrorException($value, string $exception = \ErrorException::class)
{
if ($value) {
return $value;
}
throw new $exception;
}
}
In other words, the isTrueOrErrorException method can throw $exception.
$exception can be ErrorException (default) or another exception that extends ErrorException (like CustomErrorException).
So far, I believe I have used the #templates correctly (is that right?).
Now let's try to get PhpStorm to generate some code. I get this result:
class Example
{
/**
* #return bool
*/
public function test1(): bool
{
return CanThrowExceptions::isTrueOrErrorException(true);
}
/**
* #return bool
*/
public function test2(): bool
{
return CanThrowExceptions::isTrueOrErrorException(true, CustomErrorException::class);
}
}
There is no #throws tag, when I would expect ErrorException for the first method and CustomErrorException for the second.
Needless to say, if I try to edit by hand, the IDE will complain and report a problem to me.
I mean, I think I should normally get this (but this is not the case!):
class Example
{
/**
* #return bool
* #throws \ErrorException
*/
public function test1(): bool
{
return CanThrowExceptions::isTrueOrErrorException(true);
}
/**
* #return bool
* #throws \CustomErrorException
*/
public function test2(): bool
{
return CanThrowExceptions::isTrueOrErrorException(true, CustomErrorException::class);
}
}
So what have I done wrong so far?
Let's go to complicate the code.
I add the valueIsIntOrCustomErrorException() method to the CanThrowExceptions class, which is now like this:
class CustomErrorException extends ErrorException {}
/**
* This class can throw exceptions
*/
class CanThrowExceptions
{
/**
* Checks `$value` is an int or throws `CustomErrorException`
* #template IntValue
* #param IntValue $value
* #return IntValue
*/
public static function valueIsIntOrCustomErrorException($value)
{
//This returns a boolean, so I can't return here
self::isTrueOrErrorException(is_int($value), CustomErrorException::class);
return $value;
}
/**
* Checks `$value` is `true` or throws `$exception`
* #template TrueValue
* #template PassedException as \ErrorException
* #param TrueValue $value Value you want to check
* #param class-string<PassedException> $exception
* #return TrueValue
* #throws PassedException
*/
public static function isTrueOrErrorException($value, string $exception = \ErrorException::class)
{
if ($value) {
return $value;
}
throw new $exception;
}
}
Yet another oddity. Again, no #throws tags for valueIsIntOrCustomErrorException() method!
This is strange, because at least here I would have expected it, since it calls the isTrueOrErrorException() method.
Still, I keep wondering, where am I wrong?
I add some tests of this new method:
class Example
{
//Here same `test1 ()` and `test2 ()` as above
/**
* #return int
*/
public function test3(): int
{
return CanThrowExceptions::valueIsIntOrCustomErrorException(1);
}
}
Again, no #throws tags (should be #throws \CustomErrorException).
Can you help me, with examples or by suggesting corrections to my code? What I did not understand?
Thanks.
In a legacy project, I was confused when I tried finding the usage of a method in phpstorm and without success.
/**
* #param SomeEntity[] $someEntity
*
* #return bool
*/
protected function warmupSomeEntity(array $brandUniverses)
{
// I want to find this method's usage
}
Debugging a bit further, I found that the method is called on the fly in a rather abstract way via dynamic class method invocation:
/**
* #param string $warmer
* #param array $objects
*
* #throws RuntimeException
*/
public function warmupType($warmer, array $objects)
{
$method = "warmup$warmer";
if (method_exists($this, $
$this->{$method}($objects);
} else {
throw new RuntimeException("There is no warmer '$warmer'");
}
}
Is there a phpdoc syntax where I can document that the warmUpType method will call warmupSomeEntity, or warmupSomeOtherEntity so that I can find its usage again if I want to jump to the calling code block again?
The #uses keyword was what I was looking for as it:
Display a link to the documentation for an element, and create a backlink in the other element's documentation to this
It is supported by PhpStorm and the caller is found again.
/**
* #param string $warmer
* #param array $objects
* #uses warmupSomeEntity
* #uses warmupSomeOtherEntity
* #throws RuntimeException
*/
public function warmupType($warmer, array $objects)
{
...
}
Let's say I have a class with a method like this:
/*
*
* Loads the user from username.
*
* #param string $username The username
*
* #return UserInterface
*
* #throws userNotFoundException if the user is not found
*/
public function getUser($username)
{
// someFunction return an UserInterface class if found, or null if not.
$user = someFunction('SELECT ....', $username);
if ($user === null) {
throw new userNotFoundException();
}
return $user
}
Now let's say that someFunction could throw a InvalidArgumentException / RuntimeException / PDOException for XYZ reasons. What should I do? And what not?
Number 1
Add all the possible exceptions that could throw someFunction in php-docs.
/*
*
* Loads the user from username.
*
* #param string $username The username
*
* #return UserInterface
*
* #throws userNotFoundException if the user is not found
* #throws InvalidArgumentException
* #throws ...
*/
Number 2
Add a try-catch block to ensure that the method should throw exceptions ONLY documented
/*
*
* Loads the user from username.
*
* #param string $username The username
*
* #return UserInterface
*
* #throws userNotFoundException if the user is not found
* #throws RuntimeException
*/
public function getUser($username)
{
try {
$user = someFunction('SELECT ....', $username);
} catch (Exception $e) {
throw new RuntimeException();
}
if ($user === null) {
throw new userNotFoundException();
}
return $user
}
Number 3
Don't do anything.
Personally I would consider treating #throws similar to Java's checked exceptions
The way this works in Java is that basically exceptions that inherit from RuntimeException can be thrown and don't have to be handled. Any other types of exceptions must have a try-catch block to handle them. This handling code must be in the caller.
Basically in PHP kinda like this:
When a method has a #throws annotation, you have to add code to handle its exceptions.
Any exceptions that don't get mentioned are optional for handling in the calling code.
Now, I don't 100% follow this principle myself. The whole handling exceptions thing is sort of up to the programmer's preference, but this is just some thoughts on how I think it could be handled in a reasonable fashion.
With regards to documentation, if a function explicitly throws an exception then it should be included in the function's documentation. Thus, for each throw statement, there should be a corresponding #throws in the PHP documentation.
With regards to handling, if there are some operations that should be executed when the exception is thrown then catch it. Otherwise, let it bubble up -- provided there is a catch statement going to handle it later.
Update:
A few years later, I have changed to the opinion that one should only let an exception "bubble up" unmodified when the exception is still relevant to the module's level of abstraction. Catch and re-throw strategies should be employed to make the exception more meaningful. It should also make error handling more secure by avoiding unnecessary disclosure of information about modules underlying the abstraction.
/**
* #throws UserNotFoundException
*/
public function getUser($username)
{
try {
$user = someFunction('SELECT ....', $username);
} catch (DatabaseException $dbe) {
/* Re-throw since a database exception may no longer be
* meaningful to the caller.
*/
throw new UserNotFoundException();
}
return $user
}
From a documentation maintenance point of view, I would only be adding the #throw lines for exceptions that are specifically thrown otherwise you will quickly get your documentation out of date.
Number two, but perhaps throw before return since throw actions will occur before a return...
My site is completely custom, as such I like to know when I have poorly written code. I use set_exception_handler and set_error_handler to use custom classes to log errors to a file. This includes notices and warnings.
Within my own code, this is fine as I get very few logs and those that I do get are things I actually want to fix.
However, i've just started using simplePie and because it's PHP4 compatible I get tons of notices, primarily for things like calling functions statically or passing things by reference incorrectly.
It's too much for me to go through and fix simplePie, if it wasn't I wouldn't be using it in the first place.
Is there a way that I can specifically ignore errors generated by a certain file or class? Here's an overview of what of my very basic exception handling looks like:
set_exception_handler("CustomExceptionHandler");
set_error_handler("customErrorHandler");
/**
* CustomExceptionHandler()
*
* This is used if an exception is thrown that isn't caught.
*
* #param object $e The exception as an object
*/
function CustomExceptionHandler(Exception $e) {
exitToError($e->getMessage());
}
/**
* customErrorHandler()
*
* This is called for any errors no matter what their level.
*/
function customErrorHandler($errno, $errstr, $errfile, $errline) {
if(in_array($errno, array(E_USER_ERROR, E_RECOVERABLE_ERROR))) {
throw new CustomErrorException($errstr, 0, $errno, $errfile, $errline);
} else {
CustomException::logError($errstr, $errno, $errfile, $errline);
}
return FALSE;
}
/**
* class CustomErrorException
*
* Used by custom_error_handler() to convert all fatal
* errors to exceptions.
*
* #see custom_error_handler()
* #see http://www.php.net/manual/en/class.errorexception.php
*/
class CustomErrorException extends CustomException {
/**
* $severity
*
* The severity level of the exception
*
* #access protected
* #var int
*/
protected $severity;
/**
* __construct()
*
* Constructs the new exception
*
* #access public
* #param string $message The Exception message
* #param int $code The Exception code
* #param int $severity The severity level of the exception
* #param string $filename The filename where the exception was thrown
* #param int $lineno The line number where the exception was thrown
*/
public function __construct($message, $code = null, $severity = E_ERROR, $filename = null, $lineno= null) {
$this->message = $message;
$this->code = $code;
$this->severity = (int)$severity;
$this->file = $filename;
$this->line = $lineno;
self::logError($this->message,$this->code,$this->file,$this->line,$this->getTraceAsString());
}
}
/**
* class CustomException
*
* Overwrites Exception to give us more control on how
* exceptions are handled and logged.
*
* #see http://www.php.net/manual/en/language.exceptions.extending.php
*/
class CustomException extends Exception {
/**
* __construct
*
* We call the parent contruct as we still want it to do all its magic. We just want
* overwrite this method so that we can log the error exactly how we want.
*/
public function __construct($message, $code = 0, Exception $previous = NULL) {
parent::__construct($message, $code);
self::logError($this->getMessage(),$this->getCode(),$this->getFile(),$this->getLine(),$this->getTraceAsString());
}
/**
* __toString()
*
* We overwrite this function so that we can use our stringBuilder function.
*/
public function __toString() {
return self::stringBuilder($this->getMessage(),$this->getCode(),$this->getFile(),$this->getLine(),$this->getTraceAsString());
}
/**
* stringBuilder()
*
* We use this method so that we have a standard method of building error
* strings that anything can tap into.
*
* #access public
* #param string $message the exception message
* #param int $code the code assigned to this exception
* #param string $file the file where the exception occurred
* #param int $line the line where the exception occurred
* #param string $trace backtrace
*/
public function stringBuilder($message, $code, $file, $line, $trace='') {
//return "[".date("d-M-Y H:i:s")."] ".$this->getMessage()." in ".$this->getFile().":".$this->getLine()."\nStack trace:\n".$this->getTraceAsString()."\n";
return "[".date("d-M-Y H:i:s")."] ".$message." in ".$file.":".$line."\n";
}
/**
* logError()
*
* We use a method so that we have a standard way of saving errors
* to a log.
*
* We use XML because it's easy to parse.
*
* #access public
* #param string $message the exception message
* #param int $code the code assigned to this exception
* #param string $file the file where the exception occurred
* #param int $line the line where the exception occurred
* #param string $trace backtrace
* #todo We could improve it to write to the xml file using DomDocument
* as laid out here http://www.xml-training-guide.com/append-delete-data-from-xml-using-php.html
*/
public function logError($message, $code, $file, $line, $trace='') {
//Save it to a standard text file to guarentee saving the error
file_put_contents(ROOT_URL.ERROR_LOG_TXT,self::stringBuilder($message, $code, $file, $line, $trace),FILE_APPEND);
}
}
and here is an example of two of the errors simplePie throws up:
[01-Aug-2010 00:50:33] Assigning the return value of new by reference is deprecated in ***\SimplePie.php:738
[01-Aug-2010 00:50:34] Non-static method SimplePie_Misc::parse_date() should not be called statically in ***\SimplePie.php:60
Is there a way that I can specifically ignore errors generated by a certain file or class?
Should be easy! You could check in your custom error handler
function customErrorHandler($errno, $errstr, $errfile, $errline)
whether $errfile is in one of the simplePie files, and in that case return true; silently.
You could inspect the $errfile parameter passed to your error handler to see if the error originates from somewhere inside simplePie.
Documentation: http://php.net/manual/en/function.set-error-handler.php
You have to remove these errors.
1. Remove & in assignments like $foo =& new Object()
2. You can't access method in statticly way if it's not a static method so instead Object::method() you should try using Object->method() or try to change function method() to static function method()
Or the best solution - try to find library in php5
Is it really necessary do something like this:
/**
* ...
*
* #return void
*/
I have quite a few methods that don't have a return value, and it seems really redundant to put something like this in the comment. Would it be considered bad form to leave it out?
If it makes it clear for the documentation, then leave it in, but it isn't strictly necessary. It's an entirely subjective decision.
Personally, I would leave it out.
EDIT
I stand corrected. After a little googling, the wikipedia page says:
#return [type description] This tag should not be used for constructors or methods defined with a void return type.
The phpdoc.org website says:
#return datatype description
#return datatype1|datatype2 description
The #return tag is used to document the return value of functions or methods. #returns is an alias for #return to support tag formats of other automatic documentors
The datatype should be a valid PHP type (int, string, bool, etc), a class name for the type of object returned, or simply "mixed". If you want to explicitly show multiple possible return types, list them pipe-delimited without spaces (e.g. "#return int|string"). If a class name is used as the datatype in the #return tag, phpDocumentor will automatically create a link to that class's documentation. In addition, if a function returns multiple possible values, separate them using the | character, and phpDocumentor will parse out any class names in the return value. phpDocumentor will display the optional description unmodified.
Sooo... Based on that, I would say leave out the void. It's non-standard, at least.
According to phpDocumentor, #return void is valid:
http://www.phpdoc.org/docs/latest/guides/types.html#keywords
...
this type is commonly only used when defining the return type of
a method or function. The basic definition is that the element
indicated with this type does not contain a value and the user should
not rely on any retrieved value.
For example:
/**
* #return void
*/
function outputHello()
{
echo 'Hello world';
}
In the example above no return statement is specified and thus is the
return value not determined.
Source: http://www.phpdoc.org/docs/latest/for-users/phpdoc/types.html (archived page).
I have to edit my answer because of something I have learned recently.
Using #return void instead of #return null has a very special meaning, consider the following two examples of PHP code.
<?php
/**
* #return void
*/
function return_never() {
echo "foo";
}
/**
* #return null|string
*/
function return_sometimes() {
if ($this->condition()) {
return "foo";
}
}
In the first example PHP will actually return NULL, since PHP always returns NULL. But the returned value is of no use to the caller since it does not say anything about what the function did. IDEs can use the documented information of #return void to indicate the developer that a return values is used which serves no purpose.
<?php
$foo1 = return_never();
$foo2 = return_sometimes();
The first call is senseless since the variable will always contain NULL, the second one might actually contain something. This is becoming even more interesting if we put the function calls into a conditional.
<?php
if (($foo1 = return_never())) {
// Dead code
var_dump($foo1);
}
if (($foo2 = return_sometimes())) {
var_dump($foo2);
}
As you can see, #return void has its use cases and should be used if applicable.
Also note that it is going to be a part of the upcoming PHP PSR-5 standard.[1]
[1] http://www.php-fig.org/psr/
As of php 7.1, void is a valid return type and can be enforced on a function.
I would always add it on the docblock.
Another benefit of writing it, is to differentiate the void methods from the methods that may return anything but don't have a #return entry on the docblock by negligence.
Here is how I understand and use PhpDocumentor annotations:
<?php
/**
* This method always returns string.
* #return string
*/
public function useCase1()
{
return 'foo';
}
/**
* This method returns 2 data types so list them both using pipeline separator.
* #return string|false
*/
public function useCase2()
{
if ($this->foo === 1) {
return 'foo';
}
return false;
}
/**
* This method performs some operation and does not return anything so no return
* annotation is needed.
*/
public function useCase3()
{
$this->doOperation();
$this->doAnotherOperation();
}
/**
* If condition passes method returns void. If condition does not pass it returns
* nothing so I think that specifying the return annotation with void is in space. :)
* #return void
*/
public function useCase4()
{
if ($this->foo === 1) {
$this->doOperation();
return;
}
$this->doAnotherOperation();
}
Personally, I think the big thing missing from this is that documenting a function returns at all is important. Currently standards dont have any documentation for functions that never return....hence a return void is way of saying yes this function does actually return.
Consider this code block
<?php
/**
* #return void
*/
function return_void() {
echo "foo";
}
/**
* #return null|string
*/
function return_sometimes() {
if ($this->condition()) {
return "foo";
}
}
/**
* This function actually doesnt return at all - it kills the script
**/
function noreturn() {
//do somthing then
die(); //or exit()
}
Clearly the use of #return at least indicates the function does return