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
85 changes: 85 additions & 0 deletions src/Command/RunTemplate/CloneCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

/**
* This file is part of the MultiFlexi package
*
* https://multiflexi.eu/
*
* (c) Vítězslav Dvořák <http://vitexsoftware.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace MultiFlexi\Cli\Command\RunTemplate;

use MultiFlexi\RunTemplate;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class CloneCommand extends BaseCommand

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Add PHPUnit coverage for the new command.

The provided changes add CloneCommand without its PHPUnit test file. Test invalid IDs, output formats, clone failures, and that an active source creates an inactive clone.

As per coding guidelines, create or update a PHPUnit test file whenever a class is created or updated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` at line 23, Add a PHPUnit test
class for CloneCommand covering invalid IDs, supported output formats, clone
failures, and successful cloning from an active source producing an inactive
clone. Follow the existing command test conventions and exercise CloneCommand
through its public command behavior.

Source: Coding guidelines

{
protected static $defaultName = 'run-template:clone';

protected function configure(): void
{
$this
->setName('run-template:clone')
->setDescription('Clone a run template; the clone is always created disabled')
->addOption('format', 'f', InputOption::VALUE_OPTIONAL, 'Output format: text or json', 'text')
->addOption('id', null, InputOption::VALUE_REQUIRED, 'Source RunTemplate ID')
->addOption('name', null, InputOption::VALUE_OPTIONAL, 'Name for the clone (default: "<source name> Clone")');
Comment on lines +31 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Translate all user-visible strings.

Only "Clone" uses _(). Wrap command descriptions, option descriptions, error messages, and success output in _(). Keep JSON field names such as status and runtemplate_id unchanged.

As per coding guidelines, src/**/*.php requires _() calls for internationalization messages.

Also applies to: 44-46, 56-59, 64-64, 69-71, 78-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` around lines 31 - 34, Update the
user-facing strings in the CloneCommand definition and execution flow, including
the command description, option descriptions, error messages, and success
output, to use _(). Preserve JSON field names such as status and runtemplate_id
unchanged, and retain the existing translated handling of “Clone.”

Source: Coding guidelines

}

protected function execute(InputInterface $input, OutputInterface $output): int
Comment on lines +23 to +37

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add docblocks for the class and methods.

CloneCommand, configure(), and execute() have no docblocks. Document the command purpose, parameters, and return value.

As per coding guidelines, **/*.php requires a docblock for every function and class.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` around lines 23 - 37, Add PHPDoc
blocks for CloneCommand, configure(), and execute(), describing the command’s
cloning purpose, method parameters, and execute() return value. Keep the
existing command behavior unchanged and follow the project’s documentation
conventions.

Source: Coding guidelines

