Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pel Bundle Laravel Package

kitano/pel-bundle

A Symfony bundle that integrates the PHP Exif Library (PEL), giving your application a service-based way to read, edit, and write EXIF metadata in JPEG and TIFF images through familiar dependency injection and configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is a thin Symfony DI wrapper around kitano/pel (PHP Expression Language) — a small library that parses a string expression and either evaluates it against a context array or compiles it to PHP for later execution. The bundle itself adds very little; nearly all behaviour lives in the underlying library. Treat the bundle as "service registration + config tree" and read the library docs for semantics.

Minimal setup:

composer require kitano/pel-bundle

Register the bundle. On Symfony 4+/Flex this is usually automatic via config/bundles.php; on older skeletons add it to AppKernel:

// config/bundles.php
Kitano\PelBundle\KitanoPelBundle::class => ['all' => true],

Where to look first — in this order:

  1. DependencyInjection/Configuration.php — this is the entire public config surface. It tells you what is configurable (typically a cache directory / service for compiled expressions). There is nothing else to learn about the bundle.
  2. Resources/config/services.xml (or .yaml) — gives you the service ID and any aliases. That service ID is your entry point.
  3. The kitano/pel README — this is where evaluate(), parse(), execute(), and function/operator registration are documented.

First use case: replacing hardcoded conditionals whose shape changes over time — pricing rules, discount eligibility, feature-gate conditions, notification routing, "should this record be visible" filters — with expressions stored as strings (in config, a DB column, or an admin UI) rather than deployed as code.

use Kitano\Pel\Pel;

class DiscountRule
{
    public function __construct(private Pel $pel) {}

    public function applies(array $order): bool
    {
        return (bool) $this->pel->evaluate(
            'order.total > 100 and order.country in ["FR", "DE"]',
            ['order' => $order]
        );
    }
}

If evaluate() is not on the injected service, run bin/console debug:container pel (or grep the services file for the ID) — the alias name varies by bundle version.

Implementation Patterns

Compile once, execute many

evaluate() parses on every call. For any expression evaluated in a loop (per-row filtering, per-request rule checks), parse once and reuse the node:

$rule = $this->pel->parse($expressionString);   // expensive
foreach ($rows as $row) {
    if ($rule->execute(['row' => $row])) { ... }
}

Cache the parsed node keyed by a hash of the expression string — an in-request array is usually enough; a PSR-6 pool is worth it only if you have many rules.

Wrap it in a domain service

Do not spray $pel->evaluate() calls across controllers. Create one small service per expression family so the context shape is defined in one place:

final class EligibilityEvaluator
{
    public function __construct(
        private Pel $pel,
        private array $compiled = [],
    ) {}

    public function isEligible(string $rule, Customer $c): bool
    {
        $node = $this->compiled[$rule] ??= $this->pel->parse($rule);

        return (bool) $node->execute([
            'customer' => $c,
            'age'      => $c->getAge(),
            'tier'     => $c->getTier(),
            'now'      => new \DateTimeImmutable(),
        ]);
    }
}

Keeping the context array flat-ish (primitives plus a couple of objects) makes expressions readable for whoever writes them — often a non-developer.

Validate at write time, not read time

If expressions come from a UI or config file, parse them when they are saved (a Symfony validator or a console command) and store only expressions that parsed successfully. Runtime parse failures on a cron job are far worse than a form error.

try {
    $this->pel->parse($submittedExpression);
} catch (\Throwable $e) {
    $this->addViolation('Expression is invalid: ' . $e->getMessage());
}

Config-driven rules

For a fixed, small set of expressions, put them in config/packages/kitano_pel.yaml (or your own parameters) and inject them into the evaluator. This gives you version control over rules without recompiling anything else.

Compiler-pass / tagged services (if you extend the language)

If PEL exposes function registration, the idiomatic Symfony move is a tagged-service compiler pass that collects pel.function tagged services and registers them on the Pel instance at container build time. Check whether the bundle already does this before writing it — with 4 stars, it probably does not.

Gotchas and Tips

Security is the headline risk. PEL evaluates expressions — it is not a sandbox. Never pass user-supplied expression strings to it without a hardening layer, and never expose expression editing to untrusted users. Even with only approved context variables, PEL's function set and object access can reach further than you expect. If you need user-authored rules, restrict to a whitelist of functions/operators, or pick a purpose-built rules engine with an explicit sandbox.

This package is effectively dormant. 4 stars, 0 dependents, and an old bundle layout. Practical consequences:

  • Pin the version in composer.json and commit composer.lock.
  • Expect AppKernel-era structure; Flex auto-registration and Symfony 5/6 config conventions may need manual wiring.
  • If you hit a bug, assume you will fix it yourself — budget for forking either the bundle or vendoring the service definition. Since the bundle is ~a services file, defining the Pel service yourself in services.yaml and dropping the bundle entirely is a legitimate, often better, option:
services:
    Kitano\Pel\Pel:
        arguments: ['@?cache.app']
        public: true

Distinguish parse errors from execution errors. A typo in an expression and an undefined variable in the context both surface as exceptions but mean very different things operationally. Wrap evaluation in a try/catch that logs the expression string alongside the exception — without it, a failing rule in production is very hard to trace.

Undefined variables are a common foot-gun. Depending on PEL's behaviour, referencing a missing context key either throws or silently evaluates to null, and null propagates quietly through comparisons (null > 5 → falsy), producing rules that "never match" with no error. Validate that your context arrays always carry every key the expression references, or add a compile-time check that extracts variable names from the parsed node and asserts they exist.

Cache invalidation. If the bundle offers a compiled-expression cache directory, it is not keyed to your expression strings unless you key it yourself. Clear it on deploy alongside cache:clear, and be aware that cache:clear alone may not be enough if expressions changed but the cache key did not.

Debugging: dump the parsed node (or generated PHP, if PEL exposes a compile()/__toString() path) rather than the expression string — it is the fastest way to see how precedence and and/or grouping were actually interpreted. Also worth writing a one-liner test per expression; expressions are exactly the kind of thing that silently changes meaning when someone "tidies up" the string.

Extension points: the useful ones live in PEL, not the bundle — custom functions, custom operators, and variable resolution. Confirm the bundle does not overwrite the Pel service in a way that discards your registrations (e.g. by using a fresh instance in a factory), otherwise your registerFunction() calls happen on a service nobody uses.

When to skip it entirely. If your expressions are static and few, plain PHP if statements are clearer, testable, and free. This bundle earns its place only when expressions must change without a deploy — and at that point, weigh it against Symfony's own symfony/expression-language, which is actively maintained, has a Security sandbox, and ships with far more documentation. The main reason to choose kitano/pel is if you specifically want its compiled-PHP execution model for performance; otherwise the default should be the Symfony component.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle