kitano/php-expression
kitano/php-expression is a lightweight PHP library for parsing and evaluating expressions safely without eval. It supports variables, operators, and functions, making it useful for dynamic formulas, rules, and configurable logic in PHP applications.
Architecture fit: Treat this as an embedded rules-engine component, not a Laravel extension. There is no service provider, facade, config, or migration surface — it is a framework-agnostic parser/evaluator that belongs behind an application-owned interface such as RuleEvaluator. Architecturally it supports the pattern we care about most as a product organization: moving configurable logic (pricing formulas, shipping eligibility, promo conditions, notification triggers, targeting rules) out of hardcoded conditionals and into data that ops/marketing can edit. The cost is a new governed runtime surface: a grammar, an authoring path, a validation contract, and a security model we now own.
Integration feasibility: Mechanically cheap — Composer install plus a thin adapter, roughly a day of spike work. Strategically more expensive, because the library's real contract needs verification before we build a product on it. The implementation notes reveal an important correction to the package description: the pipeline is parse → AST → compile to a PHP code fragment → evaluate, and the high-level path evaluates that fragment with eval. The "without eval()" framing in the description should not be repeated to stakeholders or in a security review. The safety story therefore rests entirely on the parser's grammar being tight enough that the generated code can only reference allowlisted variables and never arbitrary function calls, static calls, backticks, or object access. That is testable, and must be tested, but it is not something we should assume from marketing copy.
Technical risk: Adoption signals are weak — 30 stars, zero dependents, no commercial support, and a dormancy-shaped profile rather than a maturity-shaped one. MIT licensing is favorable and the source is small enough to read, fork, or vendor, which is the real mitigation. The dominant risk is security exposure if expressions are ever user- or tenant-authored: eval of a string derived from admin input is acceptable only behind strict grammar validation, variable allowlisting, and complexity limits; eval of a string derived from end-user input is not acceptable without a hardened sandbox and a second review. Secondary risks: parse/compile cost on hot paths if we forget to cache compiled artifacts; opaque type coercion and undefined-variable behavior producing silently wrong business outcomes (worse than an outage for pricing); PHP version drift breaking generated code; and the operational hazard that a bad compiled fragment throws a ParseError/Error inside eval, which must be caught and mapped to domain exceptions rather than surfacing a 500.
Key questions:
system(), backticks, new, static calls, superglobals)?Exception, ParseError, Error?symfony/expression-language on grammar safety, performance, sandboxing, and long-term support?src/, and does that extension path stay safe?Stack fit: Bind RuleEvaluator (or ExpressionEvaluator) in the container and keep package classes out of controllers, models, jobs, and Blade. Put the allowlisted variable names, allowed functions, and complexity ceilings in a Laravel config file so security posture is reviewable in one place. Never let a controller call eval; the single-service boundary is where logging, timing, error mapping, and any future sandbox live. Where expressions are tenant-authored, consider evaluating only in a constrained job or a separate service so the blast radius of a grammar escape is not a privileged web request.
Migration path: Time-box a spike comparing three options — this package, symfony/expression-language, and native PHP conditions — against the same five representative rules. Pick on grammar safety and maintenance signal, not on lines of code. If we adopt this one, build it behind an interface with contract tests so the implementation is swappable, ship it behind a feature flag, migrate one bounded rule family (not all conditional logic), and keep the hardcoded path runnable in parallel for a release so we can diff outputs before trusting the engine.
Compatibility: Verify Composer constraints before commitment and run CI on the oldest and newest supported PHP and Laravel versions. Because evaluation uses eval on a generated fragment, confirm generated code is compatible with the PHP versions we actually deploy, and note that opcache/JIT cannot cache the eval'd fragment — only our own compiled-string cache helps. No auto-discovery concern, but correspondingly no framework compatibility guarantee; that burden is ours.
Sequencing:
Maintenance: Assume internal ownership from day one. Pin the version, watch upstream releases and PHP support, and hold a standing option to vendor or fork — the codebase is small enough that forking is a realistic plan B rather than a crisis. Budget recurring dependency and security review effort; this is not a dependency that maintains itself, and zero dependents means zero peer pressure keeping it alive.
Support: No commercial support signal. Support is internal: a named owner, a runbook for expression failures (parse errors at save time vs. runtime errors in workers), and an escalation path. Document the expression grammar and its deliberate deviations from PHP so support engineers and admins share one mental model.
Scaling: Parsing and compiling are CPU-bound; evaluation is cheap. The scaling lever is therefore caching compiled fragments keyed by source hash plus package version, and never reparsing on the request path. Add expression complexity ceilings and per-evaluation time budgets, benchmark a worst-case rule, and treat rule storage as versioned data so cache invalidation is deterministic. Evaluation is stateless and scales horizontally; the shared cache and rule-versioning scheme is where the design attention goes.
Failure modes: Syntax errors (must be caught at authoring time with a 422, never at 3am in a queue); undefined variables and type-coercion surprises producing wrong prices or wrong eligibility rather than loud failures; eval scope leakage or shadowing if the wrapper isn't isolated in a static closure; uncaught ParseError/Error surfacing as 500s; grammar escapes if the allowlist is weaker than assumed; denial of service via pathological expressions; and upstream drift after a PHP upgrade. Mitigations: validate-on-write, allowlist, complexity and time limits, isolated evaluation scope, exception mapping, output diffing during pilot, and observability on evaluation latency and failure rate.
Ramp-up: The public API is small, so developer ramp-up is a day or two. The genuine learning curve is expression semantics, safe authoring for non-engineers, and debugging compiled artifacts in production — none of which the package documents for a Laravel audience. Fund short enablement, sample expressions as living documentation, and review every change to the allowlist as a security change. Net TPM position: viable for a bounded, internally authored rules capability where we accept ownership; not appropriate as an adopt-and-forget dependency or for untrusted user-supplied expressions without a dedicated security investment.
How can I help you explore Laravel packages today?