{
$format = strtolower($input->getOption('format'));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject unsupported output formats.

--format=xml falls through to text output. Validate text and json before processing the command, then return failure for every other value. Define the repeated format values as constants.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` at line 39, Update the format
handling in CloneCommand to define constants for the supported text and JSON
formats, validate the --format value before processing, and return failure for
any unsupported value such as xml. Use the constants for both validation and
subsequent format selection.

$id = $input->getOption('id');

if (empty($id)) {
if ($format === 'json') {
$output->writeln(json_encode(['status' => 'error', 'message' => 'Missing --id'], \JSON_PRETTY_PRINT));
} else {
$output->writeln('<error>Missing --id</error>');
}

return self::FAILURE;
}

$source = new RunTemplate((int) $id);
Comment on lines +40 to +52

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate the source ID before casting it.

--id=12abc passes the empty check and becomes ID 12. The command can clone a different template than the caller selected. Require a positive integer string before the (int) cast.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` around lines 40 - 52, Update the
--id validation in the command before constructing RunTemplate: require the raw
option value to be a string containing only a positive integer, reject values
such as 12abc, zero, and negatives with the existing failure response, and only
then cast the validated value to int.


if (empty($source->getMyKey())) {
if ($format === 'json') {
$output->writeln(json_encode(['status' => 'error', 'message' => 'RunTemplate not found'], \JSON_PRETTY_PRINT));
} else {
$output->writeln('<error>RunTemplate not found: '.$id.'</error>');
}

return self::FAILURE;
}

$newName = $input->getOption('name') ?: $source->getRecordName().' '._('Clone');
$newId = $source->cloneAs($newName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify the resolved core dependency and inspect available clone-state handling.
rg -n -C 6 '"vitexsoftware/multiflexi-core"|cloneAs\s*\(|setActive\s*\(|\bactive\b' \
  . -g 'composer.json' -g 'composer.lock' -g '*.php' || true

Repository: VitexSoftware/multiflexi-cli

Length of output: 21125


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- CloneCommand.php ---'
cat -n src/Command/RunTemplate/CloneCommand.php

printf '%s\n' '--- locked dependency metadata ---'
sed -n '3568,3595p' composer.lock

printf '%s\n' '--- upstream cloneAs references at the locked commit ---'
commit='80eefa9362998179f5c68374ec5dc77fd605cc57'
curl --fail --silent --show-error \
  "https://api.github.com/repos/VitexSoftware/php-vitexsoftware-multiflexi-core/commits/${commit}" |
  jq -r '.sha, .html_url, (.files[]?.filename)' | head -80

curl --fail --silent --show-error \
  "https://api.github.com/repos/VitexSoftware/php-vitexsoftware-multiflexi-core/git/trees/${commit}?recursive=1" |
  jq -r '.tree[] | select(.path | endswith(".php")) | .path' |
  while IFS= read -r path; do
    content_url="https://raw.githubusercontent.com/VitexSoftware/php-vitexsoftware-multiflexi-core/${commit}/${path}"
    body="$(curl --fail --silent --show-error "$content_url" || true)"
    if printf '%s' "$body" | grep -qE 'cloneAs|class RunTemplate'; then
      printf '%s\n' "--- ${path} ---"
      printf '%s\n' "$body" | grep -n -C 12 -E 'cloneAs|class RunTemplate'
    fi
  done

Repository: VitexSoftware/multiflexi-cli

Length of output: 5049


🏁 Script executed:

#!/bin/bash
set -u

repo='VitexSoftware/php-vitexsoftware-multiflexi-core'
commit='80eefa9362998179f5c68374ec5dc77fd605cc57'

printf '%s\n' '--- exact commit API response ---'
curl -sS -w '\nHTTP %{http_code}\n' \
  "https://api.github.com/repos/${repo}/commits/${commit}" |
  jq '{sha, message: .commit.message, url: .html_url, status: .status, documentation_url, message_error: .message}'

printf '%s\n' '--- remote refs ---'
git ls-remote --heads --tags "https://github.com/${repo}.git" 2>&1 |
  grep -E 'refs/(heads/(main|master|dev-main)|tags/)' | tail -80

printf '%s\n' '--- current dev-main tree paths ---'
curl -sS --fail --show-error \
  "https://api.github.com/repos/${repo}/git/trees/dev-main?recursive=1" |
  jq -r '.tree[]?.path' | grep -E '\.php$' | head -200

printf '%s\n' '--- current dev-main cloneAs references ---'
curl -sS --fail --show-error \
  "https://api.github.com/repos/${repo}/search/code?q=cloneAs+repo:${repo}" |
  jq -r '.items[]?.html_url' 2>/dev/null || true

Repository: VitexSoftware/multiflexi-cli

Length of output: 878


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='VitexSoftware/php-vitexsoftware-multiflexi-core'
branch='main'

curl --fail --silent --show-error \
  "https://api.github.com/repos/${repo}/git/trees/${branch}?recursive=1" |
  jq -r '.tree[] | select(.type == "blob") | .path' |
  tee /tmp/multiflexi-core-php-paths.txt |
  grep -Ei 'run.?template|template|\.php$' |
  head -250

printf '%s\n' '--- candidate source excerpts ---'
while IFS= read -r path; do
  case "$path" in
    *[Rr]un*[Tt]emplate*.php|*runtemplate*.php|*RunTemplate*.php)
      url="https://raw.githubusercontent.com/${repo}/${branch}/${path}"
      body="$(curl --fail --silent --show-error "$url")"
      if printf '%s' "$body" | grep -qE 'cloneAs|class RunTemplate'; then
        printf '%s\n' "--- ${path} ---"
        printf '%s\n' "$body" | grep -n -C 20 -E 'cloneAs|class RunTemplate'
      fi
      ;;
  esac
done < /tmp/multiflexi-core-php-paths.txt

Repository: VitexSoftware/multiflexi-cli

Length of output: 204


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='VitexSoftware/php-vitexsoftware-multiflexi-core'
branch='main'
tree_file=/tmp/multiflexi-core-tree.json

curl --fail --silent --show-error \
  "https://api.github.com/repos/${repo}/git/trees/${branch}?recursive=1" > "$tree_file"

printf '%s\n' '--- candidate paths ---'
jq -r '.tree[] | select(.type == "blob") | .path' "$tree_file" |
  grep -Ei 'run.?template|template|\.php$' |
  head -250

printf '%s\n' '--- candidate source excerpts ---'
jq -r '.tree[] | select(.type == "blob") | .path' "$tree_file" |
  while IFS= read -r path; do
    case "$path" in
      *[Rr]un*[Tt]emplate*.php|*runtemplate*.php|*RunTemplate*.php)
        url="https://raw.githubusercontent.com/${repo}/${branch}/${path}"
        body="$(curl --fail --silent --show-error "$url")"
        if printf '%s' "$body" | grep -qE 'cloneAs|class RunTemplate'; then
          printf '%s\n' "--- ${path} ---"
          printf '%s\n' "$body" | grep -n -C 20 -E 'cloneAs|class RunTemplate'
        fi
        ;;
    esac
  done

Repository: VitexSoftware/multiflexi-cli

