Provide services and rules for a better PHPStan analyze on CakePHP applications, includes services to resolve types (Table, Helpers, Behaviors, etc) and multiple rules.
To use this extension, require it through Composer:
composer require --dev cakedc/cakephp-phpstan
If you also install phpstan/extension-installer, then you're all set!
Manual installation
If you don't want to use phpstan/extension-installer, include extension.neon in your project's PHPStan config:
includes:
- vendor/cakedc/cakephp-phpstan/extension.neon
Features included:
- Provide correct return type for
Cake\ORM\Locator\LocatorInterface::get() - Provide correct return type for
Cake\Controller\Controller::loadComponent() - Provide correct return type for
Cake\Controller\Controller::fetchTable() - Provide correct return type for
Cake\Controller\Component::fetchTable() - Provide correct return type for
Cake\Command\Command::fetchTable() - Provide correct return type for
Cake\Mailer\Mailer::fetchTable() - Provide correct return type for
Cake\View\Cell::fetchTable() - Provide correct return type for
Cake\Console\ConsoleIo::helper()
- Provide correct return type for
Cake\ORM\Table::getbased on your table class name - Provide correct return type for
Cake\ORM\Table::newEntitybased on your table class name - Provide correct return type for
Cake\ORM\Table::newEntitiesbased on your table class name - Provide correct return type for
Cake\ORM\Table::newEmptyEntitybased on your table class name - Provide correct return type for
Cake\ORM\Table::findOrCreatebased on your table class name
Examples:
//Now PHPStan know that \App\Models\Table\NotesTable::get returns \App\Model\Entity\Note
$note = $this->Notes->get(1);
$note->note = 'My new note';//No error
//Now PHPStan know that \App\Models\Table\NotesTable::newEntity returns \App\Model\Entity\Note
$note = $this->Notes->newEntity($data);
$note->note = 'My new note new entity';//No error
//Now PHPStan know that \App\Models\Table\NotesTable::newEmptyEntity returns \App\Model\Entity\Note
$note = $this->Notes->newEmptyEntity($data);
$note->note = 'My new note new empty entity';//No error
//Now PHPStan know that \App\Models\Table\NotesTable::findOrCreate returns \App\Model\Entity\Note
$note = $this->Notes->findOrCreate($data);
$note->note = 'My entity found or created';//No error
//Now PHPStan know that \App\Models\Table\NotesTable::newEntities returns \App\Model\Entity\Note[]
$notes = $this->Notes->newEntities($data);
foreach ($notes as $note) {
$note->note = 'My new note';//No error
}- Provide correct return type for
Cake\ORM\Table::patchEntitybased on the first argument passed - Provide correct return type for
Cake\ORM\Table::patchEntitiesbased on the first argument passed - Provide correct return type for
Cake\ORM\Table::savebased on the first argument passed - Provide correct return type for
Cake\ORM\Table::saveOrFailbased on the first argument passed - Provide correct return type for
Cake\ORM\Table::saveManybased on the first argument passed - Provide correct return type for
Cake\ORM\Table::saveManyOrFailbased on the first argument passed - Provide correct return type for
Cake\ORM\Table::deleteManybased on the first argument passed - Provide correct return type for
Cake\ORM\Table::deleteManyOrFailbased on the first argument passed - Provide correct return type for
Cake\ORM\Locator\LocatorAwareTrait::fetchTablebased on the first argument passed - Provide correct return type for
Cake\Mailer\MailerAwareTrait::getMailerbased on the first argument passed
Examples:
//Now PHPStan know that \App\Models\Table\NotesTable::get returns \App\Model\Entity\Note
$note = $this->Notes->get(1);
$notes = $this->Notes->newEntities($data);
//Since PHPStan knows the type of $note, these methods call use the same type as return type:
$note = $this->Notes->patchEntity($note, $data);
$text = $note->note;//No error.
$note = $this->Notes->save($note);
$text = $note->note;//No error.
$note = $this->Notes->saveOrFail($note);
$text = $note->note;//No error.
//Since PHPStan knows the type of $notes, these methods call use the same type as return type:
$notes = $this->Notes->patchEntities($notes);
$notes = $this->Notes->saveMany($notes);
$notes = $this->Notes->saveManyOrFail($notes);
$notes = $this->Notes->deleteMany($notes);
$notes = $this->Notes->deleteManyOrFail($notes);All rules provided by this library are included in rules.neon and are enabled by default:
This rule check if the target association has a valid table class when calling to Table::belongsTo, Table::hasMany, Table::belongsToMany, Table::hasOne and AssociationCollection::load.
This rule check if association options are valid option types based on what each class expects. This cover calls to Table::belongsTo, Table::hasMany, Table::belongsToMany, Table::hasOne and AssociationCollection::load.
This rule check if the target behavior has a valid class when calling to Table::addBehavior and BehaviorRegistry::load.
This rule disallow use of debug functions (dd, debug, debug_print_backtrace, debug_zval_dump, pr, print_r, stacktrace, var_dump and var_export).
The use of these functions in shipped code is discouraged because they can leak sensitive information or clutter output.
This rule disallow use of debug methods. The use of these methods in shipped code is discouraged because they can leak sensitive information or clutter output.
Methods covered:
- Cake\Error\Debugger::dump
- Cake\Error\Debugger::printVar
- DebugKit\DebugSql::sql
- DebugKit\DebugSql::sqld
This rule disallow array access to entity in favor of object notation, is easier to detect a wrong property and to refactor code.
This rule check if the target mailer is a valid class when calling to Cake\Mailer\MailerAwareTrait::getMailer.
This rule check if the target component has a valid class when calling to Controller::loadComponent and ComponentRegistry::load.
This rule check if the options (args) passed to Table::find and SelectQuery are valid find options types.
This rule check if the options (args) passed to Table::get are valid find options types.
This rule enforces that controller methods like render() and redirect() must be used (returned or assigned) to prevent unreachable code. These methods should not be called in void context - use them with a return statement or assign them to a variable to make the control flow explicit.
Examples:
// Bad - code after render() is unreachable
public function myAction()
{
$this->render('edit');
$this->set('data', 'value'); // This will never execute
}
// Good - explicit return prevents confusion
public function myAction()
{
return $this->render('edit');
}
// Also good - assignment is valid
public function myAction()
{
$response = $this->render('edit');
return $response;
}
// Bad - code after redirect() is unreachable
public function myAction()
{
$this->redirect(['action' => 'index']);
$this->Flash->success('Done'); // This will never execute
}
// Good - explicit return prevents confusion
public function myAction()
{
return $this->redirect(['action' => 'index']);
}Each rule has a parameter in cakeDC 'namespace' to enable or disable, it is the same name of the rule with first letter in lowercase. For example to disable the rule AddAssociationExistsTableClassRule you should have
parameters:
cakeDC:
addAssociationExistsTableClassRule: false
Fix intersection association phpDoc to correct generic object type, ex:
Change \Cake\ORM\Association\BelongsTo&\App\Model\Table\UsersTable to \Cake\ORM\Association\BelongsTo<\App\Model\Table\UsersTable>
To make your life easier make sure to have @mixin and @method annotations in your table classes.
The @mixin annotation will help phpstan know you are using methods from behavior, and @method annotations
will allow it to know the correct return types for methods like Table::get(), Table::newEntity().
You can easily update annotations with the plugin IdeHelper.
For bugs and feature requests, please use the issues section of this repository.
Commercial support is also available, contact us for more information.
If you'd like to contribute new features, enhancements or bug fixes to the plugin, please read our Contribution Guidelines for detailed instructions.