Skip to content

Commit 219fbd9

Browse files
authored
Improved typing (#96)
* fix: improve typing using phpstan and fix some errors identified by phpstan * fix: revert some changes that broke tests
1 parent 2971f1f commit 219fbd9

File tree

9 files changed

+217
-230
lines changed

9 files changed

+217
-230
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ jobs:
2626

2727
- name: Install dependencies
2828
run: composer install --prefer-dist --no-progress --no-interaction --no-suggest
29+
- name: Run PHPStan
30+
run: php vendor/bin/phpstan
2931

3032
- name: Run test suite
3133
run: |

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727
"yiisoft/yii2-app-advanced": "dev-master",
2828
"codeception/verify": "^3.0",
2929
"codemix/yii2-localeurls": "^1.7",
30-
"codeception/module-asserts": "^3.0",
31-
"codeception/module-filesystem": "^3.0"
30+
"codeception/module-asserts": ">= 3.0",
31+
"codeception/module-filesystem": "> 3.0",
32+
"phpstan/phpstan": "^1.10"
3233
},
3334
"autoload":{
3435
"classmap": ["src/"]

phpstan.neon

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#includes:
2+
# - phpstan-baseline.neon
3+
parameters:
4+
reportUnmatchedIgnoredErrors: true
5+
dynamicConstantNames:
6+
- CONSOLE
7+
- YII_DEBUG
8+
level: 5
9+
paths:
10+
- src
11+
checkMaybeUndefinedVariables: true
12+
checkGenericClassInNonGenericObjectType: false
13+
ignoreErrors:
14+
# All Yii setters accept `null` but their phpdoc is incorrect.
15+
- message: '~^Parameter #1 \$(.+) of method yii\\web\\Request::set(.+)\(\) expects (.+), null given.$~'
16+
path: 'src/'
17+
- message: '~^Variable \$_COOKIE in isset\(\) always exists and is not nullable.$~'
18+
path: 'src/'
19+
stubFiles:
20+
- tests/Yii.stub

src/Codeception/Lib/Connector/Yii2.php

Lines changed: 50 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use Codeception\Util\Debug;
88
use Symfony\Component\BrowserKit\AbstractBrowser as Client;
99
use Symfony\Component\BrowserKit\Cookie;
10+
use Symfony\Component\BrowserKit\CookieJar;
11+
use Symfony\Component\BrowserKit\History;
1012
use Symfony\Component\BrowserKit\Response;
1113
use Yii;
1214
use yii\base\ExitException;
@@ -15,8 +17,10 @@
1517
use yii\mail\MessageInterface;
1618
use yii\web\Application;
1719
use yii\web\ErrorHandler;
20+
use yii\web\IdentityInterface;
1821
use yii\web\Request;
1922
use yii\web\Response as YiiResponse;
23+
use yii\web\User;
2024

2125
class Yii2 extends Client
2226
{
@@ -81,34 +85,30 @@ class Yii2 extends Client
8185
/**
8286
* @var bool whether to close the session in between requests inside a single test, if recreateApplication is set to true
8387
*/
84-
public $closeSessionOnRecreateApplication = true;
88+
public bool $closeSessionOnRecreateApplication = true;
8589

8690
/**
87-
* @var string The FQN of the application class to use. In a default Yii setup, should be either `yii\web\Application`
91+
* @var class-string The FQN of the application class to use. In a default Yii setup, should be either `yii\web\Application`
8892
* or `yii\console\Application`
8993
*/
90-
public $applicationClass = null;
94+
public string|null $applicationClass = null;
9195

9296

93-
private $emails = [];
97+
private array $emails = [];
9498

9599
/**
96-
* @return \yii\web\Application
97-
*
98100
* @deprecated since 2.5, will become protected in 3.0. Directly access to \Yii::$app if you need to interact with it.
101+
* @internal
99102
*/
100-
public function getApplication()
103+
public function getApplication(): \yii\base\Application
101104
{
102105
if (!isset(Yii::$app)) {
103106
$this->startApp();
104107
}
105108
return Yii::$app;
106109
}
107110

108-
/**
109-
* @param bool $closeSession
110-
*/
111-
public function resetApplication($closeSession = true)
111+
public function resetApplication(bool $closeSession = true): void
112112
{
113113
codecept_debug('Destroying application');
114114
if (true === $closeSession) {
@@ -127,40 +127,28 @@ public function resetApplication($closeSession = true)
127127
/**
128128
* Finds and logs in a user
129129
* @internal
130-
* @param $user
131130
* @throws ConfigurationException
132131
* @throws \RuntimeException
133132
*/
134-
public function findAndLoginUser($user)
133+
public function findAndLoginUser(int|string|IdentityInterface $user): void
135134
{
136135
$app = $this->getApplication();
137-
if (!$app->has('user')) {
136+
$user = $app->get('user');
137+
if (!$user instanceof User) {
138138
throw new ConfigurationException('The user component is not configured');
139139
}
140140

141141
if ($user instanceof \yii\web\IdentityInterface) {
142142
$identity = $user;
143143
} else {
144144
// class name implementing IdentityInterface
145-
$identityClass = $app->user->identityClass;
145+
$identityClass = $user->identityClass;
146146
$identity = call_user_func([$identityClass, 'findIdentity'], $user);
147147
if (!isset($identity)) {
148148
throw new \RuntimeException('User not found');
149149
}
150150
}
151-
$app->user->login($identity);
152-
}
153-
154-
/**
155-
* Masks a value
156-
* @internal
157-
* @param string $val
158-
* @return string
159-
* @see \yii\base\Security::maskToken
160-
*/
161-
public function maskToken($val)
162-
{
163-
return $this->getApplication()->security->maskToken($val);
151+
$user->login($identity);
164152
}
165153

166154
/**
@@ -169,7 +157,7 @@ public function maskToken($val)
169157
* @param string $value The value of the cookie
170158
* @return string The value to send to the browser
171159
*/
172-
public function hashCookieData($name, $value)
160+
public function hashCookieData($name, $value): string
173161
{
174162
$app = $this->getApplication();
175163
if (!$app->request->enableCookieValidation) {
@@ -182,7 +170,7 @@ public function hashCookieData($name, $value)
182170
* @internal
183171
* @return array List of regex patterns for recognized domain names
184172
*/
185-
public function getInternalDomains()
173+
public function getInternalDomains(): array
186174
{
187175
/** @var \yii\web\UrlManager $urlManager */
188176
$urlManager = $this->getApplication()->urlManager;
@@ -202,7 +190,7 @@ public function getInternalDomains()
202190
* @internal
203191
* @return array List of sent emails
204192
*/
205-
public function getEmails()
193+
public function getEmails(): array
206194
{
207195
return $this->emails;
208196
}
@@ -211,7 +199,7 @@ public function getEmails()
211199
* Deletes all stored emails.
212200
* @internal
213201
*/
214-
public function clearEmails()
202+
public function clearEmails(): void
215203
{
216204
$this->emails = [];
217205
}
@@ -230,11 +218,8 @@ public function getComponent($name)
230218

231219
/**
232220
* Getting domain regex from rule host template
233-
*
234-
* @param string $template
235-
* @return string
236221
*/
237-
private function getDomainRegex($template)
222+
private function getDomainRegex(string $template): string
238223
{
239224
if (preg_match('#https?://(.*)#', $template, $matches)) {
240225
$template = $matches[1];
@@ -259,24 +244,13 @@ function ($matches) use (&$parameters) {
259244
/**
260245
* Gets the name of the CSRF param.
261246
* @internal
262-
* @return string
263247
*/
264-
public function getCsrfParamName()
248+
public function getCsrfParamName(): string
265249
{
266250
return $this->getApplication()->request->csrfParam;
267251
}
268252

269-
/**
270-
* @internal
271-
* @param $params
272-
* @return mixed
273-
*/
274-
public function createUrl($params)
275-
{
276-
return is_array($params) ?$this->getApplication()->getUrlManager()->createUrl($params) : $params;
277-
}
278-
279-
public function startApp(\yii\log\Logger $logger = null)
253+
public function startApp(\yii\log\Logger $logger = null): void
280254
{
281255
codecept_debug('Starting application');
282256
$config = require($this->configFile);
@@ -306,12 +280,9 @@ public function startApp(\yii\log\Logger $logger = null)
306280
}
307281

308282
/**
309-
*
310283
* @param \Symfony\Component\BrowserKit\Request $request
311-
*
312-
* @return \Symfony\Component\BrowserKit\Response
313284
*/
314-
public function doRequest(object $request)
285+
public function doRequest(object $request): \Symfony\Component\BrowserKit\Response
315286
{
316287
$_COOKIE = $request->getCookies();
317288
$_SERVER = $request->getServer();
@@ -343,6 +314,9 @@ public function doRequest(object $request)
343314
$this->beforeRequest();
344315

345316
$app = $this->getApplication();
317+
if (!$app instanceof Application) {
318+
throw new ConfigurationException("Application is not a web application");
319+
}
346320

347321
// disabling logging. Logs are slowing test execution down
348322
foreach ($app->log->targets as $target) {
@@ -407,22 +381,22 @@ protected function revertErrorHandler()
407381

408382
/**
409383
* Encodes the cookies and adds them to the headers.
410-
* @param \yii\web\Response $response
411384
* @throws \yii\base\InvalidConfigException
412385
*/
413386
protected function encodeCookies(
414387
YiiResponse $response,
415388
Request $request,
416389
Security $security
417-
) {
390+
): void {
418391
if ($request->enableCookieValidation) {
419392
$validationKey = $request->cookieValidationKey;
420393
}
421394

422395
foreach ($response->getCookies() as $cookie) {
423396
/** @var \yii\web\Cookie $cookie */
424397
$value = $cookie->value;
425-
if ($cookie->expire != 1 && isset($validationKey)) {
398+
// Expire = 1 means we're removing the cookie
399+
if ($cookie->expire !== 1 && isset($validationKey)) {
426400
$data = version_compare(Yii::getVersion(), '2.0.2', '>')
427401
? [$cookie->name, $cookie->value]
428402
: $cookie->value;
@@ -443,10 +417,10 @@ protected function encodeCookies(
443417

444418
/**
445419
* Replace mailer with in memory mailer
446-
* @param array $config Original configuration
447-
* @return array New configuration
420+
* @param array<string, mixed> $config Original configuration
421+
* @return array<string, mixed> New configuration
448422
*/
449-
protected function mockMailer(array $config)
423+
protected function mockMailer(array $config): array
450424
{
451425
// options that make sense for mailer mock
452426
$allowedOptions = [
@@ -489,30 +463,23 @@ public function restart(): void
489463
/**
490464
* Return an assoc array with the client context: cookieJar, history.
491465
*
492-
* @return array
466+
* @internal
467+
* @return array{ cookieJar: CookieJar, history: History }
493468
*/
494-
public function getContext()
469+
public function getContext(): array
495470
{
496471
return [
497472
'cookieJar' => $this->cookieJar,
498473
'history' => $this->history,
499474
];
500475
}
501476

502-
/**
503-
* Reset the client context: empty cookieJar and history.
504-
*/
505-
public function removeContext()
506-
{
507-
parent::restart();
508-
}
509-
510477
/**
511478
* Set the context, see getContext().
512479
*
513-
* @param array $context
480+
* @param array{ cookieJar: CookieJar, history: History } $context
514481
*/
515-
public function setContext(array $context)
482+
public function setContext(array $context): void
516483
{
517484
$this->cookieJar = $context['cookieJar'];
518485
$this->history = $context['history'];
@@ -522,18 +489,19 @@ public function setContext(array $context)
522489
* This functions closes the session of the application, if the application exists and has a session.
523490
* @internal
524491
*/
525-
public function closeSession()
492+
public function closeSession(): void
526493
{
527-
if (isset(\Yii::$app) && \Yii::$app->has('session', true)) {
528-
\Yii::$app->session->close();
494+
$app = \Yii::$app;
495+
if ($app instanceof \yii\web\Application && $app->has('session', true)) {
496+
$app->session->close();
529497
}
530498
}
531499

532500
/**
533501
* Resets the applications' response object.
534502
* The method used depends on the module configuration.
535503
*/
536-
protected function resetResponse(Application $app)
504+
protected function resetResponse(Application $app): void
537505
{
538506
$method = $this->responseCleanMethod;
539507
// First check the current response object.
@@ -566,7 +534,7 @@ protected function resetResponse(Application $app)
566534
}
567535
}
568536

569-
protected function resetRequest(Application $app)
537+
protected function resetRequest(Application $app): void
570538
{
571539
$method = $this->requestCleanMethod;
572540
$request = $app->request;
@@ -596,8 +564,8 @@ protected function resetRequest(Application $app)
596564
$request->setScriptFile(null);
597565
$request->setScriptUrl(null);
598566
$request->setUrl(null);
599-
$request->setPort(null);
600-
$request->setSecurePort(null);
567+
$request->setPort(0);
568+
$request->setSecurePort(0);
601569
$request->setAcceptableContentTypes(null);
602570
$request->setAcceptableLanguages(null);
603571

@@ -610,7 +578,7 @@ protected function resetRequest(Application $app)
610578
/**
611579
* Called before each request, preparation happens here.
612580
*/
613-
protected function beforeRequest()
581+
protected function beforeRequest(): void
614582
{
615583
if ($this->recreateApplication) {
616584
$this->resetApplication($this->closeSessionOnRecreateApplication);
@@ -619,6 +587,9 @@ protected function beforeRequest()
619587

620588
$application = $this->getApplication();
621589

590+
if (!$application instanceof Application) {
591+
throw new ConfigurationException('Application must be an instance of web application when doing requests');
592+
}
622593
$this->resetResponse($application);
623594
$this->resetRequest($application);
624595

0 commit comments

Comments
 (0)