|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +This file provides guidance to AI coding agents when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Constructo is a PHP library for powerful object serialization and deserialization. It converts between PHP objects and arrays/JSON with full type safety, supporting complex nested structures, backed enums, readonly properties, and custom formatters. |
| 8 | + |
| 9 | +**Key Concepts:** |
| 10 | +- **Builder**: Serializes data (arrays/JSON) into typed PHP objects |
| 11 | +- **Demolisher**: Deserializes PHP objects back into arrays/JSON |
| 12 | +- **Set**: Type-safe container for managing key-value data |
| 13 | +- **Entity**: Base class providing automatic export() and JSON serialization |
| 14 | +- **Faker**: Test data generation utility using FakerPHP |
| 15 | + |
| 16 | +## Development Commands |
| 17 | + |
| 18 | +**All commands use Docker via Makefile.** The project uses `compose.yml` with a `constructo-app` container. |
| 19 | + |
| 20 | +### Initial Setup |
| 21 | +```bash |
| 22 | +# Setup project (prune, install dependencies, start containers) |
| 23 | +make setup |
| 24 | + |
| 25 | +# Start containers |
| 26 | +make up |
| 27 | + |
| 28 | +# Stop containers |
| 29 | +make down |
| 30 | + |
| 31 | +# Prune containers and volumes |
| 32 | +make prune |
| 33 | + |
| 34 | +# Enter container bash |
| 35 | +make bash |
| 36 | +``` |
| 37 | + |
| 38 | +### Testing |
| 39 | +```bash |
| 40 | +# Run all tests (generates HTML coverage in tests/.phpunit/html) |
| 41 | +docker-compose exec constructo vendor/bin/phpunit |
| 42 | + |
| 43 | +# Run specific test file |
| 44 | +docker-compose exec constructo vendor/bin/phpunit tests/Core/Serialize/BuilderTest.php |
| 45 | + |
| 46 | +# Run specific test method |
| 47 | +docker-compose exec constructo vendor/bin/phpunit --filter testMethodName |
| 48 | + |
| 49 | +# Run tests with specific options |
| 50 | +docker-compose exec constructo vendor/bin/phpunit --testdox --colors=always |
| 51 | +``` |
| 52 | + |
| 53 | +### Code Quality |
| 54 | +```bash |
| 55 | +# Run all linters |
| 56 | +make lint |
| 57 | + |
| 58 | +# Individual linters |
| 59 | +make lint-phpcs # Code style (PSR-12) |
| 60 | +make lint-phpstan # Static analysis (level 10) |
| 61 | +make lint-phpmd # Mess detection |
| 62 | +make lint-rector # Modernization check (dry-run) |
| 63 | +make lint-psalm # Additional static analysis |
| 64 | + |
| 65 | +# Auto-fix code issues |
| 66 | +make fix # Runs rector and php-cs-fixer |
| 67 | +``` |
| 68 | + |
| 69 | +### CI Pipeline |
| 70 | +```bash |
| 71 | +# Full CI check (lint + test) |
| 72 | +make ci |
| 73 | +``` |
| 74 | + |
| 75 | +### Composer |
| 76 | +```bash |
| 77 | +# Install dependencies |
| 78 | +make install |
| 79 | + |
| 80 | +# Dump autoload |
| 81 | +make dump |
| 82 | +``` |
| 83 | + |
| 84 | +## Architecture |
| 85 | + |
| 86 | +### Core Components |
| 87 | + |
| 88 | +**Serialization (Data → Objects):** |
| 89 | +- `Core/Serialize/Builder`: Main entry point for building objects from data |
| 90 | +- `Core/Serialize/Resolver/*`: Chain of responsibility pattern for resolving parameter values |
| 91 | + - `ValidateValue`: Validates input data |
| 92 | + - `DependencyValue`: Resolves constructor dependencies |
| 93 | + - `TypeMatched`: Matches types for nested objects |
| 94 | + - `BackedEnumValue`: Converts strings to backed enums |
| 95 | + - `AttributeValue`: Applies custom attributes |
| 96 | + - `CollectionValue`: Handles collections/arrays |
| 97 | + - `FormatValue`: Applies custom formatters |
| 98 | + - `NoValue`: Falls back to default values |
| 99 | + |
| 100 | +**Deserialization (Objects → Data):** |
| 101 | +- `Core/Deserialize/Demolisher`: Converts objects to arrays/JSON |
| 102 | +- `Core/Deserialize/Resolve/*`: Chain for processing object properties |
| 103 | + - `DoNothingChain`: Passes through simple values |
| 104 | + - `DependencyChain`: Handles nested dependencies |
| 105 | + - `AttributeChain`: Processes custom attributes |
| 106 | + - `CollectionChain`: Demolishes collections |
| 107 | + - `DateChain`: Formats DateTime objects |
| 108 | + - `FormatterChain`: Applies custom formatters |
| 109 | + |
| 110 | +**Reflection System:** |
| 111 | +- `Support/Reflective/Engine`: Base class with reflection utilities and formatter selection |
| 112 | +- `Core/Reflect/Reflector`: Introspects classes for metadata |
| 113 | +- `Support/Reflective/Factory/Target`: Wraps ReflectionClass with parameter access |
| 114 | + |
| 115 | +**Testing Utilities:** |
| 116 | +- `Core/Fake/Faker`: Generates fake data for testing (extends FakerPHP) |
| 117 | +- `Testing/BuilderExtension`: PHPUnit trait providing builder() helper |
| 118 | +- `Testing/MakeExtension`: PHPUnit trait providing make() helper for test instances |
| 119 | +- `Testing/FakerExtension`: PHPUnit trait providing faker() helper |
| 120 | + |
| 121 | +**Helper Functions:** |
| 122 | +Located in `src/_/` directory: |
| 123 | +- `cast.php`: Type conversion (arrayify, stringify, mapify, etc.) |
| 124 | +- `json.php`: JSON encoding/decoding |
| 125 | +- `util.php`: Data extraction (extractString, extractInt, extractBool, extractArray) |
| 126 | +- `notation.php`: Snake_case ↔ camelCase conversion |
| 127 | +- `crypt.php`: Encryption utilities |
| 128 | + |
| 129 | +### Design Patterns |
| 130 | + |
| 131 | +**Chain of Responsibility:** |
| 132 | +Both Builder and Demolisher use resolver chains. Each resolver attempts to handle the parameter, or passes it to the next in the chain via `then()`. |
| 133 | + |
| 134 | +**Factory Pattern:** |
| 135 | +- `Factory/ReflectorFactory`: Creates Reflector instances |
| 136 | +- `Factory/SchemaFactory`: Generates schemas from class definitions |
| 137 | +- `Support/Reflective/Factory/Target`: Creates reflection targets |
| 138 | + |
| 139 | +**Template Method:** |
| 140 | +`Engine` abstract class defines the common reflection/formatting logic, while `Builder` and `Demolisher` implement specific serialization flows. |
| 141 | + |
| 142 | +### Key Types |
| 143 | + |
| 144 | +- **Set**: Immutable key-value container with validation (all keys must be strings) |
| 145 | +- **Value**: Wrapper for individual values with validation/transformation |
| 146 | +- **Datum**: Error handling wrapper that combines exceptions with original data |
| 147 | +- **Entity**: Base class for domain objects needing serialization |
| 148 | +- **Collection**: Base for typed collections implementing `Collectable` |
| 149 | +- **Timestamp**: Custom DateTime wrapper for timestamp handling |
| 150 | + |
| 151 | +## Code Conventions |
| 152 | + |
| 153 | +### Type Safety |
| 154 | +- Use PHP 8.3+ features: readonly properties, union types, backed enums |
| 155 | +- Always declare strict types: `declare(strict_types=1);` |
| 156 | +- PHPStan level 10 must pass (strictest analysis) |
| 157 | + |
| 158 | +### Comments |
| 159 | +- Do not write comments in code unless explicitly requested |
| 160 | +- Code should be self-documenting through clear naming and structure |
| 161 | +- PHPDoc blocks for type hints are acceptable |
| 162 | + |
| 163 | +### Git Commits |
| 164 | +- Use Conventional Commits format: `type(scope): description` |
| 165 | +- Common types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `style`, `perf` |
| 166 | +- Write commit messages in English |
| 167 | +- Do not use emojis in commit messages |
| 168 | +- Keep commits focused and atomic |
| 169 | + |
| 170 | +### Constructor Pattern |
| 171 | +Constructo relies on constructor-based hydration: |
| 172 | +```php |
| 173 | +class User extends Entity |
| 174 | +{ |
| 175 | + public function __construct( |
| 176 | + public readonly int $id, |
| 177 | + public readonly string $name, |
| 178 | + public readonly Status $status = Status::ACTIVE, |
| 179 | + ) {} |
| 180 | +} |
| 181 | +``` |
| 182 | + |
| 183 | +### Naming Conventions |
| 184 | +- **snake_case**: Used in data arrays (JSON, database) |
| 185 | +- **camelCase**: Used in PHP properties |
| 186 | +- Automatic conversion via `Notation` class |
| 187 | + |
| 188 | +### Testing |
| 189 | +- Test files mirror src/ structure in tests/ |
| 190 | +- Use `MakeExtension` trait for creating test instances |
| 191 | +- Use `BuilderExtension` trait for builder() access |
| 192 | +- Use `FakerExtension` trait for fake data generation |
| 193 | +- PHPUnit configuration in `phpunit.xml` |
| 194 | +- Test results cached in `tests/.phpunit/` |
| 195 | + |
| 196 | +## Common Patterns |
| 197 | + |
| 198 | +### Creating Objects from Data |
| 199 | +```php |
| 200 | +use Constructo\Core\Serialize\Builder; |
| 201 | +use Constructo\Support\Set; |
| 202 | + |
| 203 | +$builder = new Builder(); |
| 204 | +$user = $builder->build(User::class, Set::createFrom([ |
| 205 | + 'id' => 1, |
| 206 | + 'name' => 'John Doe', |
| 207 | + 'status' => 'active', |
| 208 | +])); |
| 209 | +``` |
| 210 | + |
| 211 | +### Converting Objects to Data |
| 212 | +```php |
| 213 | +use Constructo\Core\Deserialize\Demolisher; |
| 214 | + |
| 215 | +$demolisher = new Demolisher(); |
| 216 | +$data = $demolisher->demolish($user); |
| 217 | +// Returns stdClass with snake_case keys |
| 218 | +``` |
| 219 | + |
| 220 | +### Custom Formatters |
| 221 | +```php |
| 222 | +$builder = new Builder(formatters: [ |
| 223 | + 'string' => fn($val) => strtoupper($val), |
| 224 | + MyClass::class => new MyCustomFormatter(), |
| 225 | +]); |
| 226 | +``` |
| 227 | + |
| 228 | +### Handling Collections |
| 229 | +```php |
| 230 | +class UserCollection extends Collection implements Collectable |
| 231 | +{ |
| 232 | + protected function getItemClass(): string |
| 233 | + { |
| 234 | + return User::class; |
| 235 | + } |
| 236 | +} |
| 237 | + |
| 238 | +$collection = new UserCollection(); |
| 239 | +$collection->push($user1); |
| 240 | +$demolished = $demolisher->demolishCollection($collection); |
| 241 | +``` |
| 242 | + |
| 243 | +### Error Handling |
| 244 | +```php |
| 245 | +use Constructo\Exception\AdapterException; |
| 246 | +use Constructo\Support\Datum; |
| 247 | + |
| 248 | +try { |
| 249 | + $result = $builder->build(User::class, $data); |
| 250 | +} catch (AdapterException $e) { |
| 251 | + $datum = new Datum($e, $data); |
| 252 | + $errorData = $datum->export(); // Contains '@error' key |
| 253 | +} |
| 254 | +``` |
| 255 | + |
| 256 | +## Dependencies |
| 257 | + |
| 258 | +**Runtime:** |
| 259 | +- PHP 8.3+ (strict requirement) |
| 260 | +- ext-json |
| 261 | +- jawira/case-converter: Case conversion utilities |
| 262 | +- visus/cuid2: ID generation |
| 263 | +- fakerphp/faker: Test data generation |
| 264 | + |
| 265 | +**Development:** |
| 266 | +- PHPUnit 10.5+ |
| 267 | +- PHPStan 2+ (level 10) |
| 268 | +- Rector 2+ (code modernization) |
| 269 | +- PHPCS (PSR-12) |
| 270 | +- PHPMD, Psalm (additional analysis) |
| 271 | + |
| 272 | +## Maintenance Notes |
| 273 | + |
| 274 | +- Immutability is preferred (Set, Value are readonly) |
| 275 | +- Builder and Demolisher are stateless (can be reused) |
| 276 | +- Resolver chains execute in specific order (see architecture section) |
| 277 | +- Property visibility: prefer public readonly over private with getters |
| 278 | +- All autoloaded helper functions check for existence before definition |
0 commit comments