Refactor Safely
Refactoring means changing the structure of code without changing its behavior. AI makes refactoring fast but introduces one new risk: it may accidentally change behavior while restructuring. Prevent this with two habits:
Step 1 - Write tests FIRST (if none exist):
> Read UserModel::create(). Write PHPUnit tests for the current behavior
(what inputs produce what outputs) BEFORE I refactor anything.
Step 2 - Refactor:
> Now refactor create() to extract validation to a private validateData() method.
The observable behavior (what create() returns for any input) must not change.
Step 3 - Verify:
> Run the tests. Did they all pass? If not, what changed?
"Do not change the behavior" should appear in every refactoring prompt.
Extract and Split Patterns
> Read UserModel::create(). It is 80 lines long.
Extract the validation logic (lines 12-35) to a private method validateUserData(array $data): array
that returns ['valid' => bool, 'errors' => array].
create() should call validateUserData() and use its result.
Do not change the behavior of create() from the caller's perspective.
> Read AuthController.php. The processLogin() method handles:
validation, user lookup, password verify, session creation, remember_me, logging.
That is too many responsibilities.
Extract to app/services/AuthService.php:
- authenticate(string $email, string $password): ?array (returns user or null)
- startSession(array $user, bool $remember): void
- logout(): void
Update AuthController to use AuthService.
AuthController should have no direct PDO or session_start() calls after refactor.
> Read functions.php. It has 400 lines with unrelated utilities mixed together.
Group the functions by category and split into separate files:
- app/helpers/StringHelper.php (string manipulation functions)
- app/helpers/DateHelper.php (date/time functions)
- app/helpers/FileHelper.php (file/image functions)
- app/helpers/ValidationHelper.php (form validation functions)
Create an autoloader or require-all file so existing code that uses
these functions does not need to change its requires.
Apply Design Patterns
> Read UserModel.php. Introduce the Repository pattern:
- Create an interface: app/repositories/UserRepositoryInterface.php
with methods: findById, findByEmail, create, update, delete
- Create: app/repositories/UserRepository.php implementing the interface (PDO implementation)
- Keep UserModel as a simple data object (no database code)
This allows swapping the database implementation without changing business logic.
Show me the refactored files and how to update the DI in controllers.
> Read PaymentController.php. It has a giant if/elseif block for
Razorpay, Stripe, and PayPal with 100+ lines each.
Apply the Strategy pattern:
- Interface: app/payments/PaymentGatewayInterface.php
with methods: createOrder(), verifyPayment(), refund()
- Implement: app/payments/RazorpayGateway.php, StripeGateway.php, PayPalGateway.php
- PaymentController uses $this->gateway->createOrder() only
- Gateway is injected via constructor based on config setting
Clean Legacy Code
# Remove global variables:
> Read the codebase. Find all uses of global $db, global $config.
Replace with dependency injection (pass PDO/config as constructor parameter).
Update all classes and the entry point file.
# Fix procedural to OOP:
> Read /pages/products.php. It is 200 lines of procedural PHP
(no classes, direct $_GET/$_POST, inline SQL).
Refactor to: ProductController class + ProductModel class + view template.
Keep the same URL and same database structure.
# Remove copy-paste duplicates:
> I have UserController, ProductController, and OrderController.
Each has nearly identical pagination code (80 lines).
Extract it to a PaginationTrait that all three can use.
# Remove dead code:
> Read all PHP files. Find functions and methods that are never called.
List them (do not delete yet - I will review the list first).
Modernize PHP Code
# Add type hints:
> Read app/models/UserModel.php. Add PHP 8.2 type declarations to all
method parameters and return types. Use union types, nullable types,
and built-in types (int, string, array, bool). Add 'strict_types=1'.
# Use named arguments:
> Read all files that call the UserModel constructor and DB methods.
Replace positional arguments with named arguments where it improves clarity.
Example: findUsers(limit: 20, offset: 0) instead of findUsers(20, 0)
# Use match expressions:
> Read any switch/case statements in the codebase.
Replace them with PHP 8 match expressions where appropriate.
List files changed.
# Use enums:
> The database has ENUM columns: status ('draft','published','archived'),
priority ('low','medium','high','urgent').
Create PHP 8.1 enums for these in app/enums/ and update models
to use them instead of string comparisons.
# Use constructor property promotion:
> Read all model classes that have a __construct that assigns parameters to properties.
Use PHP 8.0 constructor property promotion to shorten them.
Example: public function __construct(private PDO $pdo) {}