memran / marwa-entity
Fluent Entity Builder and validation library. define once, protect everywhere.
Requires
- php: >=8.2
- memran/marwa-support: ^1.3.1
- psr/container: ^2.0
- psr/http-factory: ^1.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.66
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
README
Framework-agnostic entity schema, validation, sanitization, and migration metadata for PHP 8.2+.
The package provides a single schema definition that can be reused across typed hydration, form rendering, and migration planning without coupling to a specific framework.
Validation and sanitization are powered by memran/marwa-support, keeping the package lightweight and avoiding duplicated utilities.
Features
- Define fields, types, rules, sanitizers, and metadata in one place
- Reuse the same schema for hydration and migration export
- Type casting and validation in a single pass
- Built-in field types: string, integer, boolean, decimal, datetime, json, enum
- Built-in sanitizers: trim, lower, strip_tags (via marwa-support)
Requirements
- PHP 8.2 or higher
- Composer
Installation
composer require memran/marwa-entity
Quick Start
<?php use Marwa\Entity\Entity\Entity; use Marwa\Entity\Entity\EntitySchema; use Marwa\Entity\Support\SanitizerFactory; use Marwa\Entity\Validation\Validator; $schema = EntitySchema::make('users'); $schema->string('name') ->label('Full Name') ->sanitize(SanitizerFactory::make('trim')); $schema->string('email') ->label('Email Address') ->sanitize(SanitizerFactory::make('trim'), SanitizerFactory::make('lower')); $schema->boolean('is_active'); $entity = new Entity($schema, new Validator()); $data = $entity->hydrate([ 'name' => ' Emran ', 'email' => ' TEST@EXAMPLE.COM ', 'is_active' => 'true', ]);
Hydrated result:
[
'name' => 'Emran',
'email' => 'test@example.com',
'is_active' => true,
]
Usage
Define a schema
<?php use Marwa\Entity\Entity\EntitySchema; use Marwa\Entity\Support\SanitizerFactory; $schema = EntitySchema::make('users'); $schema->string('name') ->label('Full Name') ->sanitize(SanitizerFactory::make('trim')); $schema->string('email') ->label('Email Address') ->sanitize(SanitizerFactory::make('trim'), SanitizerFactory::make('lower')) ->meta('unique', true) ->meta('widget', 'email'); $schema->boolean('is_active') ->label('Active') ->meta('default', true);
Hydrate and validate input
<?php use Marwa\Entity\Entity\Entity; use Marwa\Entity\Validation\Validator; $entity = new Entity($schema, new Validator()); $data = $entity->hydrate([ 'name' => ' Emran ', 'email' => ' TEST@EXAMPLE.COM ', 'is_active' => 'true', ]); /* [ 'name' => 'Emran', 'email' => 'test@example.com', 'is_active' => true, ] */
If validation fails or a typed cast is invalid, Entity::hydrate() throws an InvalidArgumentException containing JSON-encoded field errors.
Available field types
stringintegerbooleandecimaldatetimejsonenum
Example:
$schema->integer('age'); $schema->decimal('balance'); $schema->json('preferences'); $schema->enum('status', ['draft', 'published']);
Sanitizers
Use SanitizerFactory to create sanitizers:
use Marwa\Entity\Support\SanitizerFactory; $schema->string('title')->sanitize( SanitizerFactory::make('trim'), SanitizerFactory::make('lower'), SanitizerFactory::make('strip_tags', ['allowed' => ['strong', 'em']]), );
Built-in sanitizers:
trim- trim whitespacelower- convert to lowercasestrip_tags- strip HTML tags (supports allowed tags)
Build a schema from configuration
<?php use Marwa\Entity\Entity\SchemaFactory; use Marwa\Entity\Support\SanitizerFactory; $schema = SchemaFactory::fromArray( [ 'name' => 'users', 'fields' => [ 'name' => [ 'type' => 'string', 'sanitize' => ['trim'], ], ], ], static fn (string $name, array $params = []) => throw new \RuntimeException('Rules not implemented in demo'), static fn (string $name, array $params = []) => SanitizerFactory::make($name, $params), );
Supported sanitizer definitions:
'sanitize' => [ 'trim', ['name' => 'strip_tags', 'params' => ['allowed' => ['strong']]], ]
Export migration and form metadata
$ui = $schema->uiSpec(); $migration = $schema->migrationSpec();
Configuration Guide
Configuration is intentionally code-first. The package does not require environment variables.
- Use
SchemaFactory::fromArray()when definitions come from configuration files. - Register custom sanitizers through
SanitizerFactory::register().
Testing
Run the test suite with:
composer test
Generate text coverage output with:
composer test:coverage
Run the full local CI command set with:
composer ci
Static Analysis
PHPStan is configured at max level:
composer analyse
Coding standards can be checked or fixed with:
composer lint composer fix
CI/CD
GitHub Actions is configured in .github/workflows/ci.yml.
The pipeline runs:
composer validate --strict- coding standards
- PHPStan analysis
- PHPUnit with coverage generation
The matrix targets PHP 8.2, 8.3, and 8.4.
The lock file is resolved against PHP 8.2.0 in Composer config so CI does not accidentally lock dependencies that require a newer runtime than the package minimum.
Security Notes
- Validation happens before type casting to avoid silently mutating invalid input into trusted values.
- JSON decoding failures and invalid scalar casts are surfaced as validation failures.
Contributing
- Fork the repository.
- Install dependencies with
composer install. - Run
composer cibefore opening a pull request. - Add or update tests for behavior changes.
- Update examples or documentation when public APIs change.
Keep changes framework-agnostic and prefer PSR interfaces over concrete framework services.
License
MIT