# opis/closure: serializing PHP closures and anonymous classes

> Opis Closure lets PHP code turn closures, anonymous classes and arbitrary data into a string and back. It is a narrow tool with a wide blast radius, so the interesting question is when you should not reach for it.

**opis/closure** — Serialize closures, anonymous classes, and arbitrary data

- Repository: https://github.com/opis/closure
- Website: https://opis.io/closure
- Stars: 2,571 · Forks: 94
- Language: PHP
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/opis-closure

## The gap opis/closure fills in PHP serialization

PHP's built-in serialize() refuses closures and anonymous classes. The language has no way to express the body of an anonymous function as data, because a closure is compiled code plus a bound scope plus captured variables, not a value. That refusal is deliberate, and it is the reason queue workers, cache layers and RPC bridges in PHP usually ask you to send a class name and a method name instead of the callable itself.

Opis Closure exists to remove that constraint. According to the README, it serializes closures, anonymous classes and arbitrary data, and it does so without relying on PHP extensions: no FFI, no compiled helper. The intended reader is a PHP developer who already has a callable in hand and needs it to exist in another process or another request. The library's own examples are two lines long, which is the honest shape of the problem it solves.

## How the serializer reconstructs code instead of storing it

The mechanism the README points at is code reconstruction. The library does not persist a compiled closure object; it captures the source of the function or class, the captured variables, and the scope the closure was bound to, then rebuilds an equivalent callable on the other side. That is why the README claims the reconstructed code is close to the original and debugger friendly: what you get back is readable source, not an opaque blob.

The stated feature list is where the engineering cost shows. The library handles circular references, works with attributes, readonly properties and property hooks, and supports PHP 8.0 through 8.5 syntax. Each of those is a language feature that changes what source looks like, so the parser has to keep pace with PHP itself. Reconstruction also means the payload carries PHP source text, and that is the design decision every other trade-off in this review follows from.

## Installing opis/closure and running a first round trip

The README gives one install path: Composer. Run the require command from your project root and Composer will resolve the current 4.x line.

```bash
composer require opis/closure
```

If you prefer to pin the dependency by hand, the README shows the same requirement written into composer.json. The caret range it uses is ^4.5, so adjust the minor version only if you have a reason to.

```json
{
    "require": {
        "opis/closure": "^4.5"
    }
}
```

The library exports serialize and unserialize as namespaced functions. The README's first example is a closure that returns a string; note the use function import, because these are not the global built-ins.

```php
use function Opis\Closure\{serialize, unserialize};

$serialized = serialize(fn() => "hello from closure!");
$greet = unserialize($serialized);

echo $greet(); // hello from closure!
```

The second documented example does the same for an anonymous class with a constructor-promoted property. It prints the message the constructor received, which tells you the captured state survived the round trip.

```php
use function Opis\Closure\{serialize, unserialize};

$serialized = serialize(new class("hello from anonymous class!") {
    public function __construct(private string $message) {}

    public function greet(): string {
        return $this->message;
    }
});

$object = unserialize($serialized);
```

That is the whole first-use surface: install, serialize, unserialize, call. Everything else in the 4.x documentation is about controlling what gets captured and how it is validated.

## The limitation that matters: reconstructed source is executable input

A payload that carries PHP source is a payload that can carry PHP source you did not write. The README lists cryptographically signed data as a feature, with a link to the 4.x security page, which tells you the maintainers treat this as a real concern rather than a theoretical one. If your serialized closures arrive from a queue message, a cache entry, or any other store an attacker can write to, you are deserializing code, and signing is the control the project offers for that.

The second limitation is narrower and easier to miss. Reconstruction depends on the source being available and parseable at unserialize time. The README states support for PHP 8.0 to 8.5 syntax, which is a range, not a promise about every construct in every future release. A closure that uses syntax newer than the library's parser understands is not a case the README claims to handle.

The third is a fit question. If the callable is a named class method, PHP's ordinary serialization of a class name plus arguments is simpler, has no parser in the loop, and produces a payload you can read in a log. Opis Closure earns its place when the callable genuinely has no name.

## opis/closure against the class-name-and-arguments approach

The realistic alternative is not another serialization library. It is the pattern most PHP queue and cache systems already use: store a class name and a method name, plus a plain array of arguments, and let the worker instantiate the class and call the method. The difference in approach is total. That pattern serializes data and a pointer; opis/closure serializes behaviour.

The pointer approach cannot express a closure that captured three variables from the calling scope, and it cannot express an anonymous class at all. In exchange it never executes reconstructed source, its payloads are inspectable without the library, and it does not need to track PHP syntax releases. If your callable is a method on a class that the worker can autoload, the pointer approach is the smaller system. If it is a closure built at runtime, opis/closure is the one that addresses the actual problem.

## Maintenance, version 4.x migration and the MIT licence

The repository is not archived, and the last push was on 2026-03-05, the same date as the 4.5.0 release. Before that, 4.4.0 landed on 2025-11-20 and 3.7.0 on 2025-07-08, so the 3.x line was still receiving releases alongside 4.x. That matters for upgrade cost: the README states that 4.x is a full rewrite, and that deserializing data written by 3.x is possible, with a dedicated migration page. A rewrite plus a compatibility path is a different upgrade shape from a minor bump, and the migration guide is the document to read before you touch composer.json.

The licence is MIT, declared in the README and present as a LICENSE file at the repository root. MIT is permissive and imposes no obligation on how you distribute your own application. That is a statement about the licence text, not legal advice; if your organisation has a policy on third-party dependencies, the LICENSE file is the artefact to hand to it. The README does not document a rollback procedure for a failed migration.

## Conclusion

Adopt opis/closure when you need to move a closure or an anonymous class across a process boundary and you control both the writer and the reader. Do not adopt it when the serialized payload will be deserialized by code you do not trust, or when a plain array of parameters would express the same intent. Verify three things first: that your PHP version falls inside the 8.0 to 8.5 range the README states, that your captured variables survive a round trip through the functions Opis\Closure\serialize and Opis\Closure\unserialize, and, if the payload crosses a trust boundary, that you have turned on the cryptographically signed data support described in the 4.x security documentation. Version 4.x is a full rewrite, so any 3.x payload you still hold needs the migration guide, not a guess.

## FAQ

### What is opis/closure used for?

It is a PHP library that serializes closures, anonymous classes and arbitrary data, so a callable created in one process can be reconstructed in another. The README's examples show a closure and an anonymous class surviving a serialize and unserialize round trip.

### How do I install opis/closure?

The README gives Composer as the install path, with composer require opis/closure, or an entry in composer.json requiring opis/closure at ^4.5. The stated requirement is PHP 8.0 or newer.

### Does opis/closure need any PHP extensions?

No. The README states that the library does not rely on PHP extensions and specifically rules out FFI or similar dependencies.

### Can opis/closure deserialize data written by version 3.x?

The README says version 4.x is a full rewrite but that data deserialization from 3.x is possible, and it links to a migration guide in the 4.x documentation. The migration page is the source for the exact steps.

### Is opis/closure safe to use with untrusted serialized data?

The README lists cryptographically signed data as a supported feature and links to a security page in the 4.x documentation. Reconstruction produces executable PHP source, so the signing support is the control the project documents for payloads you do not fully trust.

## Sources

- [License: MIT](https://github.com/opis/closure/blob/master/LICENSE)
- [opis/closure on GitHub](https://github.com/opis/closure)
- [Project website](https://opis.io/closure)
- [README](https://github.com/opis/closure/blob/master/README.md)
- [Releases](https://github.com/opis/closure/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/opis-closure
