How to make PHPStan understand exception handling for child methods? - php

TL;DR
My questions being :
Is it a good practice to have exceptions inside an overriden methods ?
If so, how should I document and handle exceptions so that PHPStan doesn't block my pipelines ?
If not, how is the best way(s) to handle this kind of cases ?
Full context
I have some trouble regarding Handling Exception with PHPStan.
I have this abstract class :
abstract class GenericClass {
final public function handle(): void {
$this->myMethod();
}
abstract protected function myMethod(): void;
}
and this child class :
class MyClass extends GenericClass {
/**
* #throws MyException
**/
protected function myMethod(): void
{
throw new MyException();
}
}
Finally, I inject this child class inside a service class :
class ServiceClass {
private MyClass $myClass;
protected function __construct(MyClass $myClass)
{
$this->myClass = $myClass;
}
public function handle(): void
{
try {
$this->myClass->handle();
} catch(MyException $e) {
//error handling here
}
}
}
Obviously, abstract method myMethod cannot guess what kind of exception will be thrown (if any). It all a matter of context. Only the calling Service with the corresponding class can know the context in which the exception is thrown.
However, when PHPStan run through this code, it says that my catch is a dead code because it's never thrown.
This is a simple case with not many exception thrown but on a larger scale with multiple child classes and different exceptions thrown on these overriden methods, it could be time-consuming to update PHPDoc on parent method.
How do I make PHPStan understand that it is not dead code.
I only want to add #phpstan-ignore-next-line if no other solutions is possible.
Thanks in advance for all your replies

Related

PHP class implements interface where methods type hint interface getting compatibility errors

I am getting an error that to me is weird, but maybe I am doing something wrong or have a misunderstanding of how these work. I tried Googling and looking at similar questions, but none of them seemed to be the same, so that's where I'm at now.
An overview: I have a model as a service, that implements QueryableServiceProvider. QueryableServiceProvider is an interface that has a method single() that type hints a QueryStruct interface as the parameter. Then back in my model as a service, I have a single() method that type hints the SingleStruct class. The SingleStruct class implements the QueryStruct interface. However, when I try and run this I am getting the error:
Declaration of ModelServiceProvider::single(SingleStruct
$parameters): Model must be compatible with
QueryableServiceProvider::single(QueryStruct $parameters) in
ModelServiceProvider.php
So now for some code, which is going to be a little confusing because there are 5 different classes:
QueryableServiceProvider.php
interface QueryableServiceProvider {
public function single(QueryStruct $parameters);
}
QueryStruct.php
interface QueryStruct {
/** Stuff Here **/
}
SingleStruct.php
class SingleStruct implements QueryStruct {
/** Stuff Here **/
}
Model.php
class Model {
/** Model bits here **/
}
ModelServiceProvider.php
class ModelServiceProvider implements QueryableServiceProvider {
public function single(SingleStruct $parameters): Model { // This is the line the error references
/** Does the query and returns an instance of Model **/
}
}
I am using PHP 7.1, just for clarification. I know this works when it's not interfaces like this, but I don't see why this should work so maybe someone can help me out. Hopefully there is enough information here to actually be able to help me haha.
Thanks!
You've specified return type for single method of ModelServiceProvider, while it doesn't meet the similarity principle of method signature against single methods signature in QueryableServiceProvider interface.
single's signature in QueryableServiceProvider:
public function single(QueryStruct $parameters);
single's signature in ModelServiceProvider:
public function single(SingleStruct $parameters): Model {}
You should make those exactly same, either by specifying return type in the interface(like this):
interface QueryableServiceProvider {
public function single(QueryStruct $parameters) : Model;
}
class ModelServiceProvider implements QueryableServiceProvider {
public function single(SingleStruct $parameters): Model {
//method body
}
}
or omitting it in your class's method(this way):
interface QueryableServiceProvider {
public function single(QueryStruct $parameters) : Model;
}
class ModelServiceProvider implements QueryableServiceProvider {
public function single(SingleStruct $parameters): Model {
//method body
}
}
Update:
With thanks to #Kenyon's notice on testing the result on applying the previous fix, The second fix to the problem is to Maintain the exact same type hinting in parameter declaration between the interface & implementer class As stated here, while you can pass an argument to the class which needs to be only instanceof the parameter type.
So this MUST work:
interface QueryableServiceProvider {
public function single(QueryStruct $parameters) : Model;
}
class ModelServiceProvider implements QueryableServiceProvider {
public function single(QueryStruct $parameters): Model {
//method body
}
}
Well, according to this, Php type hinting not getting along with interfaces and abstract classes?
PHP intentionally does this. I don't really see why, since you can normally type hint interfaces and it seems like as long as you as a programmer were using the interfaces properly (i.e. requiring what you're using) then logically it'd be fine.
But, I guess it's intentional to work this way so I'll just change my code :/ Bummer, seems less descriptive to me, but that's alright.

