first commit
This commit is contained in:
21
vendor/codeception/module-yii2/LICENSE
vendored
Normal file
21
vendor/codeception/module-yii2/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011 Michael Bodnarchuk and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
55
vendor/codeception/module-yii2/composer.json
vendored
Normal file
55
vendor/codeception/module-yii2/composer.json
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"name":"codeception/module-yii2",
|
||||
"description":"Codeception module for Yii2 framework",
|
||||
"keywords":["codeception", "yii2"],
|
||||
"homepage":"https://codeception.com/",
|
||||
"type":"library",
|
||||
"license":"MIT",
|
||||
"authors":[
|
||||
{
|
||||
"name":"Alexander Makarov"
|
||||
},
|
||||
{
|
||||
"name":"Sam Mouse"
|
||||
},
|
||||
{
|
||||
"name":"Michael Bodnarchuk"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "RC",
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"codeception/codeception": "^5.0.8",
|
||||
"codeception/lib-innerbrowser": "^3.0 | ^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"yiisoft/yii2": "dev-master",
|
||||
"yiisoft/yii2-app-advanced": "dev-master",
|
||||
"codeception/verify": "^3.0",
|
||||
"codemix/yii2-localeurls": "^1.7",
|
||||
"codeception/module-asserts": ">= 3.0",
|
||||
"codeception/module-filesystem": "> 3.0",
|
||||
"phpstan/phpstan": "^1.10"
|
||||
},
|
||||
"autoload":{
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"classmap": [
|
||||
"vendor/yiisoft/yii2/Yii.php",
|
||||
"tests/cases"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"yiisoft/yii2-composer": true
|
||||
},
|
||||
"classmap-authoritative": true
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://asset-packagist.org"
|
||||
}
|
||||
]
|
||||
}
|
||||
20
vendor/codeception/module-yii2/phpstan.neon
vendored
Normal file
20
vendor/codeception/module-yii2/phpstan.neon
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
#includes:
|
||||
# - phpstan-baseline.neon
|
||||
parameters:
|
||||
reportUnmatchedIgnoredErrors: true
|
||||
dynamicConstantNames:
|
||||
- CONSOLE
|
||||
- YII_DEBUG
|
||||
level: 5
|
||||
paths:
|
||||
- src
|
||||
checkMaybeUndefinedVariables: true
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
ignoreErrors:
|
||||
# All Yii setters accept `null` but their phpdoc is incorrect.
|
||||
- message: '~^Parameter #1 \$(.+) of method yii\\web\\Request::set(.+)\(\) expects (.+), null given.$~'
|
||||
path: 'src/'
|
||||
- message: '~^Variable \$_COOKIE in isset\(\) always exists and is not nullable.$~'
|
||||
path: 'src/'
|
||||
stubFiles:
|
||||
- tests/Yii.stub
|
||||
604
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2.php
vendored
Normal file
604
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2.php
vendored
Normal file
@ -0,0 +1,604 @@
|
||||
<?php
|
||||
namespace Codeception\Lib\Connector;
|
||||
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Codeception\Lib\Connector\Yii2\Logger;
|
||||
use Codeception\Lib\Connector\Yii2\TestMailer;
|
||||
use Codeception\Util\Debug;
|
||||
use Symfony\Component\BrowserKit\AbstractBrowser as Client;
|
||||
use Symfony\Component\BrowserKit\Cookie;
|
||||
use Symfony\Component\BrowserKit\CookieJar;
|
||||
use Symfony\Component\BrowserKit\History;
|
||||
use Symfony\Component\BrowserKit\Response;
|
||||
use Yii;
|
||||
use yii\base\ExitException;
|
||||
use yii\base\Security;
|
||||
use yii\base\UserException;
|
||||
use yii\mail\MessageInterface;
|
||||
use yii\web\Application;
|
||||
use yii\web\ErrorHandler;
|
||||
use yii\web\IdentityInterface;
|
||||
use yii\web\Request;
|
||||
use yii\web\Response as YiiResponse;
|
||||
use yii\web\User;
|
||||
|
||||
class Yii2 extends Client
|
||||
{
|
||||
use Shared\PhpSuperGlobalsConverter;
|
||||
|
||||
const CLEAN_METHODS = [
|
||||
self::CLEAN_RECREATE,
|
||||
self::CLEAN_CLEAR,
|
||||
self::CLEAN_FORCE_RECREATE,
|
||||
self::CLEAN_MANUAL
|
||||
];
|
||||
/**
|
||||
* Clean the response object by recreating it.
|
||||
* This might lose behaviors / event handlers / other changes that are done in the application bootstrap phase.
|
||||
*/
|
||||
const CLEAN_RECREATE = 'recreate';
|
||||
/**
|
||||
* Same as recreate but will not warn when behaviors / event handlers are lost.
|
||||
*/
|
||||
const CLEAN_FORCE_RECREATE = 'force_recreate';
|
||||
/**
|
||||
* Clean the response object by resetting specific properties via its' `clear()` method.
|
||||
* This will keep behaviors / event handlers, but could inadvertently leave some changes intact.
|
||||
* @see \yii\web\Response::clear()
|
||||
*/
|
||||
const CLEAN_CLEAR = 'clear';
|
||||
|
||||
/**
|
||||
* Do not clean the response, instead the test writer will be responsible for manually resetting the response in
|
||||
* between requests during one test
|
||||
*/
|
||||
const CLEAN_MANUAL = 'manual';
|
||||
|
||||
|
||||
/**
|
||||
* @var string application config file
|
||||
*/
|
||||
public $configFile;
|
||||
|
||||
/**
|
||||
* @var string method for cleaning the response object before each request
|
||||
*/
|
||||
public $responseCleanMethod;
|
||||
|
||||
/**
|
||||
* @var string method for cleaning the request object before each request
|
||||
*/
|
||||
public $requestCleanMethod;
|
||||
|
||||
/**
|
||||
* @var string[] List of component names that must be recreated before each request
|
||||
*/
|
||||
public $recreateComponents = [];
|
||||
|
||||
/**
|
||||
* This option is there primarily for backwards compatibility.
|
||||
* It means you cannot make any modification to application state inside your app, since they will get discarded.
|
||||
* @var bool whether to recreate the whole application before each request
|
||||
*/
|
||||
public $recreateApplication = false;
|
||||
|
||||
/**
|
||||
* @var bool whether to close the session in between requests inside a single test, if recreateApplication is set to true
|
||||
*/
|
||||
public bool $closeSessionOnRecreateApplication = true;
|
||||
|
||||
/**
|
||||
* @var class-string The FQN of the application class to use. In a default Yii setup, should be either `yii\web\Application`
|
||||
* or `yii\console\Application`
|
||||
*/
|
||||
public string|null $applicationClass = null;
|
||||
|
||||
|
||||
private array $emails = [];
|
||||
|
||||
/**
|
||||
* @deprecated since 2.5, will become protected in 3.0. Directly access to \Yii::$app if you need to interact with it.
|
||||
* @internal
|
||||
*/
|
||||
public function getApplication(): \yii\base\Application
|
||||
{
|
||||
if (!isset(Yii::$app)) {
|
||||
$this->startApp();
|
||||
}
|
||||
return Yii::$app;
|
||||
}
|
||||
|
||||
public function resetApplication(bool $closeSession = true): void
|
||||
{
|
||||
codecept_debug('Destroying application');
|
||||
if (true === $closeSession) {
|
||||
$this->closeSession();
|
||||
}
|
||||
Yii::$app = null;
|
||||
\yii\web\UploadedFile::reset();
|
||||
if (method_exists(\yii\base\Event::class, 'offAll')) {
|
||||
\yii\base\Event::offAll();
|
||||
}
|
||||
Yii::setLogger(null);
|
||||
// This resolves an issue with database connections not closing properly.
|
||||
gc_collect_cycles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and logs in a user
|
||||
* @internal
|
||||
* @throws ConfigurationException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function findAndLoginUser(int|string|IdentityInterface $user): void
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
$userComponent = $app->get('user');
|
||||
if (!$userComponent instanceof User) {
|
||||
throw new ConfigurationException('The user component is not configured');
|
||||
}
|
||||
|
||||
if ($user instanceof \yii\web\IdentityInterface) {
|
||||
$identity = $user;
|
||||
} else {
|
||||
// class name implementing IdentityInterface
|
||||
$identityClass = $userComponent->identityClass;
|
||||
$identity = call_user_func([$identityClass, 'findIdentity'], $user);
|
||||
if (!isset($identity)) {
|
||||
throw new \RuntimeException('User not found');
|
||||
}
|
||||
}
|
||||
$userComponent->login($identity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param string $name The name of the cookie
|
||||
* @param string $value The value of the cookie
|
||||
* @return string The value to send to the browser
|
||||
*/
|
||||
public function hashCookieData($name, $value): string
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
if (!$app->request->enableCookieValidation) {
|
||||
return $value;
|
||||
}
|
||||
return $app->security->hashData(serialize([$name, $value]), $app->request->cookieValidationKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return array List of regex patterns for recognized domain names
|
||||
*/
|
||||
public function getInternalDomains(): array
|
||||
{
|
||||
/** @var \yii\web\UrlManager $urlManager */
|
||||
$urlManager = $this->getApplication()->urlManager;
|
||||
$domains = [$this->getDomainRegex($urlManager->hostInfo)];
|
||||
if ($urlManager->enablePrettyUrl) {
|
||||
foreach ($urlManager->rules as $rule) {
|
||||
/** @var \yii\web\UrlRule $rule */
|
||||
if (isset($rule->host)) {
|
||||
$domains[] = $this->getDomainRegex($rule->host);
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_unique($domains);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return array List of sent emails
|
||||
*/
|
||||
public function getEmails(): array
|
||||
{
|
||||
return $this->emails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all stored emails.
|
||||
* @internal
|
||||
*/
|
||||
public function clearEmails(): void
|
||||
{
|
||||
$this->emails = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getComponent($name)
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
if (!$app->has($name)) {
|
||||
throw new ConfigurationException("Component $name is not available in current application");
|
||||
}
|
||||
return $app->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting domain regex from rule host template
|
||||
*/
|
||||
private function getDomainRegex(string $template): string
|
||||
{
|
||||
if (preg_match('#https?://(.*)#', $template, $matches)) {
|
||||
$template = $matches[1];
|
||||
}
|
||||
$parameters = [];
|
||||
if (strpos($template, '<') !== false) {
|
||||
$template = preg_replace_callback(
|
||||
'/<(?:\w+):?([^>]+)?>/u',
|
||||
function ($matches) use (&$parameters) {
|
||||
$key = '__' . count($parameters) . '__';
|
||||
$parameters[$key] = isset($matches[1]) ? $matches[1] : '\w+';
|
||||
return $key;
|
||||
},
|
||||
$template
|
||||
);
|
||||
}
|
||||
$template = preg_quote($template);
|
||||
$template = strtr($template, $parameters);
|
||||
return '/^' . $template . '$/u';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the CSRF param.
|
||||
* @internal
|
||||
*/
|
||||
public function getCsrfParamName(): string
|
||||
{
|
||||
return $this->getApplication()->request->csrfParam;
|
||||
}
|
||||
|
||||
public function startApp(?\yii\log\Logger $logger = null): void
|
||||
{
|
||||
codecept_debug('Starting application');
|
||||
$config = require($this->configFile);
|
||||
if (!isset($config['class'])) {
|
||||
if (null !== $this->applicationClass) {
|
||||
$config['class'] = $this->applicationClass;
|
||||
} else {
|
||||
$config['class'] = 'yii\web\Application';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['container']))
|
||||
{
|
||||
Yii::configure(Yii::$container, $config['container']);
|
||||
unset($config['container']);
|
||||
}
|
||||
|
||||
$config = $this->mockMailer($config);
|
||||
/** @var \yii\base\Application $app */
|
||||
Yii::$app = Yii::createObject($config);
|
||||
|
||||
if ($logger !== null) {
|
||||
Yii::setLogger($logger);
|
||||
} else {
|
||||
Yii::setLogger(new Logger());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\BrowserKit\Request $request
|
||||
*/
|
||||
public function doRequest(object $request): \Symfony\Component\BrowserKit\Response
|
||||
{
|
||||
$_COOKIE = $request->getCookies();
|
||||
$_SERVER = $request->getServer();
|
||||
$_FILES = $this->remapFiles($request->getFiles());
|
||||
$_REQUEST = $this->remapRequestParameters($request->getParameters());
|
||||
$_POST = $_GET = [];
|
||||
|
||||
if (strtoupper($request->getMethod()) === 'GET') {
|
||||
$_GET = $_REQUEST;
|
||||
} else {
|
||||
$_POST = $_REQUEST;
|
||||
}
|
||||
|
||||
$uri = $request->getUri();
|
||||
|
||||
$pathString = parse_url($uri, PHP_URL_PATH);
|
||||
$queryString = parse_url($uri, PHP_URL_QUERY);
|
||||
$_SERVER['REQUEST_URI'] = $queryString === null ? $pathString : $pathString . '?' . $queryString;
|
||||
$_SERVER['REQUEST_METHOD'] = strtoupper($request->getMethod());
|
||||
$_SERVER['QUERY_STRING'] = (string)$queryString;
|
||||
|
||||
parse_str($queryString ?: '', $params);
|
||||
foreach ($params as $k => $v) {
|
||||
$_GET[$k] = $v;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
$this->beforeRequest();
|
||||
|
||||
$app = $this->getApplication();
|
||||
if (!$app instanceof Application) {
|
||||
throw new ConfigurationException("Application is not a web application");
|
||||
}
|
||||
|
||||
// disabling logging. Logs are slowing test execution down
|
||||
foreach ($app->log->targets as $target) {
|
||||
$target->enabled = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
$yiiRequest = $app->getRequest();
|
||||
if ($request->getContent() !== null) {
|
||||
$yiiRequest->setRawBody($request->getContent());
|
||||
$yiiRequest->setBodyParams(null);
|
||||
} else {
|
||||
$yiiRequest->setRawBody(null);
|
||||
$yiiRequest->setBodyParams($_POST);
|
||||
}
|
||||
$yiiRequest->setQueryParams($_GET);
|
||||
|
||||
try {
|
||||
/*
|
||||
* This is basically equivalent to $app->run() without sending the response.
|
||||
* Sending the response is problematic because it tries to send headers.
|
||||
*/
|
||||
$app->trigger($app::EVENT_BEFORE_REQUEST);
|
||||
$response = $app->handleRequest($yiiRequest);
|
||||
$app->trigger($app::EVENT_AFTER_REQUEST);
|
||||
$response->send();
|
||||
} catch (\Exception $e) {
|
||||
if ($e instanceof UserException) {
|
||||
// Don't discard output and pass exception handling to Yii to be able
|
||||
// to expect error response codes in tests.
|
||||
$app->errorHandler->discardExistingOutput = false;
|
||||
$app->errorHandler->handleException($e);
|
||||
} elseif (!$e instanceof ExitException) {
|
||||
// for exceptions not related to Http, we pass them to Codeception
|
||||
throw $e;
|
||||
}
|
||||
$response = $app->response;
|
||||
}
|
||||
|
||||
$this->encodeCookies($response, $yiiRequest, $app->security);
|
||||
|
||||
if ($response->isRedirection) {
|
||||
Debug::debug("[Redirect with headers]" . print_r($response->getHeaders()->toArray(), true));
|
||||
}
|
||||
|
||||
$content = ob_get_clean();
|
||||
if (empty($content) && !empty($response->content) && !isset($response->stream)) {
|
||||
throw new \Exception('No content was sent from Yii application');
|
||||
}
|
||||
|
||||
return new Response($content, $response->statusCode, $response->getHeaders()->toArray());
|
||||
}
|
||||
|
||||
protected function revertErrorHandler()
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
set_error_handler([$handler, 'errorHandler']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes the cookies and adds them to the headers.
|
||||
* @throws \yii\base\InvalidConfigException
|
||||
*/
|
||||
protected function encodeCookies(
|
||||
YiiResponse $response,
|
||||
Request $request,
|
||||
Security $security
|
||||
): void {
|
||||
if ($request->enableCookieValidation) {
|
||||
$validationKey = $request->cookieValidationKey;
|
||||
}
|
||||
|
||||
foreach ($response->getCookies() as $cookie) {
|
||||
/** @var \yii\web\Cookie $cookie */
|
||||
$value = $cookie->value;
|
||||
// Expire = 1 means we're removing the cookie
|
||||
if ($cookie->expire !== 1 && isset($validationKey)) {
|
||||
$data = version_compare(Yii::getVersion(), '2.0.2', '>')
|
||||
? [$cookie->name, $cookie->value]
|
||||
: $cookie->value;
|
||||
$value = $security->hashData(serialize($data), $validationKey);
|
||||
}
|
||||
$c = new Cookie(
|
||||
$cookie->name,
|
||||
$value,
|
||||
$cookie->expire,
|
||||
$cookie->path,
|
||||
$cookie->domain,
|
||||
$cookie->secure,
|
||||
$cookie->httpOnly
|
||||
);
|
||||
$this->getCookieJar()->set($c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace mailer with in memory mailer
|
||||
* @param array<string, mixed> $config Original configuration
|
||||
* @return array<string, mixed> New configuration
|
||||
*/
|
||||
protected function mockMailer(array $config): array
|
||||
{
|
||||
// options that make sense for mailer mock
|
||||
$allowedOptions = [
|
||||
'htmlLayout',
|
||||
'textLayout',
|
||||
'messageConfig',
|
||||
'messageClass',
|
||||
'useFileTransport',
|
||||
'fileTransportPath',
|
||||
'fileTransportCallback',
|
||||
'view',
|
||||
'viewPath',
|
||||
];
|
||||
|
||||
$mailerConfig = [
|
||||
'class' => TestMailer::class,
|
||||
'callback' => function (MessageInterface $message) {
|
||||
$this->emails[] = $message;
|
||||
}
|
||||
];
|
||||
|
||||
if (isset($config['components']['mailer']) && is_array($config['components']['mailer'])) {
|
||||
foreach ($config['components']['mailer'] as $name => $value) {
|
||||
if (in_array($name, $allowedOptions, true)) {
|
||||
$mailerConfig[$name] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
$config['components']['mailer'] = $mailerConfig;
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function restart(): void
|
||||
{
|
||||
parent::restart();
|
||||
$this->resetApplication();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an assoc array with the client context: cookieJar, history.
|
||||
*
|
||||
* @internal
|
||||
* @return array{ cookieJar: CookieJar, history: History }
|
||||
*/
|
||||
public function getContext(): array
|
||||
{
|
||||
return [
|
||||
'cookieJar' => $this->cookieJar,
|
||||
'history' => $this->history,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the context, see getContext().
|
||||
*
|
||||
* @param array{ cookieJar: CookieJar, history: History } $context
|
||||
*/
|
||||
public function setContext(array $context): void
|
||||
{
|
||||
$this->cookieJar = $context['cookieJar'];
|
||||
$this->history = $context['history'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions closes the session of the application, if the application exists and has a session.
|
||||
* @internal
|
||||
*/
|
||||
public function closeSession(): void
|
||||
{
|
||||
$app = \Yii::$app;
|
||||
if ($app instanceof \yii\web\Application && $app->has('session', true)) {
|
||||
$app->session->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the applications' response object.
|
||||
* The method used depends on the module configuration.
|
||||
*/
|
||||
protected function resetResponse(Application $app): void
|
||||
{
|
||||
$method = $this->responseCleanMethod;
|
||||
// First check the current response object.
|
||||
if (($app->response->hasEventHandlers(\yii\web\Response::EVENT_BEFORE_SEND)
|
||||
|| $app->response->hasEventHandlers(\yii\web\Response::EVENT_AFTER_SEND)
|
||||
|| $app->response->hasEventHandlers(\yii\web\Response::EVENT_AFTER_PREPARE)
|
||||
|| count($app->response->getBehaviors()) > 0
|
||||
) && $method === self::CLEAN_RECREATE
|
||||
) {
|
||||
Debug::debug(<<<TEXT
|
||||
[WARNING] You are attaching event handlers or behaviors to the response object. But the Yii2 module is configured to recreate
|
||||
the response object, this means any behaviors or events that are not attached in the component config will be lost.
|
||||
We will fall back to clearing the response. If you are certain you want to recreate it, please configure
|
||||
responseCleanMethod = 'force_recreate' in the module.
|
||||
TEXT
|
||||
);
|
||||
$method = self::CLEAN_CLEAR;
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case self::CLEAN_FORCE_RECREATE:
|
||||
case self::CLEAN_RECREATE:
|
||||
$app->set('response', $app->getComponents()['response']);
|
||||
break;
|
||||
case self::CLEAN_CLEAR:
|
||||
$app->response->clear();
|
||||
break;
|
||||
case self::CLEAN_MANUAL:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function resetRequest(Application $app): void
|
||||
{
|
||||
$method = $this->requestCleanMethod;
|
||||
$request = $app->request;
|
||||
|
||||
// First check the current request object.
|
||||
if (count($request->getBehaviors()) > 0 && $method === self::CLEAN_RECREATE) {
|
||||
Debug::debug(<<<TEXT
|
||||
[WARNING] You are attaching event handlers or behaviors to the request object. But the Yii2 module is configured to recreate
|
||||
the request object, this means any behaviors or events that are not attached in the component config will be lost.
|
||||
We will fall back to clearing the request. If you are certain you want to recreate it, please configure
|
||||
requestCleanMethod = 'force_recreate' in the module.
|
||||
TEXT
|
||||
);
|
||||
$method = self::CLEAN_CLEAR;
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case self::CLEAN_FORCE_RECREATE:
|
||||
case self::CLEAN_RECREATE:
|
||||
$app->set('request', $app->getComponents()['request']);
|
||||
break;
|
||||
case self::CLEAN_CLEAR:
|
||||
$request->getHeaders()->removeAll();
|
||||
$request->setBaseUrl(null);
|
||||
$request->setHostInfo(null);
|
||||
$request->setPathInfo(null);
|
||||
$request->setScriptFile(null);
|
||||
$request->setScriptUrl(null);
|
||||
$request->setUrl(null);
|
||||
$request->setPort(0);
|
||||
$request->setSecurePort(0);
|
||||
$request->setAcceptableContentTypes(null);
|
||||
$request->setAcceptableLanguages(null);
|
||||
|
||||
break;
|
||||
case self::CLEAN_MANUAL:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before each request, preparation happens here.
|
||||
*/
|
||||
protected function beforeRequest(): void
|
||||
{
|
||||
if ($this->recreateApplication) {
|
||||
$this->resetApplication($this->closeSessionOnRecreateApplication);
|
||||
return;
|
||||
}
|
||||
|
||||
$application = $this->getApplication();
|
||||
|
||||
if (!$application instanceof Application) {
|
||||
throw new ConfigurationException('Application must be an instance of web application when doing requests');
|
||||
}
|
||||
$this->resetResponse($application);
|
||||
$this->resetRequest($application);
|
||||
|
||||
$definitions = $application->getComponents(true);
|
||||
foreach ($this->recreateComponents as $component) {
|
||||
// Only recreate if it has actually been instantiated.
|
||||
if ($application->has($component, true)) {
|
||||
$application->set($component, $definitions[$component]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/ConnectionWatcher.php
vendored
Normal file
65
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/ConnectionWatcher.php
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Codeception\Lib\Connector\Yii2;
|
||||
|
||||
use yii\base\Event;
|
||||
use yii\db\Connection;
|
||||
|
||||
/**
|
||||
* Class ConnectionWatcher
|
||||
* This class will watch for new database connection and store a reference to the connection object.
|
||||
* @package Codeception\Lib\Connector\Yii2
|
||||
*/
|
||||
class ConnectionWatcher
|
||||
{
|
||||
private \Closure $handler;
|
||||
|
||||
/** @var Connection[] */
|
||||
private array $connections = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->handler = function (Event $event) {
|
||||
if ($event->sender instanceof Connection) {
|
||||
$this->connectionOpened($event->sender);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected function connectionOpened(Connection $connection): void
|
||||
{
|
||||
$this->debug('Connection opened!');
|
||||
$this->connections[] = $connection;
|
||||
}
|
||||
|
||||
public function start(): void
|
||||
{
|
||||
Event::on(Connection::class, Connection::EVENT_AFTER_OPEN, $this->handler);
|
||||
$this->debug('watching new connections');
|
||||
}
|
||||
|
||||
public function stop(): void
|
||||
{
|
||||
Event::off(Connection::class, Connection::EVENT_AFTER_OPEN, $this->handler);
|
||||
$this->debug('no longer watching new connections');
|
||||
}
|
||||
|
||||
public function closeAll(): void
|
||||
{
|
||||
$count = count($this->connections);
|
||||
$this->debug("closing all ($count) connections");
|
||||
foreach ($this->connections as $connection) {
|
||||
$connection->close();
|
||||
}
|
||||
}
|
||||
|
||||
protected function debug($message): void
|
||||
{
|
||||
$title = (new \ReflectionClass($this))->getShortName();
|
||||
if (is_array($message) or is_object($message)) {
|
||||
$message = stripslashes(json_encode($message));
|
||||
}
|
||||
codecept_debug("[$title] $message");
|
||||
}
|
||||
}
|
||||
37
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/FixturesStore.php
vendored
Normal file
37
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/FixturesStore.php
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace Codeception\Lib\Connector\Yii2;
|
||||
|
||||
use yii\test\FixtureTrait;
|
||||
use yii\test\InitDbFixture;
|
||||
|
||||
class FixturesStore
|
||||
{
|
||||
use FixtureTrait;
|
||||
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Expects fixtures config
|
||||
*
|
||||
* FixturesStore constructor.
|
||||
* @param $data
|
||||
*/
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function fixtures()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function globalFixtures()
|
||||
{
|
||||
return [
|
||||
'initDbFixture' => [
|
||||
'class' => InitDbFixture::class,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
66
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/Logger.php
vendored
Normal file
66
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/Logger.php
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
namespace Codeception\Lib\Connector\Yii2;
|
||||
|
||||
use Codeception\Util\Debug;
|
||||
use yii\helpers\VarDumper;
|
||||
|
||||
class Logger extends \yii\log\Logger
|
||||
{
|
||||
private \SplQueue $logQueue;
|
||||
|
||||
public function __construct(private int $maxLogItems = 5, $config = [])
|
||||
{
|
||||
parent::__construct($config);
|
||||
$this->logQueue = new \SplQueue();
|
||||
}
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
// overridden to prevent register_shutdown_function
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array<mixed>|\yii\base\Exception $message
|
||||
* @param $level
|
||||
* @param $category
|
||||
* @return void
|
||||
*/
|
||||
public function log($message, $level, $category = 'application'): void
|
||||
{
|
||||
if (!in_array($level, [
|
||||
\yii\log\Logger::LEVEL_INFO,
|
||||
\yii\log\Logger::LEVEL_WARNING,
|
||||
\yii\log\Logger::LEVEL_ERROR,
|
||||
])) {
|
||||
return;
|
||||
}
|
||||
if (str_starts_with($category, 'yii\db\Command')) {
|
||||
return; // don't log queries
|
||||
}
|
||||
|
||||
// https://github.com/Codeception/Codeception/issues/3696
|
||||
if ($message instanceof \yii\base\Exception) {
|
||||
$message = $message->__toString();
|
||||
}
|
||||
|
||||
$logMessage = "[$category] " . VarDumper::export($message);
|
||||
|
||||
Debug::debug($logMessage);
|
||||
|
||||
$this->logQueue->enqueue($logMessage);
|
||||
if ($this->logQueue->count() > $this->maxLogItems) {
|
||||
$this->logQueue->dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public function getAndClearLog(): string
|
||||
{
|
||||
$completeStr = '';
|
||||
foreach ($this->logQueue as $item) {
|
||||
$completeStr .= $item . PHP_EOL;
|
||||
}
|
||||
$this->logQueue = new \SplQueue();
|
||||
|
||||
return $completeStr;
|
||||
}
|
||||
}
|
||||
26
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/TestMailer.php
vendored
Normal file
26
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/TestMailer.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace Codeception\Lib\Connector\Yii2;
|
||||
|
||||
use yii\mail\BaseMailer;
|
||||
|
||||
class TestMailer extends BaseMailer
|
||||
{
|
||||
public $messageClass = \yii\symfonymailer\Message::class;
|
||||
|
||||
/**
|
||||
* @var \Closure
|
||||
*/
|
||||
public $callback;
|
||||
|
||||
protected function sendMessage($message)
|
||||
{
|
||||
call_user_func($this->callback, $message);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function saveMessage($message)
|
||||
{
|
||||
call_user_func($this->callback, $message);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
98
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/TransactionForcer.php
vendored
Normal file
98
vendor/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/TransactionForcer.php
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Codeception\Lib\Connector\Yii2;
|
||||
|
||||
use Codeception\Util\Debug;
|
||||
use yii\base\Event;
|
||||
use yii\db\Connection;
|
||||
use yii\db\Transaction;
|
||||
|
||||
/**
|
||||
* Class TransactionForcer
|
||||
* This class adds support for forcing transactions as well as reusing PDO objects.
|
||||
* @package Codeception\Lib\Connector\Yii2
|
||||
*/
|
||||
class TransactionForcer extends ConnectionWatcher
|
||||
{
|
||||
private $ignoreCollidingDSN;
|
||||
|
||||
private $pdoCache = [];
|
||||
|
||||
private $dsnCache;
|
||||
|
||||
private $transactions = [];
|
||||
|
||||
public function __construct(
|
||||
$ignoreCollidingDSN
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->ignoreCollidingDSN = $ignoreCollidingDSN;
|
||||
}
|
||||
|
||||
|
||||
protected function connectionOpened(Connection $connection): void
|
||||
{
|
||||
parent::connectionOpened($connection);
|
||||
/**
|
||||
* We should check if the known PDO objects are the same, in which case we should reuse the PDO
|
||||
* object so only 1 transaction is started and multiple connections to the same database see the
|
||||
* same data (due to writes inside a transaction not being visible from the outside).
|
||||
*
|
||||
*/
|
||||
$key = md5(json_encode([
|
||||
'dsn' => $connection->dsn,
|
||||
'user' => $connection->username,
|
||||
'pass' => $connection->password,
|
||||
'attributes' => $connection->attributes,
|
||||
'emulatePrepare' => $connection->emulatePrepare,
|
||||
'charset' => $connection->charset
|
||||
]));
|
||||
|
||||
/*
|
||||
* If keys match we assume connections are "similar enough".
|
||||
*/
|
||||
if (isset($this->pdoCache[$key])) {
|
||||
$connection->pdo = $this->pdoCache[$key];
|
||||
} else {
|
||||
$this->pdoCache[$key] = $connection->pdo;
|
||||
}
|
||||
|
||||
if (isset($this->dsnCache[$connection->dsn])
|
||||
&& $this->dsnCache[$connection->dsn] !== $key
|
||||
&& !$this->ignoreCollidingDSN
|
||||
) {
|
||||
$this->debug(<<<TEXT
|
||||
You use multiple connections to the same DSN ({$connection->dsn}) with different configuration.
|
||||
These connections will not see the same database state since we cannot share a transaction between different PDO
|
||||
instances.
|
||||
You can remove this message by adding 'ignoreCollidingDSN = true' in the module configuration.
|
||||
TEXT
|
||||
);
|
||||
Debug::pause();
|
||||
}
|
||||
|
||||
if (isset($this->transactions[$key])) {
|
||||
$this->debug('Reusing PDO, so no need for a new transaction');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->debug('Transaction started for: ' . $connection->dsn);
|
||||
$this->transactions[$key] = $connection->beginTransaction();
|
||||
}
|
||||
|
||||
public function rollbackAll(): void
|
||||
{
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($this->transactions as $transaction) {
|
||||
if ($transaction->db->isActive) {
|
||||
$transaction->rollBack();
|
||||
$this->debug('Transaction cancelled; all changes reverted.');
|
||||
}
|
||||
}
|
||||
|
||||
$this->transactions = [];
|
||||
$this->pdoCache = [];
|
||||
$this->dsnCache = [];
|
||||
}
|
||||
}
|
||||
901
vendor/codeception/module-yii2/src/Codeception/Module/Yii2.php
vendored
Normal file
901
vendor/codeception/module-yii2/src/Codeception/Module/Yii2.php
vendored
Normal file
@ -0,0 +1,901 @@
|
||||
<?php
|
||||
namespace Codeception\Module;
|
||||
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Codeception\Exception\ModuleConfigException;
|
||||
use Codeception\Exception\ModuleException;
|
||||
use Codeception\Lib\Connector\Yii2 as Yii2Connector;
|
||||
use Codeception\Lib\Connector\Yii2\ConnectionWatcher;
|
||||
use Codeception\Lib\Connector\Yii2\Logger;
|
||||
use Codeception\Lib\Connector\Yii2\TransactionForcer;
|
||||
use Codeception\Lib\Framework;
|
||||
use Codeception\Lib\Interfaces\ActiveRecord;
|
||||
use Codeception\Lib\Interfaces\MultiSession;
|
||||
use Codeception\Lib\Interfaces\PartedModule;
|
||||
use Codeception\TestInterface;
|
||||
use Symfony\Component\DomCrawler\Crawler as SymfonyCrawler;
|
||||
use Yii;
|
||||
use yii\base\Security;
|
||||
use yii\db\ActiveQueryInterface;
|
||||
use yii\helpers\Url;
|
||||
use yii\web\Application;
|
||||
use yii\web\IdentityInterface;
|
||||
|
||||
/**
|
||||
* This module provides integration with [Yii framework](https://www.yiiframework.com/) (2.0).
|
||||
*
|
||||
* It initializes the Yii framework in a test environment and provides actions
|
||||
* for functional testing.
|
||||
*
|
||||
* ## Application state during testing
|
||||
*
|
||||
* This section details what you can expect when using this module.
|
||||
*
|
||||
* * You will get a fresh application in `\Yii::$app` at the start of each test
|
||||
* (available in the test and in `_before()`).
|
||||
* * Inside your test you may change application state; however these changes
|
||||
* will be lost when doing a request if you have enabled `recreateApplication`.
|
||||
* * When executing a request via one of the request functions the `request`
|
||||
* and `response` component are both recreated.
|
||||
* * After a request the whole application is available for inspection /
|
||||
* interaction.
|
||||
* * You may use multiple database connections, each will use a separate
|
||||
* transaction; to prevent accidental mistakes we will warn you if you try to
|
||||
* connect to the same database twice but we cannot reuse the same connection.
|
||||
*
|
||||
* ## Config
|
||||
*
|
||||
* * `configFile` *required* - path to the application config file. The file
|
||||
* should be configured for the test environment and return a configuration
|
||||
* array.
|
||||
* * `applicationClass` - Fully qualified class name for the application. There are
|
||||
* several ways to define the application class. Either via a `class` key in the Yii
|
||||
* config, via specifying this codeception module configuration value or let codeception
|
||||
* use its default value `yii\web\Application`. In a standard Yii application, this
|
||||
* value should be either `yii\console\Application`, `yii\web\Application` or unset.
|
||||
* * `entryUrl` - initial application url (default: http://localhost/index-test.php).
|
||||
* * `entryScript` - front script title (like: index-test.php). If not set it's
|
||||
* taken from `entryUrl`.
|
||||
* * `transaction` - (default: `true`) wrap all database connection inside a
|
||||
* transaction and roll it back after the test. Should be disabled for
|
||||
* acceptance testing.
|
||||
* * `cleanup` - (default: `true`) cleanup fixtures after the test
|
||||
* * `ignoreCollidingDSN` - (default: `false`) When 2 database connections use
|
||||
* the same DSN but different settings an exception will be thrown. Set this to
|
||||
* true to disable this behavior.
|
||||
* * `fixturesMethod` - (default: `_fixtures`) Name of the method used for
|
||||
* creating fixtures.
|
||||
* * `responseCleanMethod` - (default: `clear`) Method for cleaning the
|
||||
* response object. Note that this is only for multiple requests inside a
|
||||
* single test case. Between test cases the whole application is always
|
||||
* recreated.
|
||||
* * `requestCleanMethod` - (default: `recreate`) Method for cleaning the
|
||||
* request object. Note that this is only for multiple requests inside a single
|
||||
* test case. Between test cases the whole application is always recreated.
|
||||
* * `recreateComponents` - (default: `[]`) Some components change their state
|
||||
* making them unsuitable for processing multiple requests. In production
|
||||
* this is usually not a problem since web apps tend to die and start over
|
||||
* after each request. This allows you to list application components that
|
||||
* need to be recreated before each request. As a consequence, any
|
||||
* components specified here should not be changed inside a test since those
|
||||
* changes will get discarded.
|
||||
* * `recreateApplication` - (default: `false`) whether to recreate the whole
|
||||
* application before each request
|
||||
*
|
||||
* You can use this module by setting params in your `functional.suite.yml`:
|
||||
*
|
||||
* ```yaml
|
||||
* actor: FunctionalTester
|
||||
* modules:
|
||||
* enabled:
|
||||
* - Yii2:
|
||||
* configFile: 'path/to/config.php'
|
||||
* ```
|
||||
*
|
||||
* ## Parts
|
||||
*
|
||||
* By default all available methods are loaded, but you can also use the `part`
|
||||
* option to select only the needed actions and to avoid conflicts. The
|
||||
* available parts are:
|
||||
*
|
||||
* * `init` - use the module only for initialization (for acceptance tests).
|
||||
* * `orm` - include only `haveRecord/grabRecord/seeRecord/dontSeeRecord` actions.
|
||||
* * `fixtures` - use fixtures inside tests with `haveFixtures/grabFixture/grabFixtures` actions.
|
||||
* * `email` - include email actions `seeEmailsIsSent/grabLastSentEmail/...`
|
||||
*
|
||||
* See [WebDriver module](https://codeception.com/docs/modules/WebDriver#Loading-Parts-from-other-Modules)
|
||||
* for general information on how to load parts of a framework module.
|
||||
*
|
||||
* ### Example (`acceptance.suite.yml`)
|
||||
*
|
||||
* ```yaml
|
||||
* actor: AcceptanceTester
|
||||
* modules:
|
||||
* enabled:
|
||||
* - WebDriver:
|
||||
* url: http://127.0.0.1:8080/
|
||||
* browser: firefox
|
||||
* - Yii2:
|
||||
* configFile: 'config/test.php'
|
||||
* part: orm # allow to use AR methods
|
||||
* transaction: false # don't wrap test in transaction
|
||||
* cleanup: false # don't cleanup the fixtures
|
||||
* entryScript: index-test.php
|
||||
* ```
|
||||
*
|
||||
* ## Fixtures
|
||||
*
|
||||
* This module allows to use
|
||||
* [fixtures](https://www.yiiframework.com/doc-2.0/guide-test-fixtures.html)
|
||||
* inside a test. There are two ways to do that. Fixtures can either be loaded
|
||||
* with the [haveFixtures](#haveFixtures) method inside a test:
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* $I->haveFixtures(['posts' => PostsFixture::class]);
|
||||
* ```
|
||||
*
|
||||
* or, if you need to load fixtures before the test, you
|
||||
* can specify fixtures in the `_fixtures` method of a test case:
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* // inside Cest file or Codeception\TestCase\Unit
|
||||
* public function _fixtures()
|
||||
* {
|
||||
* return ['posts' => PostsFixture::class]
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ## URL
|
||||
*
|
||||
* With this module you can also use Yii2's URL format for all codeception
|
||||
* commands that expect a URL:
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* $I->amOnPage('index-test.php?r=site/index');
|
||||
* $I->amOnPage('http://localhost/index-test.php?r=site/index');
|
||||
* $I->sendAjaxPostRequest(['/user/update', 'id' => 1], ['UserForm[name]' => 'G.Hopper']);
|
||||
* ```
|
||||
*
|
||||
* ## Status
|
||||
*
|
||||
* Maintainer: **samdark**
|
||||
* Stability: **stable**
|
||||
*
|
||||
*/
|
||||
class Yii2 extends Framework implements ActiveRecord, MultiSession, PartedModule
|
||||
{
|
||||
/**
|
||||
* Application config file must be set.
|
||||
* @var array
|
||||
*/
|
||||
protected array $config = [
|
||||
'fixturesMethod' => '_fixtures',
|
||||
'cleanup' => true,
|
||||
'ignoreCollidingDSN' => false,
|
||||
'transaction' => true,
|
||||
'entryScript' => '',
|
||||
'entryUrl' => 'http://localhost/index-test.php',
|
||||
'responseCleanMethod' => Yii2Connector::CLEAN_CLEAR,
|
||||
'requestCleanMethod' => Yii2Connector::CLEAN_RECREATE,
|
||||
'recreateComponents' => [],
|
||||
'recreateApplication' => false,
|
||||
'closeSessionOnRecreateApplication' => true,
|
||||
'applicationClass' => null,
|
||||
];
|
||||
|
||||
protected array $requiredFields = ['configFile'];
|
||||
|
||||
/**
|
||||
* @var Yii2Connector\FixturesStore[]
|
||||
*/
|
||||
public array $loadedFixtures = [];
|
||||
|
||||
/**
|
||||
* Helper to manage database connections
|
||||
*/
|
||||
private ConnectionWatcher $connectionWatcher;
|
||||
|
||||
/**
|
||||
* Helper to force database transaction
|
||||
*/
|
||||
private TransactionForcer $transactionForcer;
|
||||
|
||||
/**
|
||||
* @var array The contents of $_SERVER upon initialization of this object.
|
||||
* This is only used to restore it upon object destruction.
|
||||
* It MUST not be used anywhere else.
|
||||
*/
|
||||
private array $server;
|
||||
|
||||
private Logger $yiiLogger;
|
||||
|
||||
private function getClient(): \Codeception\Lib\Connector\Yii2|null
|
||||
{
|
||||
if (isset($this->client) && !$this->client instanceof \Codeception\Lib\Connector\Yii2) {
|
||||
throw new \RuntimeException('The Yii2 module must be used with the Yii2 browser client');
|
||||
}
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
public function _initialize(): void
|
||||
{
|
||||
if ($this->config['transaction'] === null) {
|
||||
$this->config['transaction'] = $this->backupConfig['transaction'] = $this->config['cleanup'];
|
||||
}
|
||||
|
||||
$this->defineConstants();
|
||||
$this->server = $_SERVER;
|
||||
$this->initServerGlobal();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Module configuration changed inside a test.
|
||||
* We always re-create the application.
|
||||
*/
|
||||
protected function onReconfigure(): void
|
||||
{
|
||||
parent::onReconfigure();
|
||||
$this->getClient()->resetApplication();
|
||||
$this->configureClient($this->config);
|
||||
$this->yiiLogger->getAndClearLog();
|
||||
$this->getClient()->startApp($this->yiiLogger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the required server params.
|
||||
* Note this is done separately from the request cycle since someone might call
|
||||
* `Url::to` before doing a request, which would instantiate the request component with incorrect server params.
|
||||
*/
|
||||
private function initServerGlobal(): void
|
||||
{
|
||||
|
||||
$entryUrl = $this->config['entryUrl'];
|
||||
$entryFile = $this->config['entryScript'] ?: basename($entryUrl);
|
||||
$entryScript = $this->config['entryScript'] ?: parse_url($entryUrl, PHP_URL_PATH);
|
||||
$_SERVER = array_merge($_SERVER, [
|
||||
'SCRIPT_FILENAME' => $entryFile,
|
||||
'SCRIPT_NAME' => $entryScript,
|
||||
'SERVER_NAME' => parse_url($entryUrl, PHP_URL_HOST),
|
||||
'SERVER_PORT' => parse_url($entryUrl, PHP_URL_PORT) ?: '80',
|
||||
'HTTPS' => parse_url($entryUrl, PHP_URL_SCHEME) === 'https'
|
||||
]);
|
||||
}
|
||||
|
||||
protected function validateConfig(): void
|
||||
{
|
||||
parent::validateConfig();
|
||||
|
||||
$pathToConfig = codecept_absolute_path($this->config['configFile']);
|
||||
if (!is_file($pathToConfig)) {
|
||||
throw new ModuleConfigException(
|
||||
__CLASS__,
|
||||
"The application config file does not exist: " . $pathToConfig
|
||||
);
|
||||
}
|
||||
|
||||
if (!in_array($this->config['responseCleanMethod'], Yii2Connector::CLEAN_METHODS)) {
|
||||
throw new ModuleConfigException(
|
||||
__CLASS__,
|
||||
"The response clean method must be one of: " . implode(", ", Yii2Connector::CLEAN_METHODS)
|
||||
);
|
||||
}
|
||||
|
||||
if (!in_array($this->config['requestCleanMethod'], Yii2Connector::CLEAN_METHODS)) {
|
||||
throw new ModuleConfigException(
|
||||
__CLASS__,
|
||||
"The response clean method must be one of: " . implode(", ", Yii2Connector::CLEAN_METHODS)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function configureClient(array $settings): void
|
||||
{
|
||||
$settings['configFile'] = codecept_absolute_path($settings['configFile']);
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
if (property_exists($this->client, $key)) {
|
||||
$this->getClient()->$key = $value;
|
||||
}
|
||||
}
|
||||
$this->getClient()->resetApplication();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates the client based on module configuration
|
||||
*/
|
||||
protected function recreateClient(): void
|
||||
{
|
||||
$entryUrl = $this->config['entryUrl'];
|
||||
$entryFile = $this->config['entryScript'] ?: basename($entryUrl);
|
||||
$entryScript = $this->config['entryScript'] ?: parse_url($entryUrl, PHP_URL_PATH);
|
||||
|
||||
$this->client = new Yii2Connector([
|
||||
'SCRIPT_FILENAME' => $entryFile,
|
||||
'SCRIPT_NAME' => $entryScript,
|
||||
'SERVER_NAME' => parse_url($entryUrl, PHP_URL_HOST),
|
||||
'SERVER_PORT' => parse_url($entryUrl, PHP_URL_PORT) ?: '80',
|
||||
'HTTPS' => parse_url($entryUrl, PHP_URL_SCHEME) === 'https'
|
||||
]);
|
||||
|
||||
$this->configureClient($this->config);
|
||||
}
|
||||
|
||||
public function _before(TestInterface $test): void
|
||||
{
|
||||
$this->recreateClient();
|
||||
$this->yiiLogger = new Yii2Connector\Logger();
|
||||
$this->getClient()->startApp($this->yiiLogger);
|
||||
|
||||
$this->connectionWatcher = new ConnectionWatcher();
|
||||
$this->connectionWatcher->start();
|
||||
|
||||
// load fixtures before db transaction
|
||||
if ($test instanceof \Codeception\Test\Cest) {
|
||||
$this->loadFixtures($test->getTestInstance());
|
||||
} elseif ($test instanceof \Codeception\Test\TestCaseWrapper) {
|
||||
$this->loadFixtures($test->getTestCase());
|
||||
} else {
|
||||
$this->loadFixtures($test);
|
||||
}
|
||||
|
||||
|
||||
$this->startTransactions();
|
||||
}
|
||||
|
||||
/**
|
||||
* load fixtures before db transaction
|
||||
*/
|
||||
private function loadFixtures(object $test): void
|
||||
{
|
||||
$this->debugSection('Fixtures', 'Loading fixtures');
|
||||
if (empty($this->loadedFixtures)
|
||||
&& method_exists($test, $this->_getConfig('fixturesMethod'))
|
||||
) {
|
||||
$connectionWatcher = new ConnectionWatcher();
|
||||
$connectionWatcher->start();
|
||||
$this->haveFixtures(call_user_func([$test, $this->_getConfig('fixturesMethod')]));
|
||||
$connectionWatcher->stop();
|
||||
$connectionWatcher->closeAll();
|
||||
}
|
||||
$this->debugSection('Fixtures', 'Done');
|
||||
}
|
||||
|
||||
public function _after(TestInterface $test): void
|
||||
{
|
||||
$_SESSION = [];
|
||||
$_FILES = [];
|
||||
$_GET = [];
|
||||
$_POST = [];
|
||||
$_COOKIE = [];
|
||||
$_REQUEST = [];
|
||||
|
||||
$this->rollbackTransactions();
|
||||
|
||||
if ($this->config['cleanup']) {
|
||||
foreach ($this->loadedFixtures as $fixture) {
|
||||
$fixture->unloadFixtures();
|
||||
}
|
||||
$this->loadedFixtures = [];
|
||||
}
|
||||
|
||||
$this->getClient()?->resetApplication();
|
||||
|
||||
if (isset($this->connectionWatcher)) {
|
||||
$this->connectionWatcher->stop();
|
||||
$this->connectionWatcher->closeAll();
|
||||
unset($this->connectionWatcher);
|
||||
}
|
||||
|
||||
parent::_after($test);
|
||||
}
|
||||
|
||||
public function _failed(TestInterface $test, $fail): void
|
||||
{
|
||||
$log = $this->yiiLogger->getAndClearLog();
|
||||
if ($log !== '') {
|
||||
$test->getMetadata()->addReport('yii-log', $log);
|
||||
}
|
||||
|
||||
parent::_failed($test, $fail);
|
||||
}
|
||||
|
||||
protected function startTransactions(): void
|
||||
{
|
||||
if ($this->config['transaction']) {
|
||||
$this->transactionForcer = new TransactionForcer($this->config['ignoreCollidingDSN']);
|
||||
$this->transactionForcer->start();
|
||||
}
|
||||
}
|
||||
|
||||
protected function rollbackTransactions(): void
|
||||
{
|
||||
if (isset($this->transactionForcer)) {
|
||||
$this->transactionForcer->rollbackAll();
|
||||
$this->transactionForcer->stop();
|
||||
unset($this->transactionForcer);
|
||||
}
|
||||
}
|
||||
|
||||
public function _parts(): array
|
||||
{
|
||||
return ['orm', 'init', 'fixtures', 'email'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a user on a site without submitting a login form.
|
||||
* Use it for fast pragmatic authorization in functional tests.
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* // User is found by id
|
||||
* $I->amLoggedInAs(1);
|
||||
*
|
||||
* // User object is passed as parameter
|
||||
* $admin = \app\models\User::findByUsername('admin');
|
||||
* $I->amLoggedInAs($admin);
|
||||
* ```
|
||||
* Requires the `user` component to be enabled and configured.
|
||||
*
|
||||
* @throws \Codeception\Exception\ModuleException
|
||||
*/
|
||||
public function amLoggedInAs(int|string|IdentityInterface $user): void
|
||||
{
|
||||
try {
|
||||
$this->getClient()?->findAndLoginUser($user);
|
||||
} catch (ConfigurationException $e) {
|
||||
throw new ModuleException($this, $e->getMessage());
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new ModuleException($this, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and loads fixtures from a config.
|
||||
* The signature is the same as for the `fixtures()` method of `yii\test\FixtureTrait`
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* $I->haveFixtures([
|
||||
* 'posts' => PostsFixture::class,
|
||||
* 'user' => [
|
||||
* 'class' => UserFixture::class,
|
||||
* 'dataFile' => '@tests/_data/models/user.php',
|
||||
* ],
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* Note: if you need to load fixtures before a test (probably before the
|
||||
* cleanup transaction is started; `cleanup` option is `true` by default),
|
||||
* you can specify the fixtures in the `_fixtures()` method of a test case
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* // inside Cest file or Codeception\TestCase\Unit
|
||||
* public function _fixtures(){
|
||||
* return [
|
||||
* 'user' => [
|
||||
* 'class' => UserFixture::class,
|
||||
* 'dataFile' => codecept_data_dir() . 'user.php'
|
||||
* ]
|
||||
* ];
|
||||
* }
|
||||
* ```
|
||||
* instead of calling `haveFixtures` in Cest `_before`
|
||||
*
|
||||
* @param $fixtures
|
||||
* @part fixtures
|
||||
*/
|
||||
public function haveFixtures($fixtures): void
|
||||
{
|
||||
if (empty($fixtures)) {
|
||||
return;
|
||||
}
|
||||
$fixturesStore = new Yii2Connector\FixturesStore($fixtures);
|
||||
$fixturesStore->unloadFixtures();
|
||||
$fixturesStore->loadFixtures();
|
||||
$this->loadedFixtures[] = $fixturesStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all loaded fixtures.
|
||||
* Array of fixture instances
|
||||
*
|
||||
* @part fixtures
|
||||
* @return array
|
||||
*/
|
||||
public function grabFixtures()
|
||||
{
|
||||
if (!$this->loadedFixtures) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return call_user_func_array(
|
||||
'array_merge',
|
||||
array_map( // merge all fixtures from all fixture stores
|
||||
function ($fixturesStore) {
|
||||
return $fixturesStore->getFixtures();
|
||||
},
|
||||
$this->loadedFixtures
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a fixture by name.
|
||||
* Returns a Fixture instance. If a fixture is an instance of
|
||||
* `\yii\test\BaseActiveFixture` a second parameter can be used to return a
|
||||
* specific model:
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* $I->haveFixtures(['users' => UserFixture::class]);
|
||||
*
|
||||
* $users = $I->grabFixture('users');
|
||||
*
|
||||
* // get first user by key, if a fixture is an instance of ActiveFixture
|
||||
* $user = $I->grabFixture('users', 'user1');
|
||||
* ```
|
||||
*
|
||||
* @param $name
|
||||
* @return mixed
|
||||
* @throws \Codeception\Exception\ModuleException if the fixture is not found
|
||||
* @part fixtures
|
||||
*/
|
||||
public function grabFixture($name, $index = null)
|
||||
{
|
||||
$fixtures = $this->grabFixtures();
|
||||
if (!isset($fixtures[$name])) {
|
||||
throw new ModuleException($this, "Fixture $name is not loaded");
|
||||
}
|
||||
$fixture = $fixtures[$name];
|
||||
if ($index === null) {
|
||||
return $fixture;
|
||||
}
|
||||
if ($fixture instanceof \yii\test\BaseActiveFixture) {
|
||||
return $fixture->getModel($index);
|
||||
}
|
||||
throw new ModuleException($this, "Fixture $name is not an instance of ActiveFixture and can't be loaded with second parameter");
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a record into the database.
|
||||
*
|
||||
* ``` php
|
||||
* <?php
|
||||
* $user_id = $I->haveRecord('app\models\User', array('name' => 'Davert'));
|
||||
* ?>
|
||||
* ```
|
||||
* @template T of \yii\db\ActiveRecord
|
||||
* @param class-string<T> $model
|
||||
* @param array<string, mixed> $attributes
|
||||
* @return mixed
|
||||
* @part orm
|
||||
*/
|
||||
public function haveRecord(string $model, $attributes = []): mixed
|
||||
{
|
||||
/** @var T $record **/
|
||||
$record = \Yii::createObject($model);
|
||||
$record->setAttributes($attributes, false);
|
||||
$res = $record->save(false);
|
||||
if (!$res) {
|
||||
$this->fail("Record $model was not saved: " . \yii\helpers\Json::encode($record->errors));
|
||||
}
|
||||
return $record->primaryKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a record exists in the database.
|
||||
*
|
||||
* ``` php
|
||||
* $I->seeRecord('app\models\User', array('name' => 'davert'));
|
||||
* ```
|
||||
*
|
||||
* @param class-string<\yii\db\ActiveRecord> $model
|
||||
* @param array<string, mixed> $attributes
|
||||
* @part orm
|
||||
*/
|
||||
public function seeRecord(string $model, array $attributes = []): void
|
||||
{
|
||||
$record = $this->findRecord($model, $attributes);
|
||||
if (!$record) {
|
||||
$this->fail("Couldn't find $model with " . json_encode($attributes));
|
||||
}
|
||||
$this->debugSection($model, json_encode($record));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a record does not exist in the database.
|
||||
*
|
||||
* ``` php
|
||||
* $I->dontSeeRecord('app\models\User', array('name' => 'davert'));
|
||||
* ```
|
||||
*
|
||||
* @param class-string<\yii\db\ActiveRecord> $model
|
||||
* @param array<string, mixed> $attributes
|
||||
* @part orm
|
||||
*/
|
||||
public function dontSeeRecord(string $model, array $attributes = []): void
|
||||
{
|
||||
$record = $this->findRecord($model, $attributes);
|
||||
$this->debugSection($model, json_encode($record));
|
||||
if ($record) {
|
||||
$this->fail("Unexpectedly managed to find $model with " . json_encode($attributes));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a record from the database
|
||||
*
|
||||
* ``` php
|
||||
* $category = $I->grabRecord('app\models\User', array('name' => 'davert'));
|
||||
* ```
|
||||
*
|
||||
* @param class-string<\yii\db\ActiveRecord> $model
|
||||
* @param array<string, mixed> $attributes
|
||||
* @part orm
|
||||
*/
|
||||
public function grabRecord(string $model, array $attributes = []): \yii\db\ActiveRecord|null|array
|
||||
{
|
||||
return $this->findRecord($model, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<\yii\db\ActiveRecord> $model Class name
|
||||
* @param array<string, mixed> $attributes
|
||||
*/
|
||||
protected function findRecord(string $model, array $attributes = []): \yii\db\ActiveRecord | null | array
|
||||
{
|
||||
if (!class_exists($model)) {
|
||||
throw new \RuntimeException("Class $model does not exist");
|
||||
}
|
||||
$rc = new \ReflectionClass($model);
|
||||
if ($rc->hasMethod('find')
|
||||
/** @phpstan-ignore-next-line */
|
||||
&& ($findMethod = $rc->getMethod('find'))
|
||||
&& $findMethod->isStatic()
|
||||
&& $findMethod->isPublic()
|
||||
&& $findMethod->getNumberOfRequiredParameters() === 0
|
||||
) {
|
||||
$activeQuery = $findMethod->invoke(null);
|
||||
if ($activeQuery instanceof ActiveQueryInterface) {
|
||||
return $activeQuery->andWhere($attributes)->one();
|
||||
}
|
||||
|
||||
throw new \RuntimeException("$model::find() must return an instance of yii\db\QueryInterface");
|
||||
}
|
||||
throw new \RuntimeException("Class $model does not have a public static find() method without required parameters");
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to `amOnPage` but accepts a route as first argument and params as second
|
||||
*
|
||||
* ```
|
||||
* $I->amOnRoute('site/view', ['page' => 'about']);
|
||||
* ```
|
||||
*
|
||||
* @param string $route A route
|
||||
* @param array $params Additional route parameters
|
||||
*/
|
||||
public function amOnRoute(string $route, array $params = []): void
|
||||
{
|
||||
if (Yii::$app->controller === null) {
|
||||
$route = "/{$route}";
|
||||
}
|
||||
|
||||
array_unshift($params, $route);
|
||||
$this->amOnPage(Url::to($params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a component from the Yii container. Throws an exception if the
|
||||
* component is not available
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* $mailer = $I->grabComponent('mailer');
|
||||
* ```
|
||||
*
|
||||
* @throws \Codeception\Exception\ModuleException
|
||||
* @deprecated in your tests you can use \Yii::$app directly.
|
||||
*/
|
||||
public function grabComponent(string $component): null|object
|
||||
{
|
||||
try {
|
||||
return $this->getClient()->getComponent($component);
|
||||
} catch (ConfigurationException $e) {
|
||||
throw new ModuleException($this, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that an email is sent.
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* // check that at least 1 email was sent
|
||||
* $I->seeEmailIsSent();
|
||||
*
|
||||
* // check that only 3 emails were sent
|
||||
* $I->seeEmailIsSent(3);
|
||||
* ```
|
||||
*
|
||||
* @param int|null $num
|
||||
* @throws \Codeception\Exception\ModuleException
|
||||
* @part email
|
||||
*/
|
||||
public function seeEmailIsSent(?int $num = null): void
|
||||
{
|
||||
if ($num === null) {
|
||||
$this->assertNotEmpty($this->grabSentEmails(), 'emails were sent');
|
||||
return;
|
||||
}
|
||||
$this->assertEquals($num, count($this->grabSentEmails()), 'number of sent emails is equal to ' . $num);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that no email was sent
|
||||
*
|
||||
* @part email
|
||||
*/
|
||||
public function dontSeeEmailIsSent(): void
|
||||
{
|
||||
$this->seeEmailIsSent(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of all sent email messages.
|
||||
* Each message implements the `yii\mail\MessageInterface` interface.
|
||||
* Useful to perform additional checks using the `Asserts` module:
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* $I->seeEmailIsSent();
|
||||
* $messages = $I->grabSentEmails();
|
||||
* $I->assertEquals('admin@site,com', $messages[0]->getTo());
|
||||
* ```
|
||||
*
|
||||
* @part email
|
||||
* @return array
|
||||
* @throws \Codeception\Exception\ModuleException
|
||||
*/
|
||||
public function grabSentEmails(): array
|
||||
{
|
||||
try {
|
||||
return $this->getClient()->getEmails();
|
||||
} catch (ConfigurationException $e) {
|
||||
throw new ModuleException($this, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last sent email:
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* $I->seeEmailIsSent();
|
||||
* $message = $I->grabLastSentEmail();
|
||||
* $I->assertEquals('admin@site,com', $message->getTo());
|
||||
* ```
|
||||
* @part email
|
||||
*/
|
||||
public function grabLastSentEmail(): object
|
||||
{
|
||||
$this->seeEmailIsSent();
|
||||
$messages = $this->grabSentEmails();
|
||||
return end($messages);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of regex patterns for recognized domain names
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getInternalDomains(): array
|
||||
{
|
||||
return $this->getClient()->getInternalDomains();
|
||||
}
|
||||
|
||||
private function defineConstants(): void
|
||||
{
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||
defined('YII_ENV') or define('YII_ENV', 'test');
|
||||
defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a cookie and, if validation is enabled, signs it.
|
||||
* @param string $name The name of the cookie
|
||||
* @param string $val The value of the cookie
|
||||
* @param array $params Additional cookie params like `domain`, `path`, `expires` and `secure`.
|
||||
*/
|
||||
public function setCookie($name, $val, $params = [])
|
||||
{
|
||||
parent::setCookie($name, $this->getClient()->hashCookieData($name, $val), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the CSRF Cookie.
|
||||
* @param string $val The value of the CSRF token
|
||||
* @return string[] Returns an array containing the name of the CSRF param and the masked CSRF token.
|
||||
*/
|
||||
public function createAndSetCsrfCookie(string $val): array
|
||||
{
|
||||
$masked = (new Security())->maskToken($val);
|
||||
$name = $this->getClient()->getCsrfParamName();
|
||||
$this->setCookie($name, $val);
|
||||
return [$name, $masked];
|
||||
}
|
||||
|
||||
public function _afterSuite(): void
|
||||
{
|
||||
parent::_afterSuite();
|
||||
codecept_debug('Suite done, restoring $_SERVER to original');
|
||||
|
||||
$_SERVER = $this->server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize an empty session. Implements MultiSession.
|
||||
*/
|
||||
public function _initializeSession(): void
|
||||
{
|
||||
$this->getClient()->restart();
|
||||
$this->headers = [];
|
||||
$_SESSION = [];
|
||||
$_COOKIE = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the session content for future restoring. Implements MultiSession.
|
||||
* @return array<string, mixed> backup data
|
||||
*/
|
||||
public function _backupSession(): array
|
||||
{
|
||||
if (Yii::$app instanceof Application && Yii::$app->has('session', true) && Yii::$app->session->useCustomStorage) {
|
||||
throw new ModuleException($this, "Yii2 MultiSession only supports the default session backend.");
|
||||
}
|
||||
return [
|
||||
'clientContext' => $this->getClient()->getContext(),
|
||||
'headers' => $this->headers,
|
||||
'cookie' => isset($_COOKIE) ? $_COOKIE : [],
|
||||
'session' => isset($_SESSION) ? $_SESSION : [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a session. Implements MultiSession.
|
||||
* @param array<mixed> $session output of _backupSession()
|
||||
*/
|
||||
public function _loadSession($session): void
|
||||
{
|
||||
$this->getClient()->setContext($session['clientContext']);
|
||||
$this->headers = $session['headers'];
|
||||
$_SESSION = $session['session'];
|
||||
$_COOKIE = $session['cookie'];
|
||||
|
||||
// reset Yii::$app->user
|
||||
if (isset(Yii::$app)) {
|
||||
$app = Yii::$app;
|
||||
$definitions = $app->getComponents(true);
|
||||
if ($app->has('user', true)) {
|
||||
$app->set('user', $definitions['user']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close and dump a session. Implements MultiSession.
|
||||
*/
|
||||
public function _closeSession($session = null): void
|
||||
{
|
||||
if (!$session) {
|
||||
$this->_initializeSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user