Skip to content

refactor: process service replacement - #38

Merged
loadinglucian merged 7 commits into
mainfrom
refactor/process-service-replacement
Oct 11, 2025
Merged

refactor: process service replacement#38
loadinglucian merged 7 commits into
mainfrom
refactor/process-service-replacement

Conversation

@loadinglucian

@loadinglucian loadinglucian commented Oct 11, 2025

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added site management: create, find, list, and delete sites persisted in inventory.
    • Introduced an immutable site data model for consistent configuration.
    • Commands now auto-load site inventory during initialization.
  • Refactor

    • Unified process handling into a single service for running external commands.
    • Version detection and related flows now use the unified process runner.
  • Tests

    • Added unit tests for site DTO and repository, including edge cases.
    • Updated tests to use the new process runner and updated command initialization.

Replace the ProcessFactory class with ProcessService to provide a more standardized service layer implementation with dependency injection support and better testability. The new service encapsulates process execution logic while maintaining the same interface compatibility.
Introduce SiteDTO for site configuration data and SiteRepository for inventory management. This establishes foundational components for site-related functionality, following the same pattern as server management.
Update BaseCommand with improved console output and input methods. Adjust test fixtures and helpers to maintain compatibility and enhance test isolation for command testing.
Remove the deprecated ProcessFactory class and corresponding test file following successful replacement with ProcessService.
Apply small fixes and improvements to the VersionService implementation for better reliability and consistency with service layer patterns.
@coderabbitai

coderabbitai Bot commented Oct 11, 2025

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a readonly SiteDTO and an inventory-backed SiteRepository; introduces ProcessService and replaces ProcessFactory usage; BaseCommand now injects ProcessService and SiteRepository and loads site inventory during initialization; tests and test helpers updated to match new DI and services.

Changes

Cohort / File(s) Summary
BaseCommand wiring
app/Contracts/BaseCommand.php, tests/Fixtures/TestConsoleCommand.php, tests/Unit/Contracts/BaseCommandTest.php
Constructor updated to accept ProcessService $proc and SiteRepository $sites; BaseCommand::initialize now loads sites inventory via sites->loadInventory($this->inventory). Test fixtures and unit tests updated to new constructor/signature and initialization flow.
Process execution service
app/Services/ProcessService.php, app/Services/VersionService.php, tests/Unit/Services/ProcessServiceTest.php, tests/TestHelpers.php
New ProcessService added; code switched from ProcessFactory->create(...)->run() to ProcessService->run(...). VersionService now depends on ProcessService. Tests and mocks updated (mockProcessService, call sites, and assertions).
Site management
app/DTOs/SiteDTO.php, app/Repositories/SiteRepository.php, tests/Unit/DTOs/SiteDTOTest.php, tests/Unit/Repositories/SiteRepositoryTest.php
Adds readonly SiteDTO and SiteRepository with loadInventory, create, findByDomain, all, delete, plus dehydrate/hydrate helpers and inventory persistence. Unit tests added for DTO and repository behaviors and edge cases.
Test infrastructure updates
tests/TestHelpers.php, tests/Fixtures/TestConsoleCommand.php
Test helpers renamed mockProcessFactorymockProcessService, added mockSiteRepository, and updated mockCommandContainer to accept and bind ProcessService and SiteRepository. Test fixture constructors and parent calls adjusted for new DI order.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant CLI as Console Command
  participant Base as BaseCommand
  participant Inv as InventoryService
  participant Sites as SiteRepository
  participant Servers as ServerRepository

  User->>CLI: invoke command
  CLI->>Base: __construct(...)
  activate Base
  Base->>Sites: loadInventory(Inv)
  Sites->>Inv: get("sites")
  Inv-->>Sites: array|null
  Sites-->>Base: initialized
  Base->>Servers: loadInventory(Inv)
  Servers-->>Base: initialized
  deactivate Base
  Note right of CLI: Repositories populated from inventory before command runs
Loading
sequenceDiagram
  autonumber
  participant Ver as VersionService
  participant Proc as ProcessService
  participant OS as System Process

  Ver->>Proc: run(["git","describe","--tags"], cwd, timeout)
  activate Proc
  Proc->>OS: start and run command
  OS-->>Proc: exit code + output
  Proc-->>Ver: completed Process
  Ver->>Ver: check isSuccessful(), parse output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • bigpixelrocket/deployer-php#27 — Modifies BaseCommand::__construct; directly related to constructor signature and DI changes.
  • bigpixelrocket/deployer-php#17 — Earlier changes to BaseCommand—relevant to initialization and constructor evolution.
  • bigpixelrocket/deployer-php#22 — Refactors process-related handling; related to replacing ProcessFactory with ProcessService.