Length of output: 12259


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='VitexSoftware/php-vitexsoftware-multiflexi-core'
for ref in main v2.9.3; do
  file="/tmp/multiflexi-core-RunTemplate-${ref}.php"
  curl --fail --silent --show-error \
    "https://raw.githubusercontent.com/${repo}/${ref}/src/MultiFlexi/RunTemplate.php" > "$file"

  printf '%s\n' "--- ${ref}: clone-related methods and active assignments ---"
  grep -n -C 12 -Ei 'function[[:space:]]+[^ (]*clone|cloneAs|active|setDataValue|save' "$file" || true
done

printf '%s\n' '--- direct existence check ---'
for ref in main v2.9.3; do
  url="https://raw.githubusercontent.com/${repo}/${ref}/src/MultiFlexi/RunTemplate.php"
  if curl --fail --silent --show-error "$url" | grep -qE 'function[[:space:]]+cloneAs[[:space:]]*\('; then
    printf '%s: cloneAs present\n' "$ref"
  else
    printf '%s: cloneAs absent\n' "$ref"
  fi
done

Repository: VitexSoftware/multiflexi-cli

Length of output: 15773


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='VitexSoftware/php-vitexsoftware-multiflexi-core'
ref='main'
tree_file=/tmp/multiflexi-core-tree.json

printf '%s\n' '--- DBEngine clone-related methods ---'
curl --fail --silent --show-error \
  "https://raw.githubusercontent.com/${repo}/${ref}/src/MultiFlexi/DBEngine.php" |
  grep -n -C 20 -Ei 'function[[:space:]]+[^ (]*clone|cloneAs|clone|active|setDataValue' || true

printf '%s\n' '--- all upstream source references to cloneAs ---'
jq -r '.tree[] | select(.type == "blob" and (.path | startswith("src/")) and (.path | endswith(".php"))) | .path' "$tree_file" |
  while IFS= read -r path; do
    url="https://raw.githubusercontent.com/${repo}/${ref}/${path}"
    body="$(curl --fail --silent --show-error "$url")"
    if printf '%s' "$body" | grep -qEi 'cloneAs'; then
      printf '%s\n' "--- ${path} ---"
      printf '%s\n' "$body" | grep -n -C 15 -Ei 'cloneAs'
    fi
  done

Repository: VitexSoftware/multiflexi-cli

Length of output: 255


Align the multiflexi-core dependency before calling cloneAs(). composer.lock references nonexistent commit 80eefa9362998179f5c68374ec5dc77fd605cc57, and available upstream source contains no cloneAs() implementation. Pin a reachable revision that provides the API, or use a supported cloning API. Add an integration test for the inactive state and documented failure behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Command/RunTemplate/CloneCommand.php` at line 65, Before the cloneAs call
in the RunTemplate clone flow, align the multiflexi-core dependency to a
reachable revision that provides cloneAs, or replace it with the supported
cloning API. Update the lockfile accordingly, then add an integration test
covering the inactive state and the documented failure behavior.

Source: Coding guidelines


if (!$newId) {
if ($format === 'json') {
$output->writeln(json_encode(['status' => 'error', 'message' => 'Failed to create clone'], \JSON_PRETTY_PRINT));
} else {
$output->writeln('<error>Failed to create clone</error>');
}

return self::FAILURE;
}

if ($format === 'json') {
$output->writeln(json_encode(['runtemplate_id' => $newId, 'name' => $newName, 'active' => false], \JSON_PRETTY_PRINT));
} else {
$output->writeln("RunTemplate cloned as ID: {$newId} (disabled — review and enable when ready)");
}

return self::SUCCESS;
}
}
2 changes: 2 additions & 0 deletions src/multiflexi-cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
use MultiFlexi\Cli\Command\Queue\OverviewCommand as QueueOverviewCommand;
use MultiFlexi\Cli\Command\Queue\TruncateCommand as QueueTruncateCommand;
use MultiFlexi\Cli\Command\RunTemplate\AssignCredentialCommand as RunTemplateAssignCredentialCommand;
use MultiFlexi\Cli\Command\RunTemplate\CloneCommand as RunTemplateCloneCommand;
use MultiFlexi\Cli\Command\RunTemplate\CreateCommand as RunTemplateCreateCommand;
use MultiFlexi\Cli\Command\RunTemplate\DeleteCommand as RunTemplateDeleteCommand;
use MultiFlexi\Cli\Command\RunTemplate\GetCommand as RunTemplateGetCommand;
Expand Down Expand Up @@ -254,6 +255,7 @@
ConsoleCompat::addCommand($application, new RunTemplateListCommand());
ConsoleCompat::addCommand($application, new RunTemplateGetCommand());
ConsoleCompat::addCommand($application, new RunTemplateCreateCommand());
ConsoleCompat::addCommand($application, new RunTemplateCloneCommand());
ConsoleCompat::addCommand($application, new RunTemplateUpdateCommand());
ConsoleCompat::addCommand($application, new RunTemplateDeleteCommand());
ConsoleCompat::addCommand($application, new RunTemplateScheduleCommand());
Expand Down
Loading