Framework-neutral eligibility rules and typed decisions that answer one reusable business question about an external Subject in a supplied Context.
maatify/php-eligibility is a standalone, framework-neutral PHP library that
answers "Is a given Subject eligible in the supplied Context?" for domains
such as products, categories, payment methods, shipping, and promotions, so
those domains do not each re-invent a customer/country restriction subsystem.
Host Input -> Public API -> Domain Service -> Integration Boundary -> Observable Result
The package knows external identities only. It does not know the database model, lifecycle, or implementation of the domains that own those identities.
The Package Reference documents the current API. See the RC.3 tag for the documentation shipped with the published release.
- Published RC:
v1.0.0-rc.3on Packagist. - Published Stable: None.
- Published artifact verification: PASS on
bb1dec9.
- Immutable, typed
Subject,Context,Rule, andEligibilityDecisionvalues with canonical UTF-8 string validation and bytewise canonical order. - Deterministic rule evaluation: ALLOW/DENY effects, active/inactive lifecycle, DENY precedence, ALLOW-list and DENY-only semantics, AND across dimensions, and no inferred cross-dimension or cross-Subject behavior.
- Fully typed decision output:
EligibilityDecision, per-dimensionDimensionOutcome, completeRuleReferencetraces, and stable machine reason codes. - Single and ordered batch evaluation with duplicate-Subject rejection, empty batch support, and a bounded bulk load path (no canonical N+1 query pattern).
- Management lifecycle: create, paginated inspect/list (including inactive
Rules, with optional dimension/lifecycle/effect filters), paginated
active-dimension introspection, a Rule lifecycle count summary, effect and
lifecycle mutations, atomic
replaceDimensionRules(), and idempotent Subject cleanup. - Direct-PDO MySQL-compatible persistence with separate command, management query, and evaluation-read adapters, plus shared Persistence transaction, savepoint, and pagination mechanics and Eligibility-owned coordination locking; no Host foreign keys or joins.
- Typed package exceptions on the shared
maatify/exceptionshierarchy, including classification of malformed persisted Rule state, with unknown external throwables propagated unchanged.
| Requirement | Constraint |
|---|---|
| PHP | 8.4.27+ on PHP 8.4 or 8.5.12+ on PHP 8.5 |
| PHP extensions | ext-pdo, ext-pdo_mysql, ext-pcre |
| Runtime packages | maatify/exceptions (^1.0), maatify/php-persistence (1.0.0-rc.1) |
| Database | MySQL-compatible database-server semantics through direct PDO (capability-based; no minimum product version is declared — see Persistence and Schema) |
Published Eligibility 1.0.0-rc.3 uses maatify/php-persistence 1.0.0-rc.1 for
SavepointTransactionRunnerInterface and PdoSavepointTransactionRunner.
Eligibility delegates transaction ownership, operation-local savepoints,
cleanup, and original-Throwable preservation to that shared API.
The database contract requires transactional InnoDB-style package-owned table behavior, binary-safe exact-value storage/comparison, the bounded indexed-key capacity of this package's schema, and the uniqueness/index semantics the schema uses. Being labelled MySQL-compatible is not enough; the server must provide those capabilities. MariaDB compatibility is not claimed without executed MariaDB verification.
Install 1.0.0-rc.3 from Packagist. The root project explicitly allows the
Persistence RC while keeping Composer's default minimum stability.
composer require maatify/php-eligibility:1.0.0-rc.3@RC maatify/php-persistence:1.0.0-rc.1@RCFor RC.2, use the published RC.2 guide
and its PHP ^8.4 requirements.
For development access only, use a local checkout of the development branch through a Composer path repository. The checkout examples below use the new shared Persistence API. From your consumer project:
git clone https://github.com/Maatify-ltd/php-eligibility.git .tools/php-eligibility
# Check out the development revision you want to test.
composer config repositories.php-eligibility '{"type":"path","url":".tools/php-eligibility","options":{"symlink":true,"versions":{"maatify/php-eligibility":"dev-local"}}}'
composer require maatify/php-eligibility:dev-local maatify/php-persistence:1.0.0-rc.1@RCThe dev-local identity comes from the path repository configuration. For developing the library itself (running the full local parity suite),
clone into a working directory and follow Development and Testing.
Installation (both paths) requires ext-pdo, ext-pdo_mysql, and
ext-pcre. The package performs no automatic setup: the schema asset is an
install asset (schema/eligibility_rules.sql),
not a migration run at install time.
use Maatify\Eligibility\Factory\Pdo\PdoEligibilityRuntimeFactory;
use Maatify\Eligibility\Enum\RuleEffectEnum;
use Maatify\Eligibility\Evaluation\ValueObject\Context;
use Maatify\Eligibility\Evaluation\ValueObject\ContextDimension;
use Maatify\Eligibility\ValueObject\Subject;
$pdo = new PDO('mysql:host=127.0.0.1;dbname=app;charset=utf8mb4', 'app', 'secret', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
$pdo->exec(file_get_contents(__DIR__ . '/vendor/maatify/php-eligibility/schema/eligibility_rules.sql'));
$factory = new PdoEligibilityRuntimeFactory($pdo);
$management = $factory->createManagementService();
$evaluation = $factory->createEvaluationService();
$subject = new Subject('product', '150');
$management->createRule(new \Maatify\Eligibility\Management\Command\CreateRuleCommand(
$subject,
'country',
'EG',
RuleEffectEnum::ALLOW,
));
$management->createRule(new \Maatify\Eligibility\Management\Command\CreateRuleCommand(
$subject,
'customer_type',
'blocked',
RuleEffectEnum::DENY,
));
$decision = $evaluation->decide($subject, new Context(
ContextDimension::fromStrings('country', 'EG'),
ContextDimension::fromStrings('customer_type', 'retail'),
));
if ($decision->eligible) {
// $decision->reasonCode is DecisionReasonEnum::ELIGIBLE
}See the Usage Guide for batch evaluation and management, including pagination and atomic rule replacement.
Public surface (see the Package Reference for the full contract):
- Values:
Subject,SubjectCollection,Context,ContextDimension,ContextValue,ContextValueCollection,Rule,RuleIdentity,RuleCollection,RuleEffectEnum,RuleLifecycleEnum,CanonicalString. - Decisions:
EligibilityDecision,DimensionOutcome,DimensionOutcomeCollection,RuleReference,RuleReferenceCollection,DecisionReasonEnum(UNRESTRICTED,ELIGIBLE,DENIED),DimensionReasonEnum. - Evaluation service:
EligibilityEvaluationService/EligibilityEvaluationServiceInterface—decide()anddecideMany(). - Management service:
EligibilityManagementService/EligibilityManagementServiceInterface—createRule(),inspectRule(),inspectRules()(paginated),inspectActiveDimensionKeys()(paginated),inspectRuleLifecycleSummary(),updateRuleEffect(),deactivateRule(),reactivateRule(),replaceDimensionRules(),cleanupSubject(). - Persistence contracts:
RuleCommandRepositoryInterfaceowns command mutations,RuleManagementQueryInterfaceowns paginated management reads and the lifecycle summary aggregate, andActiveRuleReaderInterfaceowns active bulk reads for evaluation. The internalRuleMutationSupportInterfaceowns only the coordination lock, complete Subject + dimension mutation read, and coordination cleanup needed by atomic replacement/cleanup. Generic transaction/savepoint and pagination mechanics are owned bymaatify/php-persistence(SavepointTransactionRunnerInterface,PageRequest,PageResultDTO,PdoPaginator); Eligibility owns only its domain filter/count SQL and row mapping. - Default PDO construction:
PdoEligibilityRuntimeFactoryaccepts one caller-ownedPDOand returns the Evaluation and Management service interfaces. It creates all default adapters and the savepoint runner over that same PDO; it does not create connections, apply schema, own credentials, or expose the internal mutation-support contract. - PDO adapters:
PdoRuleCommandRepository,PdoRuleManagementQuery,PdoActiveRuleReader, andPdoSavepointTransactionRunnerremain available for advanced explicit composition. The command adapter implements the Eligibility command and mutation-support contracts; ordinary consumers should use the factory. - Commands / queries / results:
CreateRuleCommand,UpdateRuleEffectCommand,DeactivateRuleCommand,ReactivateRuleCommand,DesiredRule,DesiredRuleCollection,ReplaceDimensionRulesCommand,CleanupSubjectCommand,RuleCriteria(subject/dimension/lifecycle/effect filters),ActiveDimensionKeysCriteria,ActiveDimensionKeyDTO,RuleLifecycleSummaryCriteria,RuleLifecycleSummaryDTO,SubjectDecisionDTO,SubjectDecisionCollectionDTO. - Exceptions:
EligibilityExceptionInterface,InvalidEligibilityInputException,RuleNotFoundException,RuleIdentityConflictException,RuleConcurrencyConflictException,InvalidPersistedRuleStateException(backed by the sharedmaatify/exceptionshierarchy). Unknown/external storage failures (for example an unclassifiedPDOException) propagate unchanged rather than being forced into the Eligibility marker.
Apply the schema before creating the runtime. The Host owns access control, tenant isolation, PDO configuration, and any outer transaction. See the Package Reference for rule evaluation, transaction participation, and the typed errors returned by the package.
The Usage Guide contains the capability decision map and helps a consumer choose the smallest suitable public API surface before installing or trying the package.
| Document | Purpose |
|---|---|
| Package Reference | Canonical package contract: identity, Context, Rule, Decision, lifecycle, ordering, pagination, lifecycle summary, persistence, transaction, concurrency, error, batch, and 60-scenario coverage. |
| Usage Guide | Consumer-facing API guide, capability decision map, input/output types, transaction notes, and links to runnable examples. |
| Runnable Examples | Standalone public-API examples for evaluation, batch evaluation, management, replacement, PDO wiring, and typed exception handling. |
| Schema | Persistence contract, tables, bounds, applying/reapplying, and the local MySQL fixture. |
| CHANGELOG | Published releases and compatibility changes. |
| Security Policy | Support state, vulnerability reporting, and scope. |
| Contributing Guide | Contribution expectations, local verification, and PR requirements. |
| Code of Conduct | Community rules and reporting. |
The package owns two tables with the maa_eligibility_ prefix:
maa_eligibility_rules— Rules with the exact-string-safeVARBINARYcolumns and a 574-byte natural-identity unique key.maa_eligibility_subject_locks— package-owned coordination rows used by replacement and cleanup to serialize per-Subject mutations.
See schema/README.md for bounds, storage guarantees,
transaction/savepoint behavior, and the local mysql:8.4.11 reproducibility
fixture. The fixture version is not a minimum supported product version.
The default runtime composition keeps command, management-query,
evaluation-read, and internal mutation-support responsibilities explicit inside
PdoEligibilityRuntimeFactory. The Host creates/configures the PDO, applies
the schema, and may own an outer transaction; the factory builds both service
graphs over that exact PDO. Direct adapter/service construction remains an
advanced extension path. Generic transaction/savepoint mechanics belong to
maatify/php-persistence; per-Subject coordination locking remains
Eligibility-owned.
- PHPStan level max, zero errors, no baseline and no suppressions.
- Unit, Golden (60 canonical acceptance scenarios with an executable evidence map), real-MySQL Integration, and concurrency/invariant suites.
- Real-service Integration is run on both supported PHP minors with a repeated run for cleanup/repeatability evidence; there is no SQLite or mock substitute.
- Consumer Verification Harness: two clean external-consumer runs using production autoload, public contracts, and the real persistence boundary.
- Fail-closed CI with stable aggregate gates:
ci-quality,ci-tests,ci-integration.
Prerequisites: PHP 8.4.27+ on PHP 8.4 or 8.5.12+ on PHP 8.5, Composer, Docker (for the real MySQL fixture).
The repository development and verification workflow requires Composer 2.10.x;
this is a tooling policy only and is not a runtime or consumer install
requirement. Verify it before resolving dependencies:
composer --version | grep -Eq '^Composer version 2\.10\.'Resolve the latest-compatible dependencies and run the local aggregate gate:
composer update --no-interaction --prefer-dist --no-progress
tools/check-local.sh # Docker-free local gates, Unit, and Golden
tools/check-local.sh --with-integration # adds fresh Integration, Harness, and examples lifecycles
composer test:integration # focused fresh real-MySQL Integration lifecycle
composer test:harness # focused fresh Consumer Harness lifecycle
composer test:examples # focused fresh standalone-example smoke lifecycleThe maintained verification commands require Composer 2.10.x, a working
Docker daemon with docker compose, PHP 8.4.27+ on PHP 8.4 or 8.5.12+ on PHP 8.5, and pdo_mysql. Each real
service command provisions and tears down its own disposable Compose project
with a dynamic loopback-only MySQL port; manual service startup is not needed.
CI checks the actual PHP patch floor with php tools/check-php-floor.php
on PHP 8.4 and 8.5. The contributing guide documents the
latest-compatible and lowest-supported dependency runs.
Use requires a separate written license from Maatify. See LICENSE.
Engineered by Mohamed Abdulalim (@megyptm)
Backend Lead & Technical Architect
https://www.maatify.dev