Poem

I nibble bytes and hop through code,
A DTO neat, a repo stowed.
Processes run, no factory sigh—
Inventory loads, the sites reply.
I thump with joy, carrot held high 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately and concisely reflects the core refactoring of the process service replacement and refers to a real part of the changeset, making it clear that the PR is focused on updating the ProcessFactory to ProcessService across the codebase.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89bfb8a and c8aface.

📒 Files selected for processing (7)
  • app/Contracts/BaseCommand.php (5 hunks)
  • app/DTOs/SiteDTO.php (1 hunks)
  • app/Repositories/SiteRepository.php (1 hunks)
  • app/Services/ProcessService.php (1 hunks)
  • app/Services/VersionService.php (5 hunks)
  • tests/Fixtures/TestConsoleCommand.php (3 hunks)
  • tests/TestHelpers.php (7 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 0

🧹 Nitpick comments (2)
app/Services/ProcessService.php (1)

24-36: Consider simplifying the timeout handling.

The null-coalescing operator on line 35 is redundant since the parameter already has a default value of 3.0. The ?? 3.0 will only apply when null is explicitly passed, which is unlikely given the default.

Consider this simpler approach:

-    public function run(array $command, string $cwd, ?float $timeout = 3.0): Process
+    public function run(array $command, string $cwd, float|null $timeout = 3.0): Process
     {
         if ($command === []) {
             throw new \InvalidArgumentException('Process command cannot be empty');
         }
 
         if (!$this->fs->isDirectory($cwd)) {
             throw new \InvalidArgumentException("Invalid working directory: {$cwd}");
         }
 
         $process = new Process($command, $cwd);
-        $process->setTimeout($timeout ?? 3.0);
+        $process->setTimeout($timeout);
         $process->run();
 
         return $process;
     }
app/Contracts/BaseCommand.php (1)

109-114: Consider lazy loading for site inventory.

The site inventory is now loaded eagerly for all commands, even those that may not need it. Since SiteRepository already follows the stateful service pattern with explicit loadInventory(), consider deferring this initialization to commands that actually use site data.

However, this matches the pattern used for ServerRepository (line 112), so consistency may be more valuable than the minor performance gain from lazy loading.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a56f08 and e84dd00.

📒 Files selected for processing (11)
  • app/Contracts/BaseCommand.php (3 hunks)
  • app/DTOs/SiteDTO.php (1 hunks)
  • app/Repositories/SiteRepository.php (1 hunks)
  • app/Services/ProcessService.php (2 hunks)
  • app/Services/VersionService.php (4 hunks)
  • tests/Fixtures/TestConsoleCommand.php (2 hunks)
  • tests/TestHelpers.php (6 hunks)
  • tests/Unit/Contracts/BaseCommandTest.php (5 hunks)
  • tests/Unit/DTOs/SiteDTOTest.php (1 hunks)
  • tests/Unit/Repositories/SiteRepositoryTest.php (1 hunks)
  • tests/Unit/Services/ProcessServiceTest.php (3 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

**/*.php: Eliminate single-use private methods by inlining them directly
Cache expensive computed values by initializing them in the constructor instead of recomputing
Prefer direct property access over method calls when appropriate to avoid call overhead
Organize code into comment-separated sections; prefer alphabetical ordering when it does not conflict with logical grouping

**/*.php: Adhere to PSR-12 coding style in all PHP files
Declare strict_types=1 at the top of every PHP file
Prefer PHP 8.x features (union types, match, attributes, readonly) where appropriate
Always import classes with use statements; avoid fully qualified class names in code bodies
All methods must declare explicit return types; use proper generics in types/docblocks (e.g., Collection<int, User>)
Use dependency injection instead of manually resolving or instantiating classes
Prefer Symfony component classes (e.g., Filesystem, Process) over native PHP functions for testability
All object creation must use $container->build(ClassName::class) instead of new, except for value objects/DTOs/pure data structures
In production code, access the Container via constructor injection, not via static/global access
Add minimal DocBlock comments with descriptions, parameters, and return types for classes and functions
Use comments as visual separators for sections/subsections with a single newline between header, subheader, and paragraph; avoid obvious or stale comments
Run rector on changed PHP files before completing a task
Run pint on changed PHP files to fix code style before completing a task

Files:

  • app/DTOs/SiteDTO.php
  • tests/Unit/Repositories/SiteRepositoryTest.php
  • tests/Unit/Services/ProcessServiceTest.php
  • app/Repositories/SiteRepository.php
  • tests/Fixtures/TestConsoleCommand.php
  • tests/Unit/Contracts/BaseCommandTest.php
  • app/Contracts/BaseCommand.php
  • app/Services/VersionService.php
  • app/Services/ProcessService.php
  • tests/Unit/DTOs/SiteDTOTest.php
  • tests/TestHelpers.php
{tests/**,test/**,**/*@(Test|Spec).php}

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

Do not run or edit tests unless explicitly instructed

Files:

  • tests/Unit/Repositories/SiteRepositoryTest.php
  • tests/Unit/Services/ProcessServiceTest.php
  • tests/Fixtures/TestConsoleCommand.php
  • tests/Unit/Contracts/BaseCommandTest.php
  • tests/Unit/DTOs/SiteDTOTest.php
  • tests/TestHelpers.php
tests/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

tests/**/*.php: In tests, direct Container instantiation and bind() for mocks is allowed and encouraged for isolation
Do not run PHPStan on test files; tests are excluded from static analysis

tests/**/*.php: Unit tests must instantiate services manually (no DI container)
Command/integration tests must use mockCommandContainer() for building commands and overriding services
Only use container auto-wiring in tests to verify DI configuration or multi-service integration (edge cases)
Keep test files under 1.8x the size of the source they test (without sacrificing readability)
Test core business logic; avoid testing the framework itself
Prefer dataset-driven testing using ->with([...]) for multiple scenarios
Consolidate related assertions (e.g., expect($x)->toBe(...)->and($y)->toBe(...))
Mock only external dependencies; keep unit tests isolated from filesystem/HTTP/processes
Avoid performance tests unless performance is the primary concern
Use the AAA pattern in tests (Arrange, Act, Assert; optional Cleanup)
In exception tests, use a combined // ACT & ASSERT step when the act triggers the assertion
Organize tests with describe() blocks, beforeEach() setup, and shared helpers/traits for DRY
Forbidden assertions in tests: type-only or generic checks (e.g., toBeInstanceOf, toBeArray, not->toBeNull, expect(true)->toBeTrue) and sleep(...); prefer time mocking
Preferred assertions: assert observable behavior and interactions (e.g., domain values, validator outcomes, mock expectations)
Unit tests: mock all external dependencies, test single units in isolation, and complete in milliseconds
Integration tests: use real file operations and external processes; cover CLI commands and full workflows
Do not require PHPStan compliance in tests; avoid excessive phpdoc solely to satisfy types in tests

Files:

  • tests/Unit/Repositories/SiteRepositoryTest.php
  • tests/Unit/Services/ProcessServiceTest.php
  • tests/Fixtures/TestConsoleCommand.php
  • tests/Unit/Contracts/BaseCommandTest.php
  • tests/Unit/DTOs/SiteDTOTest.php
  • tests/TestHelpers.php
**/*Command.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

**/*Command.php: Commands handle user interaction (input/output) and orchestrate services
Commands must not contain business logic; delegate business logic to Services
Commands must not duplicate orchestration logic; extract shared orchestration to Services
Commands should not invoke other commands (no proxy commands)
Use SymfonyStyle consistently for all user-facing console output

Files:

  • tests/Fixtures/TestConsoleCommand.php
  • app/Contracts/BaseCommand.php
app/{Contracts/BaseCommand.php,Traits/ConsoleOutputTrait.php,Traits/ConsoleInputTrait.php}

📄 CodeRabbit inference engine (.cursor/rules/03-commands.mdc)

If output functionality is missing, add a new method to BaseCommand (and the appropriate Console*Trait) with modern styling and documentation

Files:

  • app/Contracts/BaseCommand.php
app/Contracts/BaseCommand.php

📄 CodeRabbit inference engine (.cursor/rules/03-commands.mdc)

BaseCommand should contain only shared initialization/configuration/orchestration logic; do not put individual I/O operations here

Files:

  • app/Contracts/BaseCommand.php
**/*Service.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

**/*Service.php: Services provide atomic, reusable functionality with no console I/O
Services accept and return plain PHP data types (no console I/O)
Services must be dependency-injected via constructor; declare dependencies in constructor signatures
Extract complex orchestration shared by multiple Commands into dedicated Services
Stateful services should use lazy loading when initialization is expensive or path-dependent
State must be initialized explicitly via public methods (e.g., load(), initialize()) before use
Services should document their stateful nature and initialization requirements

Files:

  • app/Services/VersionService.php
  • app/Services/ProcessService.php
app/Services/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/03-commands.mdc)

Services must not perform any console I/O; they should return plain data only

Files:

  • app/Services/VersionService.php
  • app/Services/ProcessService.php
tests/TestHelpers.php

📄 CodeRabbit inference engine (.cursor/rules/02-tests.mdc)

When BaseCommand gains a new service, update mockCommandContainer() in tests/TestHelpers.php by adding a parameter, constructing/binding it, and wiring it to the container

Files:

  • tests/TestHelpers.php
🧠 Learnings (2)
📚 Learning: 2025-10-10T11:10:25.074Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-10T11:10:25.074Z
Learning: Applies to tests/TestHelpers.php : When BaseCommand gains a new service, update mockCommandContainer() in tests/TestHelpers.php by adding a parameter, constructing/binding it, and wiring it to the container

Applied to files:

  • tests/Fixtures/TestConsoleCommand.php
  • tests/Unit/Contracts/BaseCommandTest.php
  • tests/TestHelpers.php
📚 Learning: 2025-10-10T11:10:25.074Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-10T11:10:25.074Z
Learning: Applies to tests/**/*.php : Command/integration tests must use mockCommandContainer() for building commands and overriding services

Applied to files:

  • tests/Unit/Contracts/BaseCommandTest.php
  • tests/TestHelpers.php
🧬 Code graph analysis (10)
tests/Unit/Repositories/SiteRepositoryTest.php (4)
app/DTOs/SiteDTO.php (1)
  • SiteDTO (7-19)
app/Repositories/SiteRepository.php (6)
  • SiteRepository (15-165)
  • all (83-93)
  • loadInventory (31-43)
  • create (48-60)
  • findByDomain (65-76)
  • delete (98-112)
tests/TestHelpers.php (1)
  • mockInventoryService (156-172)
app/Services/InventoryService.php (1)
  • loadInventoryFile (97-111)
tests/Unit/Services/ProcessServiceTest.php (2)
tests/TestHelpers.php (1)
  • mockProcessService (186-190)
app/Services/ProcessService.php (1)
  • run (24-39)
app/Repositories/SiteRepository.php (2)
app/DTOs/SiteDTO.php (1)
  • SiteDTO (7-19)
app/Services/InventoryService.php (2)
  • InventoryService (33-264)
  • set (54-60)
tests/Fixtures/TestConsoleCommand.php (3)
app/Repositories/SiteRepository.php (1)
  • SiteRepository (15-165)
app/Services/ProcessService.php (1)
  • ProcessService (12-40)
app/Contracts/BaseCommand.php (1)
  • __construct (38-54)
tests/Unit/Contracts/BaseCommandTest.php (4)
app/Repositories/SiteRepository.php (2)
  • SiteRepository (15-165)
  • findByDomain (65-76)
tests/TestHelpers.php (2)
  • mockCommandContainer (410-449)
  • mockInventoryService (156-172)
app/Container.php (1)
  • build (59-85)
app/Repositories/ServerRepository.php (2)
  • ServerRepository (15-168)
  • findByName (65-76)
app/Contracts/BaseCommand.php (2)
app/Repositories/SiteRepository.php (1)
  • SiteRepository (15-165)
app/Services/ProcessService.php (1)
  • ProcessService (12-40)
app/Services/VersionService.php (1)
app/Services/ProcessService.php (2)
  • ProcessService (12-40)
  • run (24-39)
app/Services/ProcessService.php (1)
app/Services/FilesystemService.php (1)
  • FilesystemService (27-106)
tests/Unit/DTOs/SiteDTOTest.php (1)
app/DTOs/SiteDTO.php (1)
  • SiteDTO (7-19)
tests/TestHelpers.php (4)
app/Repositories/SiteRepository.php (1)
  • SiteRepository (15-165)
app/Services/InventoryService.php (2)
  • InventoryService (33-264)
  • loadInventoryFile (97-111)
app/Services/ProcessService.php (1)
  • ProcessService (12-40)
app/Container.php (2)
  • Container (23-250)
  • bind (46-50)
🔇 Additional comments (16)
app/DTOs/SiteDTO.php (1)

7-19: LGTM!

The readonly DTO follows PHP 8.x best practices with constructor property promotion and proper type declarations. The implementation is clean and appropriate for an immutable value object.

app/Services/VersionService.php (4)

21-21: LGTM!

The dependency injection change from ProcessFactory to ProcessService aligns with the broader refactor and maintains proper service isolation.


117-127: LGTM!

The migration to ProcessService::run() is clean and maintains the same error handling logic. The method correctly checks isSuccessful() on the returned process.


135-145: LGTM!

Consistent migration to the new ProcessService::run() API with proper error handling.


153-166: LGTM!

The refactor correctly uses ProcessService::run() for both git commands and combines their outputs when both succeed. The error handling is appropriate.

tests/Unit/DTOs/SiteDTOTest.php (1)

7-23: LGTM!

The test properly validates SiteDTO construction with named parameters and verifies all properties using chained Pest assertions. Good coverage for a simple value object.

tests/Unit/Services/ProcessServiceTest.php (3)

14-32: LGTM!

The test correctly validates the new run() method behavior, including the important addition of the isSuccessful() assertion. The parameterized timeout scenarios provide good coverage.


34-41: LGTM!

Exception test properly updated to use the new run() API while preserving validation logic.


43-56: LGTM!

The parameterized test for invalid working directories covers multiple edge cases and correctly validates exception behavior.

tests/Unit/Repositories/SiteRepositoryTest.php (5)

15-22: LGTM!

Clear test ensuring operations fail gracefully when inventory is not loaded. This validates the repository's state management requirements.


28-67: LGTM!

Comprehensive CRUD lifecycle test that validates create, find, list, and delete operations with proper assertions at each step. The test also verifies edge cases like finding non-existent domains and deleting non-existent sites.


69-81: LGTM!

Proper validation of duplicate domain prevention, ensuring data integrity in the repository.


87-128: LGTM!

Excellent parameterized test covering various malformed data scenarios. The test validates that hydration handles missing fields, wrong types, and mixed arrays gracefully with sensible defaults.


134-145: LGTM!

Good edge case coverage for initialization when the sites key is missing from inventory.

tests/Fixtures/TestConsoleCommand.php (1)

34-45: LGTM!

The constructor correctly reflects BaseCommand's updated signature with the new ProcessService and SiteRepository dependencies. Parameter ordering aligns with the parent class.

app/Contracts/BaseCommand.php (1)

38-54: LGTM!

The constructor is well-organized into logical sections (Framework, Base services, Servers & sites). The new ProcessService and SiteRepository dependencies are properly injected.

- Changed timeout parameter from ?float to float in ProcessService::run()
- Removed null coalescing operator since parameter cannot be null
- Fixed test to omit parameter instead of passing null when testing default behavior
- All existing usages already rely on default value without passing null
@loadinglucian

Copy link
Copy Markdown
Owner Author

@coderabbitai generate docstrings

@coderabbitai

coderabbitai Bot commented Oct 11, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@coderabbitai

coderabbitai Bot commented Oct 11, 2025

Copy link
Copy Markdown
Contributor

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at https://github.com/bigpixelrocket/deployer-php/pull/39

coderabbitai Bot referenced this pull request Oct 11, 2025
Docstrings generation was requested by @lucianvacaroiu.

* https://github.com/bigpixelrocket/deployer-php/pull/38#issuecomment-3393662882

The following files were modified:

* `app/Contracts/BaseCommand.php`
* `app/DTOs/SiteDTO.php`
* `app/Repositories/SiteRepository.php`
* `app/Services/ProcessService.php`
* `app/Services/VersionService.php`
* `tests/Fixtures/TestConsoleCommand.php`
* `tests/TestHelpers.php`
Docstrings generation was requested by @lucianvacaroiu.

* https://github.com/bigpixelrocket/deployer-php/pull/38#issuecomment-3393662882

The following files were modified:

* `app/Contracts/BaseCommand.php`
* `app/DTOs/SiteDTO.php`
* `app/Repositories/SiteRepository.php`
* `app/Services/ProcessService.php`
* `app/Services/VersionService.php`
* `tests/Fixtures/TestConsoleCommand.php`
* `tests/TestHelpers.php`

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@loadinglucian
loadinglucian merged commit cfa11e6 into main Oct 11, 2025
3 of 5 checks passed
@loadinglucian
loadinglucian deleted the refactor/process-service-replacement branch October 11, 2025 21:22
@coderabbitai coderabbitai Bot mentioned this pull request Oct 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant