Analyze the yiiunit\framework\console namespace using PHPStan (#20750)

This commit is contained in:
Maksim Spirkov
2026-03-02 17:38:08 +04:00
committed by GitHub
parent 7e818f6aa0
commit e229e9fc00
16 changed files with 69 additions and 45 deletions

View File

@@ -47,6 +47,7 @@ class PhpDocController extends ConsoleController
ConsoleController::class => [
'request',
'response',
'help',
],
Model::class => [
'errors',

View File

@@ -19,6 +19,8 @@ Yii Framework 2 Change Log
- Bug #20738: Fix `@return` annotations for `User::getAuthManager()` and `User::getAccessChecker()` (mspirkov)
- Bug #20738: Fix `@param` annotation for `$value` parameter in `AssetManager::setConverter()` (mspirkov)
- Bug #20739: Fix `@var` annotation for `BaseYii::$app` (mspirkov)
- Bug #20750: Fix `@return` annotation for `yii\console\Controller::runAction()` (mspirkov)
- Bug #20750: Add the missing `@property-write` annotation to `yii\console\Controller` (mspirkov)
2.0.54 January 09, 2026

View File

@@ -34,6 +34,7 @@ use yii\base\Module;
* @property Request $request The request object.
* @property Response $response The response object.
* @property-read string $help The help information for this controller.
* @property-write bool $help Whether to display help information about current command.
* @property-read string $helpSummary The one-line short summary describing this controller.
* @property-read array $passedOptionValues The properties corresponding to the passed options.
* @property-read array $passedOptions The names of the options passed during execution.
@@ -112,7 +113,7 @@ class Controller extends BaseController
* If the action ID is empty, the method will use [[defaultAction]].
* @param string $id the ID of the action to be executed.
* @param array $params the parameters (name-value pairs) to be passed to the action.
* @return int the status of the action execution. 0 means normal, other values mean abnormal.
* @return mixed the result of the action.
* @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
* @throws Exception if there are unknown options or missing arguments
* @see createAction

View File

@@ -16,7 +16,6 @@ parameters:
- tests/framework/base/stub
# TODO: Analyze these directories
- tests/framework/caching
- tests/framework/console
- tests/framework/data
- tests/framework/db
- tests/framework/di

View File

@@ -25,7 +25,7 @@ use yiiunit\TestCase;
*/
class ControllerTest extends TestCase
{
/** @var FakeController */
/** @var FakeController|FakePhp71Controller */
private $controller;
protected function setUp(): void

View File

@@ -14,9 +14,10 @@ class FakeHelpController extends HelpController
{
private static $_actionIndexLastCallParams;
public function actionIndex($command = null): void
public function actionIndex($command = null)
{
self::$_actionIndexLastCallParams = func_get_args();
return 0;
}
public static function getActionIndexLastCallParams()

View File

@@ -16,6 +16,7 @@ class FakeHelpControllerWithoutOutput extends HelpController
public function stdout($string)
{
return $this->outputString .= $string;
$this->outputString .= $string;
return 0;
}
}

View File

@@ -12,6 +12,7 @@ use yii\data\DataProviderInterface;
use yiiunit\framework\console\stubs\DummyService;
use yii\console\Controller;
use yii\console\Request;
use yiiunit\framework\base\Post;
class FakePhp71Controller extends Controller
{

View File

@@ -10,6 +10,8 @@ namespace yiiunit\framework\console\controllers;
use Yii;
use yii\caching\ArrayCache;
use yii\caching\CacheInterface;
use yii\console\Application;
use yii\console\controllers\CacheController;
use yiiunit\TestCase;
@@ -20,6 +22,12 @@ use yiiunit\TestCase;
* @group console
* @group db
* @group mysql
*
* @phpstan-type TestApplication Application&object{
* firstCache: ArrayCache,
* secondCache: ArrayCache,
* thirdCache: CacheInterface,
* }
*/
class CacheControllerTest extends TestCase
{
@@ -28,6 +36,11 @@ class CacheControllerTest extends TestCase
*/
private $_cacheController;
/**
* @var TestApplication
*/
private $application;
private $driverName = 'mysql';
protected function setUp(): void
@@ -69,17 +82,22 @@ class CacheControllerTest extends TestCase
],
]);
/** @var TestApplication */
$application = Yii::$app;
$this->application = $application;
$this->_cacheController = Yii::createObject([
'class' => 'yiiunit\framework\console\controllers\SilencedCacheController',
'interactive' => false,
], [null, null]); //id and module are null
if (isset($config['fixture'])) {
Yii::$app->db->open();
$this->application->db->open();
$lines = explode(';', file_get_contents($config['fixture']));
foreach ($lines as $line) {
if (trim($line) !== '') {
Yii::$app->db->pdo->exec($line);
$this->application->db->pdo->exec($line);
}
}
}
@@ -87,27 +105,27 @@ class CacheControllerTest extends TestCase
public function testFlushOne(): void
{
Yii::$app->firstCache->set('firstKey', 'firstValue');
Yii::$app->firstCache->set('secondKey', 'secondValue');
Yii::$app->secondCache->set('thirdKey', 'thirdValue');
$this->application->firstCache->set('firstKey', 'firstValue');
$this->application->firstCache->set('secondKey', 'secondValue');
$this->application->secondCache->set('thirdKey', 'thirdValue');
$this->_cacheController->actionFlush('firstCache');
$this->assertFalse(Yii::$app->firstCache->get('firstKey'), 'first cache data should be flushed');
$this->assertFalse(Yii::$app->firstCache->get('secondKey'), 'first cache data should be flushed');
$this->assertEquals('thirdValue', Yii::$app->secondCache->get('thirdKey'), 'second cache data should not be flushed');
$this->assertFalse($this->application->firstCache->get('firstKey'), 'first cache data should be flushed');
$this->assertFalse($this->application->firstCache->get('secondKey'), 'first cache data should be flushed');
$this->assertEquals('thirdValue', $this->application->secondCache->get('thirdKey'), 'second cache data should not be flushed');
}
public function testClearSchema(): void
{
$schema = Yii::$app->db->schema;
Yii::$app->db->createCommand()->createTable('test_schema_cache', ['id' => 'pk'])->execute();
$schema = $this->application->db->schema;
$this->application->db->createCommand()->createTable('test_schema_cache', ['id' => 'pk'])->execute();
$noCacheSchemas = $schema->getTableSchemas('', true);
$cacheSchema = $schema->getTableSchemas('', false);
$this->assertEquals($noCacheSchemas, $cacheSchema, 'Schema should not be modified.');
Yii::$app->db->createCommand()->dropTable('test_schema_cache')->execute();
$this->application->db->createCommand()->dropTable('test_schema_cache')->execute();
$noCacheSchemas = $schema->getTableSchemas('', true);
$this->assertNotEquals($noCacheSchemas, $cacheSchema, 'Schemas should be different.');
@@ -118,24 +136,24 @@ class CacheControllerTest extends TestCase
public function testFlushBoth(): void
{
Yii::$app->firstCache->set('firstKey', 'firstValue');
Yii::$app->firstCache->set('secondKey', 'secondValue');
Yii::$app->secondCache->set('thirdKey', 'secondValue');
$this->application->firstCache->set('firstKey', 'firstValue');
$this->application->firstCache->set('secondKey', 'secondValue');
$this->application->secondCache->set('thirdKey', 'secondValue');
$this->_cacheController->actionFlush('firstCache', 'secondCache');
$this->assertFalse(Yii::$app->firstCache->get('firstKey'), 'first cache data should be flushed');
$this->assertFalse(Yii::$app->firstCache->get('secondKey'), 'first cache data should be flushed');
$this->assertFalse(Yii::$app->secondCache->get('thirdKey'), 'second cache data should be flushed');
$this->assertFalse($this->application->firstCache->get('firstKey'), 'first cache data should be flushed');
$this->assertFalse($this->application->firstCache->get('secondKey'), 'first cache data should be flushed');
$this->assertFalse($this->application->secondCache->get('thirdKey'), 'second cache data should be flushed');
}
public function testNotFoundFlush(): void
{
Yii::$app->firstCache->set('firstKey', 'firstValue');
$this->application->firstCache->set('firstKey', 'firstValue');
$this->_cacheController->actionFlush('notExistingCache');
$this->assertEquals('firstValue', Yii::$app->firstCache->get('firstKey'), 'first cache data should not be flushed');
$this->assertEquals('firstValue', $this->application->firstCache->get('firstKey'), 'first cache data should not be flushed');
}
public function testNothingToFlushException(): void
@@ -148,14 +166,14 @@ class CacheControllerTest extends TestCase
public function testFlushAll(): void
{
Yii::$app->firstCache->set('firstKey', 'firstValue');
Yii::$app->secondCache->set('secondKey', 'secondValue');
Yii::$app->thirdCache->set('thirdKey', 'thirdValue');
$this->application->firstCache->set('firstKey', 'firstValue');
$this->application->secondCache->set('secondKey', 'secondValue');
$this->application->thirdCache->set('thirdKey', 'thirdValue');
$this->_cacheController->actionFlushAll();
$this->assertFalse(Yii::$app->firstCache->get('firstKey'), 'first cache data should be flushed');
$this->assertFalse(Yii::$app->secondCache->get('secondKey'), 'second cache data should be flushed');
$this->assertFalse(Yii::$app->thirdCache->get('thirdKey'), 'third cache data should be flushed');
$this->assertFalse($this->application->firstCache->get('firstKey'), 'first cache data should be flushed');
$this->assertFalse($this->application->secondCache->get('secondKey'), 'second cache data should be flushed');
$this->assertFalse($this->application->thirdCache->get('thirdKey'), 'third cache data should be flushed');
}
}

View File

@@ -18,8 +18,9 @@ class EchoMigrateController extends MigrateController
/**
* {@inheritdoc}
*/
public function stdout($string): void
public function stdout($string)
{
echo $string;
return 0;
}
}

View File

@@ -25,7 +25,7 @@ use yiiunit\framework\db\DatabaseTestCase;
class FixtureControllerTest extends DatabaseTestCase
{
/**
* @var FixtureConsoledController
* @var FixtureConsoledController|null
*/
private $_fixtureController;
@@ -244,5 +244,6 @@ class FixtureConsoledController extends FixtureController
{
public function stdout($string)
{
return 0;
}
}

View File

@@ -26,6 +26,7 @@ use yiiunit\TestCase;
*/
class MigrateControllerTest extends TestCase
{
/** @use MigrateControllerTestTrait<EchoMigrateController> */
use MigrateControllerTestTrait;
protected function setUp(): void

View File

@@ -19,13 +19,14 @@ use yiiunit\TestCase;
/**
* This trait provides unit tests shared by the different migration controllers implementations.
* @see BaseMigrateController
*
* @template TMigrateController of BaseMigrateController
* @phpstan-require-extends TestCase
*/
trait MigrateControllerTestTrait
{
/** @var TestCase $this */
/**
* @var string name of the migration controller class, which is under test.
* @var class-string<TMigrateController> name of the migration controller class, which is under test.
*/
protected $migrateControllerClass;
/**
@@ -75,7 +76,7 @@ trait MigrateControllerTestTrait
/**
* Creates test migrate controller instance.
* @param array $config controller configuration.
* @return BaseMigrateController migrate command instance.
* @return TMigrateController migrate command instance.
*/
protected function createMigrateController(array $config = [])
{

View File

@@ -29,8 +29,7 @@ class ServeControllerTest extends TestCase
{
$docroot = __DIR__ . '/stub';
/** @var ServeController $serveController */
$serveController = $this->getMockBuilder(ServeControllerMocK::class)
$serveController = $this->getMockBuilder(ServeControllerMock::class)
->setConstructorArgs(['serve', Yii::$app])
->onlyMethods(['isAddressTaken', 'runCommand'])
->getMock();
@@ -54,7 +53,6 @@ class ServeControllerTest extends TestCase
{
$docroot = __DIR__ . '/stub';
/** @var ServeController $serveController */
$serveController = $this->getMockBuilder(ServeControllerMock::class)
->setConstructorArgs(['serve', Yii::$app])
->onlyMethods(['runCommand'])
@@ -80,7 +78,6 @@ class ServeControllerTest extends TestCase
{
$docroot = '/not/exist/path';
/** @var ServeController $serveController */
$serveController = $this->getMockBuilder(ServeControllerMock::class)
->setConstructorArgs(['serve', Yii::$app])
->onlyMethods(['runCommand'])
@@ -104,7 +101,6 @@ class ServeControllerTest extends TestCase
$docroot = __DIR__ . '/stub';
$router = '/not/exist/path';
/** @var ServeController $serveController */
$serveController = $this->getMockBuilder(ServeControllerMock::class)
->setConstructorArgs(['serve', Yii::$app])
->onlyMethods(['runCommand'])
@@ -130,7 +126,6 @@ class ServeControllerTest extends TestCase
$docroot = __DIR__ . '/stub';
$router = __DIR__ . '/stub/index.php';
/** @var ServeController $serveController */
$serveController = $this->getMockBuilder(ServeControllerMock::class)
->setConstructorArgs(['serve', Yii::$app])
->onlyMethods(['runCommand'])

View File

@@ -18,8 +18,8 @@ class SilencedCacheController extends CacheController
/**
* {@inheritdoc}
*/
public function stdout($string): void
public function stdout($string)
{
// do nothing
return 0;
}
}

View File

@@ -19,9 +19,10 @@ trait StdOutBufferControllerTrait
*/
private $stdOutBuffer = '';
public function stdout($string): void
public function stdout($string)
{
$this->stdOutBuffer .= $string;
return 0;
}
public function flushStdOutBuffer()