concept-labs / expression
(C)oncept-Labs Expression
Installs: 31
Dependents: 1
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/concept-labs/expression
Requires
- php: >=8.2
Requires (Dev)
- pestphp/pest: ^2.0
- pestphp/pest-plugin-arch: ^2.0
- phpunit/phpunit: ^10.0|^11.0
README
A flexible and powerful PHP library for building and composing expressions with decorators. Perfect for constructing SQL queries, configuration strings, or any text-based DSL in a programmatic and maintainable way.
Features
- ๐ง Fluent API - Chain methods for clean, readable code
- ๐จ Decorator Pattern - Transform expressions with decorators
- ๐๏ธ Composable - Nest expressions within expressions
- ๐ Immutable Context - Safe context interpolation
- ๐งฉ Extensible - Easy to extend with custom decorators
- โจ Type Safe - Full PHP 8.2+ type hints
- ๐งช Well Tested - Comprehensive test coverage with PHPUnit and Pest
Installation
Install via Composer:
composer require concept-labs/expression
Quick Start
use Concept\Expression\Expression; use Concept\Expression\Expression; // Create an expression $expression = new Expression(); // Build a simple expression $expression->push('SELECT', 'id', 'name', 'FROM', 'users'); echo $expression; // Output: SELECT id name FROM users // Use decorators for more control $columns = (new Expression()) ->push('id', 'name', 'email') ->wrapItem('`') ->join(', '); $query = (new Expression()) ->push('SELECT', $columns, 'FROM', 'users'); echo $query; // Output: SELECT `id`, `name`, `email` FROM users
Basic Usage
Creating Expressions
$expr = new Expression(); // Add expressions $expr->push('SELECT', 'column'); $expr->push('FROM', 'table'); // Add to beginning $expr->unshift('EXPLAIN'); echo $expr; // Output: EXPLAIN SELECT column FROM table
Using __invoke() for Concise Syntax
// You can also use the __invoke() method for a more concise syntax $expr = new Expression(); $expr('SELECT', 'column')('FROM', 'table'); // Equivalent to: // $expr->push('SELECT', 'column')->push('FROM', 'table'); echo $expr; // Output: SELECT column FROM table
Nested Expressions
$columns = (new Expression()) ->push('id', 'name') ->join(', '); $mainExpr = (new Expression()) ->push('SELECT', $columns, 'FROM', 'users'); echo $mainExpr; // Output: SELECT id, name FROM users
Using Decorators
Wrap Expressions
$expr = (new Expression()) ->push('value') ->wrap('(', ')'); echo $expr; // Output: (value)
Wrap Items
$expr = (new Expression()) ->push('id', 'name', 'email') ->wrapItem('`') ->join(', '); echo $expr; // Output: `id`, `name`, `email`
Custom Decorators
// Item decorator - applied to each item $expr = (new Expression()) ->push('select', 'from', 'where') ->decorateItem(fn($item) => strtoupper($item)); echo $expr; // Output: SELECT FROM WHERE // Expression decorator - applied to final result $expr = (new Expression()) ->push('column') ->decorate(fn($str) => "SELECT $str FROM users"); echo $expr; // Output: SELECT column FROM users // Join decorator - custom join logic $expr = (new Expression()) ->push('condition1', 'condition2') ->decorateJoin(fn($items) => implode(' AND ', $items)); echo $expr; // Output: condition1 AND condition2
Context Interpolation
$template = (new Expression()) ->push('SELECT', '{column}', 'FROM', '{table}'); $concrete = $template->withContext([ 'column' => 'name', 'table' => 'users' ]); echo $concrete; // Output: SELECT name FROM users echo $template; // Output: SELECT {column} FROM {table} (unchanged)
Clone and Prototype
$base = (new Expression()) ->push('SELECT', 'id'); // Clone creates independent copy $clone = clone $base; $clone->push('FROM', 'users'); echo $base; // Output: SELECT id echo $clone; // Output: SELECT id FROM users // Prototype is alias for clone $proto = $base->prototype();
Reset
$expr = (new Expression()) ->push('SELECT', 'column') ->type('select'); $expr->reset(); // Clears everything echo $expr; // Output: (empty string)
Advanced Usage
Building Complex SQL Queries
// Build a complex SELECT query $columns = (new Expression()) ->push('u.id', 'u.name', 'u.email', 'p.title') ->wrapItem('`') ->join(', '); $joins = (new Expression()) ->push('JOIN', 'posts', 'p', 'ON', 'u.id = p.user_id'); $where = (new Expression()) ->push('u.active = 1', 'p.published = 1') ->join(' AND ') ->wrap('WHERE ', ''); $query = (new Expression()) ->push('SELECT', $columns) ->push('FROM', 'users', 'u') ->push($joins) ->push($where); echo $query; // Output: SELECT `u.id`, `u.name`, `u.email`, `p.title` FROM users u JOIN posts p ON u.id = p.user_id WHERE u.active = 1 AND p.published = 1
Multiple Decorator Layers
$expr = (new Expression()) ->push('a', 'b', 'c') ->decorateItem(fn($item) => strtoupper($item)) // Items: A, B, C ->decorateItem(fn($item) => "`$item`") // Items: `A`, `B`, `C` ->join(', ') // Join: `A`, `B`, `C` ->decorate(fn($str) => "SELECT $str") // Wrap: SELECT `A`, `B`, `C` ->decorate(fn($str) => "$str FROM table"); // Wrap: SELECT `A`, `B`, `C` FROM table echo $expr; // Output: SELECT `A`, `B`, `C` FROM table
API Reference
For detailed API documentation, see docs/api-reference.md.
Main Classes
- Expression - Main expression class
- ExpressionInterface - Interface for expressions
- DecoratorManager - Manages decorators for expressions
- Decorator - Static helper methods for common decorators
Key Methods
Expression Methods
__invoke(...$expressions)
- Add expressions (alias for push, enables callable syntax)push(...$expressions)
- Add expressions to the endunshift(...$expressions)
- Add expressions to the beginningwrap($left, $right = null)
- Wrap the entire expressionwrapItem($left, $right = null)
- Wrap each itemjoin($separator)
- Set the join separatordecorate(callable ...$decorator)
- Add expression decoratordecorateItem(callable ...$decorator)
- Add item decoratordecorateJoin(callable $decorator)
- Set join decoratorwithContext(array $context)
- Create new expression with contextreset()
- Reset the expressiontype(string $type)
- Set expression typeisEmpty()
- Check if expression is emptyprototype()
- Create a clone
Testing
The package includes comprehensive tests using both PHPUnit and Pest.
# Run all tests with Pest composer test # Run PHPUnit tests composer test:phpunit # Run with coverage composer test:coverage
Contributing
Contributions are welcome! Please see docs/contributing.md for details.
License
This package is licensed under the Apache License 2.0. See LICENSE file for details.
Credits
- Viktor Halytskyi - Original author
- Part of the Concept Labs ecosystem