Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 67 additions & 83 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@ name: CI

on:
push:
branches:
- 3.x
- 4.x
- 5.x
branches: [3.x, 4.x, 5.x]
pull_request:
branches:
- '*'
branches: ['*']
workflow_dispatch:

jobs:
Expand All @@ -17,92 +13,80 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version: ['8.1']
php-version: ['8.1', '8.2', '8.3', '8.4']
prefer-lowest: ['']
include:
- php-version: '8.1'
prefer-lowest: 'prefer-lowest'

steps:
- uses: actions/checkout@v5

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, intl
coverage: pcov

- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"

- name: Get date part for cache key
id: key-date
run: echo "::set-output name=date::$(date +'%Y-%m')"

- name: Cache composer dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}-${{ matrix.prefer-lowest }}

- name: Composer Install
run: |
if ${{ matrix.prefer-lowest == 'prefer-lowest' }}; then
make install-dev-lowest
elif ${{ matrix.php-version == '8.1' }}; then
make install-dev-ignore-reqs
else
make install-dev
fi

- name: Setup problem matchers for PHPUnit
if: matrix.php-version == '8.1'
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

- name: Run PHPUnit
run: |
if [[ ${{ matrix.php-version }} == '8.1' ]]; then
export CODECOVERAGE=1 && vendor/bin/phpunit --display-incomplete --display-skipped --coverage-clover=coverage.xml
else
vendor/bin/phpunit
fi

- name: Submit code coverage
if: matrix.php-version == '8.1'
uses: codecov/codecov-action@v5
- uses: actions/checkout@v5

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, intl
coverage: pcov

- name: Cache composer dependencies
id: composer-cache
uses: actions/cache@v4
with:
path: ~/.composer/cache
key: ${{ runner.os }}-composer-${{ matrix.php-version }}-${{ matrix.prefer-lowest }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-${{ matrix.php-version }}-${{ matrix.prefer-lowest }}-

- name: Composer install
run: |
if [[ "${{ matrix.prefer-lowest }}" == "prefer-lowest" ]]; then
make install-dev-lowest
elif [[ "${{ matrix.php-version }}" == "8.1" ]]; then
make install-dev-ignore-reqs
else
make install-dev
fi

- name: Setup problem matchers for PHPUnit
if: matrix.php-version == '8.1'
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

- name: Run PHPUnit
run: |
if [[ "${{ matrix.php-version }}" == "8.1" && "${{ matrix.prefer-lowest }}" != "prefer-lowest" ]]; then
export CODECOVERAGE=1
vendor/bin/phpunit --display-incomplete --display-skipped --coverage-clover=coverage.xml
else
vendor/bin/phpunit
fi

- name: Submit code coverage
if: matrix.php-version == '8.1' && matrix.prefer-lowest != 'prefer-lowest'
uses: codecov/codecov-action@v5

cs-stan:
name: Coding Standard & Static Analysis
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v5

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: mbstring, intl
coverage: none

- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"

- name: Get date part for cache key
id: key-date
run: echo "::set-output name=date::$(date +'%Y-%m')"

- name: Cache composer dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}-${{ matrix.prefer-lowest }}

- name: Composer install
run: make install-dev

- name: Run PHP CodeSniffer
run: vendor/bin/phpcs --report=checkstyle
- uses: actions/checkout@v5

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: mbstring, intl
coverage: none

- name: Cache composer dependencies
uses: actions/cache@v4
with:
path: ~/.composer/cache
key: ${{ runner.os }}-composer-8.1-${{ hashFiles('**/composer.lock') }}

- name: Composer install
run: make install-dev

- name: Run PHP CodeSniffer
run: vendor/bin/phpcs --report=checkstyle
3 changes: 3 additions & 0 deletions config/rector/sets/cakephp60.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
declare(strict_types=1);

use Cake\Upgrade\Rector\Rector\MethodCall\EventManagerOnRector;
use Cake\Upgrade\Rector\Rector\MethodCall\ReplaceCommandArgsIoWithPropertiesRector;
use PHPStan\Type\ObjectType;
use Rector\Config\RectorConfig;
use Rector\Renaming\Rector\MethodCall\RenameMethodRector;
Expand All @@ -20,6 +21,8 @@
// EventManager::on() signature change
$rectorConfig->rule(EventManagerOnRector::class);

$rectorConfig->rule(ReplaceCommandArgsIoWithPropertiesRector::class);

