-
Notifications
You must be signed in to change notification settings - Fork 32
Open
Labels
Description
Chainable classes add a lot to the Developer experience, as they give a good visual indication of what happens without needing too much text. The best of all : they can be implemented with backward-compatibility in mind.
Based on the ImpressCMS codebase structure, here are the specific modifications needed to implement fluent interfaces:
1. Specific Class Categories for Method Chaining
High Priority Categories:
- Database Criteria Builders (
icms/db/criteria/) - IPF Handlers (
icms/ipf/Handler.php) - Form Builders (
icms/form/) - Configuration Classes (
icms/config/)
Medium Priority:
- Event System (
icms/Event/) - Cache Classes (
icms/cache/) - Template System (
icms/template/)
2. Concrete Code Examples
Database Criteria System
Current Implementation (breaks chaining):
class icms_db_criteria_Compo extends icms_db_criteria_Element {
public function add($element, $condition = 'AND') {
$this->criteriaElements[] = $element;
$this->conditions[] = $condition;
// Returns nothing - breaks chaining
}
}Refactored Version (enables chaining):
public function add($element, $condition = 'AND'): self {
$this->criteriaElements[] = $element;
$this->conditions[] = $condition;
return $this;
}
public function addItem(string $column, $value = '', string $operator = '='): self {
return $this->add(new icms_db_criteria_Item($column, $value, $operator));
}
public function where(string $column, $value, string $operator = '='): self {
return $this->addItem($column, $value, $operator);
}
public function orWhere(string $column, $value, string $operator = '='): self {
return $this->add(new icms_db_criteria_Item($column, $value, $operator), 'OR');
}Usage Example:
$criteria = (new icms_db_criteria_Compo())
->where('status', 1)
->where('type', 'news')
->orWhere('featured', 1)
->setLimit(10)
->setSort('created_date', 'DESC');IPF Handler Modifications
Current Implementation:
public function updateAll($fieldname, $fieldvalue, $criteria = null, $force = false) {
// ... implementation ...
return $result; // Returns boolean - breaks chaining
}Refactored Version:
public function updateAll($fieldname, $fieldvalue, $criteria = null, $force = false): bool {
// ... existing implementation ...
return $result;
}
public function setField(string $fieldname, $fieldvalue): self {
$this->pendingUpdates[$fieldname] = $fieldvalue;
return $this;
}
public function whereField(string $column, $value, string $operator = '='): self {
if (!$this->updateCriteria) {
$this->updateCriteria = new icms_db_criteria_Compo();
}
$this->updateCriteria->add(new icms_db_criteria_Item($column, $value, $operator));
return $this;
}
public function execute(): bool {
foreach ($this->pendingUpdates as $field => $value) {
$result = $this->updateAll($field, $value, $this->updateCriteria);
if (!$result) return false;
}
$this->pendingUpdates = [];
$this->updateCriteria = null;
return true;
}3. Technical Rationale
Why Current Implementation Prevents Chaining:
- Methods return
voidor primitive types (bool,int) - No consistent return of
$thisor class instance - Methods perform immediate actions rather than building state
PHP Language Features Enabling Chaining:
- Return Type Declarations:
public function method(): self - Self Return:
return $this;enables continued chaining - Method Visibility: Public methods can be chained externally
Backward Compatibility Strategy:
abstract class icms_db_criteria_Element {
// Add fluent methods while keeping existing ones
public function setSort(string $sort, string $order = 'ASC'): self {
$this->sort = $sort;
$this->order = $order;
return $this;
}
public function setLimit(int $limit, int $start = 0): self {
$this->limit = $limit;
$this->start = $start;
return $this;
}
public function setGroupBy(string $groupby): self {
$this->groupby = $groupby;
return $this;
}
// Keep existing methods for BC
public function getSort(): string { return $this->sort; }
public function getOrder(): string { return $this->order; }
public function getLimit(): int { return $this->limit; }
}4. Implementation Priority
Phase 1 (Immediate Impact):
icms_db_criteria_Compo- Most used for database queriesicms_db_criteria_Element- Base class affects all criteriaicms_db_criteria_Item- Individual conditions
Phase 2 (Handler Integration):
icms_ipf_Handler- Core handler functionality- Form builders in
icms/form/directory - Configuration classes
Phase 3 (Advanced Features):
- Event system method chaining
- Template assignment chaining
- Cache operation chaining
5. Potential Breaking Changes & Mitigation
Breaking Changes:
- Methods that previously returned
voidnow returnself - Some method signatures change (adding return types)
Mitigation Strategies:
class icms_db_criteria_Compo extends icms_db_criteria_Element {
// Maintain BC with original method
public function add($element, $condition = 'AND') {
$this->criteriaElements[] = $element;
$this->conditions[] = $condition;
return $this; // Now returns self instead of void
}
// Add alias for clarity
public function addCriteria($element, $condition = 'AND'): self {
return $this->add($element, $condition);
}
}Testing Strategy:
- Unit tests for each modified method
- Integration tests for chained operations
- Backward compatibility tests ensuring existing code works
Example Complete Implementation:
<?php
class icms_db_criteria_QueryBuilder extends icms_db_criteria_Compo {
public function select(array $fields = ['*']): self {
$this->selectFields = $fields;
return $this;
}
public function from(string $table): self {
$this->table = $table;
return $this;
}
public function where(string $column, $value, string $operator = '='): self {
return $this->add(new icms_db_criteria_Item($column, $value, $operator));
}
public function orWhere(string $column, $value, string $operator = '='): self {
return $this->add(new icms_db_criteria_Item($column, $value, $operator), 'OR');
}
public function orderBy(string $column, string $direction = 'ASC'): self {
return $this->setSort($column, $direction);
}
public function limit(int $limit, int $offset = 0): self {
return $this->setLimit($limit, $offset);
}
public function toSql(): string {
$sql = 'SELECT ' . implode(', ', $this->selectFields);
$sql .= ' FROM ' . $this->table;
$sql .= $this->renderWhere();
if ($this->getSort()) {
$sql .= ' ORDER BY ' . $this->getSort() . ' ' . $this->getOrder();
}
if ($this->getLimit()) {
$sql .= ' LIMIT ' . $this->getLimit();
if ($this->getStart()) {
$sql .= ' OFFSET ' . $this->getStart();
}
}
return $sql;
}
}This approach provides immediate benefits while maintaining backward compatibility and follows ImpressCMS coding standards from CLAUDE.md.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
No status