Add run-template:clone command - #56
Conversation
Mirrors the web UI's clone action: copies a run template's config, saved env values, and credential assignments into a new run template via RunTemplate::cloneAs() (core). The clone is always created disabled, so a copied config can't be picked up by the scheduler before it's reviewed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds the ChangesRun template cloning
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant SymfonyConsoleApplication
participant CloneCommand
participant RunTemplate
SymfonyConsoleApplication->>CloneCommand: Execute run-template:clone
CloneCommand->>RunTemplate: Validate source and invoke cloneAs
RunTemplate-->>CloneCommand: Return cloned template
CloneCommand-->>SymfonyConsoleApplication: Output ID and disabled status
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with 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.
Inline comments:
In `@src/Command/RunTemplate/CloneCommand.php`:
- Around line 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.
- Around line 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.”
- Around line 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.
- 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.
- 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.
- 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.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 88194867-20fa-4e63-bc1a-c986871f7b9e
📒 Files selected for processing (2)
src/Command/RunTemplate/CloneCommand.phpsrc/multiflexi-cli.php
| use Symfony\Component\Console\Input\InputOption; | ||
| use Symfony\Component\Console\Output\OutputInterface; | ||
|
|
||
| class CloneCommand extends BaseCommand |
There was a problem hiding this comment.
🎯 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
| class CloneCommand extends BaseCommand | ||
| { | ||
| 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")'); | ||
| } | ||
|
|
||
| protected function execute(InputInterface $input, OutputInterface $output): int |
There was a problem hiding this comment.
📐 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
| ->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")'); |
There was a problem hiding this comment.
📐 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 | ||
| { | ||
| $format = strtolower($input->getOption('format')); |
There was a problem hiding this comment.
🎯 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); |
There was a problem hiding this comment.
🗄️ 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.
| } | ||
|
|
||
| $newName = $input->getOption('name') ?: $source->getRecordName().' '._('Clone'); | ||
| $newId = $source->cloneAs($newName); |
There was a problem hiding this comment.
🗄️ 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' || trueRepository: 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
doneRepository: 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 || trueRepository: 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.txtRepository: 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
doneRepository: 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
doneRepository: 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
doneRepository: 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
Covers the new CLI command (VitexSoftware/multiflexi-cli#56) which mirrors the web UI's clone action and always creates the clone disabled. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Summary
run-template:clone --id=<id> [--name=<name>] [--format=text|json]command, mirroring the web UI's clone action.RunTemplate::cloneAs()(core) which copies config, saved env values, and credential assignments into a new run template and always creates it disabled (active=false), regardless of the source template's state — a copied config can't be picked up by the scheduler before it's reviewed.multiflexi-doc-en(source/reference/cli.rst): command list, options note, and a usage example.Depends on: VitexSoftware/php-vitexsoftware-multiflexi-core#53 (adds
cloneAs()) — needs that merged/released first.Companion fix for the same underlying bug in the web UI: VitexSoftware/multiflexi-web#7, VitexSoftware/multiflexi-web5#4.
Test plan
php -lon changed/new filesmultiflexi-cli run-template:clone --id=<id>, confirm printed new ID and that the row hasactive=0Summary by CodeRabbit