// Changes related to the accessible => patchable rename
$rectorConfig->ruleWithConfiguration(RenameMethodRector::class, [
new MethodCallRename('Cake\ORM\Entity', 'setAccess', 'setPatchable'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php
declare(strict_types=1);

namespace Cake\Upgrade\Rector\Rector\MethodCall;

use Cake\Command\Command;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Reflection\ReflectionProvider;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\PHPStan\ScopeFetcher;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

final class ReplaceCommandArgsIoWithPropertiesRector extends AbstractRector
{
public function __construct(
protected BetterNodeFinder $betterNodeFinder,
protected ReflectionProvider $reflectionProvider,
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Replace `$args` and `$io` parameters in Command classes with `$this->args` and `$this->io`',
[
new CodeSample(
<<<'CODE_SAMPLE'
class TestCommand extends Command
{
public function execute(Arguments $args, ConsoleIo $io)
{
$io->out('Hello');
$this->someMethod($args, $io);
}

protected function someMethod(Arguments $args, ConsoleIo $io): void
{
$someArg = $args->getArgument('some');
$io->warning('Warn');
}
}
CODE_SAMPLE,
<<<'CODE_SAMPLE'
class TestCommand extends Command
{
public function execute()
{
$this->io->out('Hello');
$this->someMethod();
}

protected function someMethod(): void
{
$someArg = $this->args->getArgument('some');
$this->io->warning('Warn');
}
}
CODE_SAMPLE,
),
],
);
}

public function getNodeTypes(): array
{
return [ClassMethod::class];
}

public function refactor(Node $node): ?Node
{
if (! $node instanceof ClassMethod) {
return null;
}
Comment on lines +78 to +80
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The operating spacing here doesn't seem to be consistent with the cakephp cs rules. Perhaps we aren't running those phpcs rules on this repo?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

according to the phpcs.xml file everything inside the src folder should be checked. don't know why it doesn't fix this kind of cs violation.


// Make sure we are in a class
$scope = ScopeFetcher::fetch($node);
if (!$scope->isInClass()) {
return null;
}
$class = $scope->getClassReflection();

// Skip if class doesn't extend Command (you can expand to check parent name)
$baseCommandClass = $this->reflectionProvider->getClass(Command::class);
if ($class->getName() === Command::class || $class->isSubclassOfClass($baseCommandClass) === false) {
return null;
}

// Find if params are $args and/or $io
$argsParam = $this->findParam($node, 'args');
$ioParam = $this->findParam($node, 'io');

if (! $argsParam && ! $ioParam) {
return null;
}

// Replace all `$args` and `$io` usages inside the method body
$this->traverseNodesWithCallable($node->stmts ?? [], function (Node $innerNode) use ($argsParam, $ioParam) {
// Replace `$args` and `$io` variables
if ($innerNode instanceof Variable) {
if ($argsParam && $innerNode->name === 'args') {
return new PropertyFetch(new Variable('this'), 'args');
}

if ($ioParam && $innerNode->name === 'io') {
return new PropertyFetch(new Variable('this'), 'io');
}
}

// Remove `$args` / `$io` from method calls on `$this`
if (
$innerNode instanceof MethodCall
&& $innerNode->var instanceof Variable
&& $innerNode->var->name === 'this'
) {
$innerNode->args = array_values(array_filter(
$innerNode->args,
fn(Node\Arg $arg) => !($arg->value instanceof Variable &&
in_array($arg->value->name, ['args', 'io'], true)),
));

return $innerNode;
}

return null;
});

// Remove the parameters themselves
$node->params = array_filter($node->params, function (Param $param) {
return !in_array($this->getName($param), ['args', 'io'], true);
});

return $node;
}

private function findParam(ClassMethod $method, string $name): ?Param
{
foreach ($method->params as $param) {
if ($this->getName($param) === $name) {
return $param;
}
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Cake\Upgrade\Test\TestCase\Rector\MethodCall\AddMethodCallArgsRectorTest\Fixture;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;

class TestCommand extends Command
{
public function execute(Arguments $args, ConsoleIo $io)
{
$io->out('Hello World');
$this->someMethod($args, $io);
return static::CODE_SUCCESS;
}

protected function someMethod(Arguments $args, ConsoleIo $io): void
{
$someArg = $args->getArgument('some');
$io->warning('Warning');
}
}
?>
-----
<?php

namespace Cake\Upgrade\Test\TestCase\Rector\MethodCall\AddMethodCallArgsRectorTest\Fixture;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;

class TestCommand extends Command
{
public function execute()
{
$this->io->out('Hello World');
$this->someMethod();
return static::CODE_SUCCESS;
}

protected function someMethod(): void
{
$someArg = $this->args->getArgument('some');
$this->io->warning('Warning');
}
}
?>
Loading
Loading