| Current Path : /home/rtorresani/www/vendor/codeception/codeception/src/Codeception/Lib/Generator/ |
| Current File : //home/rtorresani/www/vendor/codeception/codeception/src/Codeception/Lib/Generator/Actions.php |
<?php
declare(strict_types=1);
namespace Codeception\Lib\Generator;
use Codeception\Codecept;
use Codeception\Configuration;
use Codeception\Lib\Di;
use Codeception\Lib\Generator\Shared\Classname;
use Codeception\Lib\ModuleContainer;
use Codeception\Step\GeneratedStep;
use Codeception\Util\ReflectionHelper;
use Codeception\Util\Template;
use Exception;
use InvalidArgumentException;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionException;
use ReflectionIntersectionType;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionType;
use ReflectionUnionType;
use function implode;
use function sprintf;
class Actions
{
use Classname;
public Di $di;
public ModuleContainer $moduleContainer;
protected string $template = <<<EOF
<?php //[STAMP] {{hash}}
// phpcs:ignoreFile
namespace {{namespace}}_generated;
// This class was automatically generated by build task
// You should not change it manually as it will be overwritten on next build
trait {{name}}Actions
{
/**
* @return \Codeception\Scenario
*/
abstract protected function getScenario();
{{methods}}
}
EOF;
protected string $methodTemplate = <<<EOF
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
{{doc}}
* @see \{{module}}::{{method}}()
*/{{attributes}}
public function {{action}}({{params}}){{return_type}} {
{{return}}\$this->getScenario()->runStep(new \Codeception\Step\{{step}}('{{method}}', func_get_args()));
}
EOF;
protected string $name;
protected array $settings = [];
protected array $modules = [];
protected array $actions = [];
protected int $numMethods = 0;
/**
* @var GeneratedStep[]
*/
protected array $generatedSteps = [];
public function __construct(array $settings)
{
$this->name = $settings['actor'];
$this->settings = $settings;
$this->di = new Di();
$modules = Configuration::modules($this->settings);
$this->moduleContainer = new ModuleContainer($this->di, $settings);
foreach ($modules as $moduleName) {
$this->moduleContainer->create($moduleName);
}
$this->modules = $this->moduleContainer->all();
$this->actions = $this->moduleContainer->getActions();
$this->generatedSteps = (array)$settings['step_decorators'];
}
public function produce(): string
{
$namespace = trim($this->supportNamespace(), '\\');
$methods = [];
$code = [];
foreach ($this->actions as $action => $moduleName) {
if (in_array($action, $methods)) {
continue;
}
$class = new ReflectionClass($this->modules[$moduleName]);
$method = $class->getMethod($action);
$code[] = $this->addMethod($method);
$methods[] = $action;
++$this->numMethods;
}
return (new Template($this->template))
->place('namespace', $namespace !== '' ? $namespace . '\\' : '')
->place('hash', self::genHash($this->modules, $this->settings))
->place('name', $this->name)
->place('methods', implode("\n\n ", $code))
->produce();
}
protected function addMethod(ReflectionMethod $refMethod): string
{
$class = $refMethod->getDeclaringClass();
$params = $this->getParamsString($refMethod);
$module = $class->getName();
$body = '';
$doc = $this->addDoc($class, $refMethod);
$doc = str_replace('/**', '', (string)$doc);
$doc = trim(str_replace('*/', '', $doc));
if ($doc === '') {
$doc = "*";
}
$returnType = $this->createReturnTypeHint($refMethod);
if (count($refMethod->getAttributes()) > 0) {
$attributes = "\n " . $this->getAttributesString($refMethod);
}
$methodTemplate = (new Template($this->methodTemplate))
->place('module', $module)
->place('method', $refMethod->name)
->place('attributes', $attributes ?? '')
->place('return_type', $returnType)
->place('return', ($returnType === ': void' || $returnType === ': never') ? '' : 'return ')
->place('params', $params);
if (str_starts_with($refMethod->name, 'see')) {
$type = 'Assertion';
} elseif (str_starts_with($refMethod->name, 'am')) {
$type = 'Condition';
} else {
$type = 'Action';
}
$body .= $methodTemplate
->place('doc', $doc)
->place('action', $refMethod->name)
->place('step', $type)
->produce();
// add auto generated steps
foreach (array_unique($this->generatedSteps) as $generator) {
if (!is_callable([$generator, 'getTemplate'])) {
throw new Exception("Wrong configuration for generated steps. {$generator} doesn't implement \Codeception\Step\GeneratedStep interface");
}
$template = call_user_func([$generator, 'getTemplate'], clone $methodTemplate);
if ($template) {
$body .= $template->produce();
}
}
return $body;
}
protected function getAttributesString(ReflectionMethod $refMethod): string
{
$attributes = [];
foreach ($refMethod->getAttributes() as $attribute) {
$attributes[] = $this->stringifyAttribute($attribute);
}
return implode("\n ", $attributes);
}
protected function getParamsString(ReflectionMethod $refMethod): string
{
$params = [];
foreach ($refMethod->getParameters() as $param) {
$type = '';
$reflectionType = $param->getType();
if ($reflectionType !== null) {
$type = $this->stringifyType($reflectionType, $refMethod->getDeclaringClass()) . ' ';
}
$attributes = '';
foreach ($param->getAttributes() as $attribute) {
$attributes .= $this->stringifyAttribute($attribute);
}
if ($attributes !== '') {
$attributes .= ' ';
}
if ($param->isOptional()) {
$params[] = $attributes . $type . '$' . $param->name . ' = ' . ReflectionHelper::getDefaultValue($param);
} else {
$params[] = $attributes . $type . '$' . $param->name;
}
}
return implode(', ', $params);
}
/**
* @throws ReflectionException
*/
protected function addDoc(ReflectionClass $class, ReflectionMethod $refMethod): string|false
{
$doc = $refMethod->getDocComment();
if (!$doc) {
$interfaces = $class->getInterfaces();
foreach ($interfaces as $interface) {
$i = new ReflectionClass($interface->name);
if ($i->hasMethod($refMethod->name)) {
$doc = $i->getMethod($refMethod->name)->getDocComment();
break;
}
}
}
if (!$doc && $class->getParentClass()) {
$parent = new ReflectionClass($class->getParentClass()->name);
if ($parent->hasMethod($refMethod->name)) {
return $parent->getMethod($refMethod->name)->getDocComment();
}
return $doc;
}
return $doc;
}
public static function genHash(array $modules, array $settings): string
{
$actions = [];
foreach ($modules as $moduleName => $module) {
$actions[$moduleName] = get_class_methods($module::class);
}
return md5(Codecept::VERSION . serialize($actions) . serialize($settings['modules']) . implode(',', (array)$settings['step_decorators']));
}
public function getNumMethods(): int
{
return $this->numMethods;
}
private function createReturnTypeHint(ReflectionMethod $refMethod): string
{
$returnType = $refMethod->getReturnType();
if (!$returnType instanceof ReflectionType) {
return '';
}
return ': ' . $this->stringifyType($returnType, $refMethod->getDeclaringClass());
}
private function stringifyType(ReflectionType $type, ReflectionClass $moduleClass): string
{
if ($type instanceof ReflectionUnionType) {
return $this->stringifyNamedTypes($type->getTypes(), $moduleClass, '|');
} elseif ($type instanceof ReflectionIntersectionType) {
return $this->stringifyNamedTypes($type->getTypes(), $moduleClass, '&');
} elseif ($type instanceof ReflectionNamedType) {
return sprintf(
'%s%s',
($type->allowsNull() && $type->getName() !== 'mixed') ? '?' : '',
self::stringifyNamedType($type, $moduleClass)
);
} else {
throw new InvalidArgumentException('Unsupported type class: ' . $type::class);
}
}
/**
* @param ReflectionNamedType[] $types
*/
private function stringifyNamedTypes(array $types, ReflectionClass $moduleClass, string $separator): string
{
$strings = [];
foreach ($types as $type) {
$strings[] = self::stringifyNamedType($type, $moduleClass);
}
return implode($separator, $strings);
}
public static function stringifyNamedType(ReflectionNamedType $type, ReflectionClass $moduleClass): string
{
$typeName = $type->getName();
if ($typeName === 'self') {
$typeName = $moduleClass->getName();
} elseif ($typeName === 'parent') {
$typeName = $moduleClass->getParentClass()->getName();
}
return sprintf(
'%s%s',
$type->isBuiltin() ? '' : '\\',
$typeName
);
}
private function stringifyAttribute(ReflectionAttribute $attribute): string
{
try {
$refClass = new ReflectionClass($attribute->getName());
$name = sprintf('%s%s', $refClass->isUserDefined() ? '\\' : '', $attribute->getName());
} catch (ReflectionException) {
// If we can't get the class then just return what we've been given.
$name = $attribute->getName();
}
$arguments = $attribute->getArguments();
// Strip the wrapping array brackets so parameters aren't converted to arrays.
$args = substr(ReflectionHelper::phpEncodeValue($arguments), 1, -1);
return '#[' . $name . '(' . $args . ')]';
}
}