different exceptions for each controller cakephp

I want to create exceptions for each controller in my project with cakephp. Example:
class UsersController extends AppController {
public function add(){
if(!$this->User->save($this->request->data)){
throw new UserException('Error save User/add');
}
}
}
class FriendsController extends AppController {
public function add(){
if(!$this->Friend->save($this->request->data)){
throw new FriendException('Error save Friend/add');
}
}
}
I'm trying this, but does not work...never paint the log in class exception user/friend
In app/Config/core.php
Configure::write('Exception.handler', 'UserException::handle');
Configure::write('Exception.handler', 'FriendException::handle');
In app/Config/bootstrap.php
App::uses('UserException', 'Lib');
App::uses('FriendException', 'Lib');
In app/Lib/UserException.php
class UserException {
public static function handle($error) {
$this->log($error)
}
}
In app/Lib/FriendException.php
class FriendException {
public static function handle($error) {
$this->log($error)
}
}
Any idea?
Regards!
An exception for each controller seems quite redundant. Why do you need 2 exception classes UserException and FriendException when they are identical, only difference being the message you pass? Also logging feature is inbuilt in core's ExceptionHandler. You can just throw a CakeException or RuntimeException with required message.
Do you realize your code:
Configure::write('Exception.handler', 'UserException::handle');
Configure::write('Exception.handler', 'FriendException::handle');
is overwriting the config same variable?
Anyway the 'Exception.handler' config is meant to specify the class with handles ALL your exceptions. You can confusing an exception class with an error handler class. Exception classes don't have any handle() method. Your exception classes should extend CakeException and override required method/property.
Use the CakeDC Utils plugin, it comes with an EmailErrorHandler that is configurable and can send an email depending on the error level.

Where to log database errors in MVC Architecture

Or any framework for that matter.
Using Zend Framework 2 as an example, I have the following table class:
<?php
namespace Contact\Model;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\TableGateway\AbstractTableGateway;
use Zend\Log\Logger;
class UserContactsTable extends AbstractTableGateway
{
protected $tableGateway;
/**
*
* #var \Zend\Log\Logger Instance
*/
protected $logger;
public function __construct(TableGateway $tableGateway, Logger $logger )
{
$this->tableGateway = $tableGateway;
$this->logger = $logger;
}
/**
* Save a contact
*
* #param \Sms\Model\UserContact $userContact
*/
public function saveUserContact(UserContact $userContact)
{
$data = array(
'user_id' => $userContact->user_id,
'contact_id' => $userContact->contact_id
);
try {
$this->tableGateway->insert($data);
} catch (\Exception $e) {
//log
$this->logger->crit($omeErrMsg);
}
}
}
?>
Should I be logging here? Should I tie my logger in to the table class?
Should I let the saveUserContact function throw an exception if insert fails and catch in the controller and log there?
What are the best practises?
My original idea was to create a class with some constant error messages, such as insert and update failures to be used in the table class by the logger, but I'm not sure what is the correct process here.
This is not really limited to PHP or Zend Framework 2 but just so happens to be the language I am using.
I'd be of the opinion that individual components of a system should be as decoupled as possible. So in this example, if saveUserContact happens to fail then it should probably result in an exception being thrown because this isn't the expected behaviour. This class doesn't need to know about what will happen 'further up the chain', such as error logging.
As you mentioned, it would be better to throw the exception and catch it in your controller (or some other form of listener perhaps), which would then handle the logging.
The benefit of such an approach is that your system will be much easier to test because you'll have less objects to stub when constructing your UserContactsTable (mock) object to test.
Generally, i feel like you should log failures where they happen (unless they're expected, in which case that's noisy) but propagate an exception up the stack (or a wrapper exception) so the caller can decide whether to ignore/retry/fail (and log its own, more business-logic-relevant message).

PHPUnit best practice for fixation of value returned by protected method?

consider the following code as PHP-style pseudo code to get my point across.
class C extends PHPUnit_Framework_TestCase
{
public function m1()
{
$v = $this->m2();
if($v == "x")
{
throw new Exception();
}
}
protected function m2()
{
[...];
return $v;
}
}
now I want to add a test that asserts that an Exception is thrown if m2() returns "x".
How can I simulate that?
I thought about using Reflection to redefine the method during runtime, but it seems that Reflection doesn't offer such a functionality and I would have to resort to experimental extensions like classkit or runkit. Would that be the way to go?
In this case I could extend the class and redefine m2() but where would I put that derived class then? In the same file as the test?
The latter solution wouldn't work anymore if I would choose m2 to be private.
I'm quite sure that there is a best practice to deal with this situation.
Ether I'm completely off on what you are trying to do here or you are doing something that confuses me greatly.
To me is seems that you are asking for is that you want to check that your test method throws an exception.
class C extends PHPUnit_Framework_TestCase
Why would you want to test your test method?
I'm going to assume that that is the real class and not your test
In that case I always strongly argue for just testing it as if the protected method would be inline in the public method.
You want to test the external behavior of your class. Not the implementation. The protected method is and implementation detail that your test shouldn't care about. That would mean that you would have to change your test when you change that protected method.
And from there on out:
class CTest extends PHPUnit_Framework_TestCase {
public function testM1NormalBehavior() {
}
/**
* #expectedException YourException
*/
public function testM1ThrowsExceptionWhenM2ConditionsAreMet() {
$c = new C('set_Up_In_A_Way_That_Makes_M2_Return_X');
$c->m1();
}
}
You can use a partial mock of C to force m2() to return "x". I'll assume that the extends PHPUnit_Framework_TestCase was accidental and that C is actually the class under test and not the unit test itself.
class CTest extends PHPUnit_Framework_TestCase {
/**
* #expectedException Exception
*/
function testM1ThrowsExceptionWhenM2ReturnsX() {
$c = $this->getMock('C', array('m2'));
$c->expects($this->once())->method('m2')->will($this->returnValue('x'));
$c->m1();
}
}

Testing objects with dependencies in PHPUnit

For objects which compose another object as part of their implementation, what's the best way to write the unit test so only the principle object gets tested? Trivial example:
class myObj {
public function doSomethingWhichIsLogged()
{
// ...
$logger = new logger('/tmp/log.txt');
$logger->info('some message');
// ...
}
}
I know that the object could be designed so that the logger object dependency could be injected and hence mocked in a unit test, but that's not always the case - in more complicated scenarios, you do need to compose other objects or make calls to static methods.
As we don't want to test the logger object, only the myObj, how do we proceed? Do we create a stubbed "double" with the test script? Something like:
class logger
{
public function __construct($filepath) {}
public function info($message) {}
}
class TestMyObj extends PHPUnit_Framework_TestCase
{
// ...
}
This seems feasible for small objects but would be a pain for more complicated APIs where the SUT depended on the return values. Also, what if you want to test the calls to the dependency object in the same was you can with mock objects? Is there a way of mocking objects which are instantiated by the SUT rather than being passed in?
I've read the man page on mocks but it doesn't seem to cover this situation where the dependency is composed rather than aggregated. How do you do it?
Following troelskn advise here's a basic example of what you should do.
<?php
class MyObj
{
/**
* #var LoggerInterface
*/
protected $_logger;
public function doSomethingWhichIsLogged()
{
// ...
$this->getLogger()->info('some message');
// ...
}
public function setLogger(LoggerInterface $logger)
{
$this->_logger = $logger;
}
public function getLogger()
{
return $this->_logger;
}
}
class MyObjText extends PHPUnit_Framework_TestCase
{
/**
* #var MyObj
*/
protected $_myObj;
public function setUp()
{
$this->_myObj = new MyObj;
}
public function testDoSomethingWhichIsLogged()
{
$mockedMethods = array('info');
$mock = $this->getMock('LoggerInterface', $mockedMethods);
$mock->expects($this->any())
->method('info')
->will($this->returnValue(null));
$this->_myObj->setLogger($mock);
// do your testing
}
}
More information about mock objects can be found in the manual.
As you seem to be aware already, Concrete Class Dependencies makes testing hard (or outright impossible). You need to decouple that dependency. A simple change, that doesn't break the existing API, is to default to the current behaviour, but provide a hook to override it. There are a number of ways that this could be implemented.
Some languages have tools that can inject mock classes into code, but I don't know of anything like this for PHP. In most cases, you would probably be better off refactoring your code anyway.
Looks like I misunderstood the question, let me try again:
You should use the singleton pattern or a factory for the logger, if it's not too late already:
class LoggerStub extends Logger {
public function info() {}
}
Logger::setInstance(new LoggerStub());
...
$logger = Logger::getInstance();
If you can't change the code, you could use a catch-all class that is overloading __call()
class GenericStub {
public function __call($functionName, $arguments) {}
}
There is actually a reasonably new extension for PHP class overloading released by the same guys that build PHPUnit. It lets you override the new operator in cases where you can't refactor the code, unfortunately it isn't that simple to install on Windows.
The URL is http://github.com/johannes/php-test-helpers/blob/master/

Categories