PHPackages                             gwangjinkim/php-multidispatch - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. gwangjinkim/php-multidispatch

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

gwangjinkim/php-multidispatch
=============================

Simple and elegant multiple dispatch for PHP with hierarchy resolution.

v0.3.1(12mo ago)17MITPHP

Since Jul 2Pushed 11mo agoCompare

[ Source](https://github.com/gwangjinkim/php-multidispatch)[ Packagist](https://packagist.org/packages/gwangjinkim/php-multidispatch)[ RSS](/packages/gwangjinkim-php-multidispatch/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (1)Versions (7)Used By (0)

php-multidispatch: Multiple Dispatch (with CLOS Extensions) for PHP
===================================================================

[](#php-multidispatch-multiple-dispatch-with-clos-extensions-for-php)

[![Packagist](https://camo.githubusercontent.com/e9b0248b4af44bd2eac58c61b28b3b2641c5e37f83e58852896514b9af0604af/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6777616e676a696e6b696d2f7068702d6d756c74696469737061746368)](https://packagist.org/packages/gwangjinkim/php-multidispatch)[![Tests](https://github.com/gwangjinkim/php-multidispatch/actions/workflows/tests.yml/badge.svg)](https://github.com/gwangjinkim/php-multidispatch/actions)

A modern, readable, CLOS-inspired multiple dispatch library for PHP—now with support for :primary, :before, :after, :around, and call-next-method! Open-Closed Principle in action, no fuss.

- **GitHub:** [gwangjinkim/php-multidispatch](https://github.com/gwangjinkim/php-multidispatch)
- **Packagist:** [gwangjinkim/php-multidispatch](https://packagist.org/packages/gwangjinkim/php-multidispatch)

---

What is Multiple Dispatch? (And Why Should You Care?)
-----------------------------------------------------

[](#what-is-multiple-dispatch-and-why-should-you-care)

Multiple dispatch lets you write functions whose behavior depends on the *runtime types* of *all* their arguments—not just the first one (like in classic OOP). Imagine polymorphism that’s not boxed into inheritance hierarchies, with the freedom to extend methods for new types, even across package boundaries.

**Why is this awesome?**

- Write cleaner, more declarative code (no tangled if-else or switch statements).
- Add new behaviors from *outside* existing packages—no patching or hacking core code.
- Follow the Open-Closed Principle: your code is open for extension, closed for modification.
- Real power: this is what makes Julia, CLOS (Common Lisp), and R so extensible.

**Multiple Dispatch allows you to import a package (which uses Multiple Dispatch) and extend it without ever having to touch or change its internal code (tests, definitions), but just add more classes and more methods/functions. It allows you to follow open-closed principle which would be impossible with classical Java/C++ OOP systems.**

---

Features
--------

[](#features)

- **True Multiple Dispatch** (not just single dispatch)
- **CLOS-style Method Combinations:** :primary, :before, :after, :around, and `$callNext`
- **Inheritance &amp; Interface Awareness:** Works with classes, interfaces, built-in types
- **Fallbacks:** Register wildcard methods for default cases
- **Simple API:** Intuitive, modern PHP code
- **Configurable Dispatch Policy**: Choose “first-wins” or “last-wins” for registration order.

---

Table of Contents
-----------------

[](#table-of-contents)

1. [Installation](#installation)
2. [Classic Usage (No CLOS Extensions)](#classic-usage-multiple-dispatch-the-simple-way-no-clos-extensions)
3. [CLOS Extensions: :primary, :before, :after, :around, and call-next-method](#clos-style-method-combination)
4. [Extended Example: CLOS in PHP](#extended-clos-example)
5. [Testing](#testing)
6. [Troubleshooting](#troubleshooting)
7. [Links &amp; References](#links--references)

---

Installation
------------

[](#installation)

### With Composer

[](#with-composer)

```
composer require gwangjinkim/php-multidispatch:dev-main
```

Use `:dev-main` to get the newest version! Specify version numbers when you want to downgrade or pin a version.

Or add to your `composer.json`:

```
"require": {
    "gwangjinkim/php-multidispatch": "dev-main"
}
```

Eventually use `"^0.3.1"` e.g., to pin to a version.

### Requirements

[](#requirements)

- PHP 8.1 or newer recommended

### PHP Installation Shortcuts

[](#php-installation-shortcuts)

- **MacOS:** See [this detailed guide](#) (insert your own article link here)
- **Linux (Ubuntu):**```
    sudo apt update
    sudo apt install -y php php-cli php-xml php-zip php-curl phpunit composer
    ```
- **Windows (with Scoop):**```
    scoop install php composer
    ```

---

Dispatch Policy: first-wins vs last-wins
----------------------------------------

[](#dispatch-policy-first-wins-vs-last-wins)

You control which matching handler "wins" when there are multiple equally-specific possibilities (for example, two interfaces, or multiple wildcards).

- **First-wins:** The first registered method for a type signature is chosen (classic OOP behavior).
- **Last-wins:** The last registered method is chosen (suits override-style patterns).

**Default:** `last-wins` for maximum extensibility, but you can switch any dispatcher at any time:

```
$fn = multidispatch();
$fn->setDispatchPolicy(\GwangJinKim\Multidispatch\DispatchPolicy::FirstWins);
// ...or...
$fn->setDispatchPolicy(\GwangJinKim\Multidispatch\DispatchPolicy::LastWins);
```

Use `DispatchPolicy::FirstWins` for backwards compatibility or stricter method resolution.

PolicyWhich handler wins?When to usefirst-winsFirst registeredStrict/classic OOP, legacy, backward-compatlast-winsLast registered (default)Override style, plugin/extensible systems---

Classic Usage: Multiple Dispatch the Simple Way (No CLOS Extensions)
--------------------------------------------------------------------

[](#classic-usage-multiple-dispatch-the-simple-way-no-clos-extensions)

If you just want plain, classic multiple dispatch—no :before, :after, or :around—your code stays as simple and clean as before. Everything you knew still works, and it’s all backwards-compatible!

Here’s how you use the package the “old-school” way (like before CLOS extensions):

```
require "vendor/autoload.php";
use function GwangJinKim\Multidispatch\multidispatch;

interface IA {}
interface IB {}
class CA implements IA, IB {}
class CB implements IA, IB {}

$fn = multidispatch();

// Register different handlers for types
$fn[['IA']] = fn($a) => "This is IA";
$fn[['IB']] = fn($a) => "This is IB";
$fn[['*']]  = fn($a) => "Default!";

// Usage: dispatch based on type
echo $fn(new CA()); // Depending on registration/interface, returns "This is IA" or "This is IB"
echo $fn(new CB()); // Ditto

// Also works for built-in types:
$scalarDispatch = multidispatch();
$scalarDispatch[['int', 'string']] = fn($x, $y) => "Int: $x, String: $y";
echo $scalarDispatch(1, "hello"); // Int: 1, String: hello

// You can always register a general handler:
$fn[['*']] = fn($a) => "Fallback/default handler";
echo $fn([]); // Fallback/default handler
```

**How it works:**

- You register handlers for each type signature (single or multiple arguments, e.g. \['int', 'string'\]).
- On call, the dispatcher figures out the most specific match (based on type, class, interface, and inheritance).
- The handler runs.
- No CLOS extensions means there’s one winner, no call\_next, and no :before/:after/:around stacking.
- This style is familiar if you know Python’s functools.singledispatch or Julia’s basic dispatch.

**When to use:**

- You want simple, one-winner dispatch logic.
- You’re porting code from a previous version of this package.
- You don’t need CLOS-style hooks or method chaining (yet).

> **Note:**You can always choose whether registration order is first-wins or last-wins, per-dispatcher, using `setDispatchPolicy`. The default is `last-wins`, but all older code is fully supported.

```
$fn = multidispatch();
$fn->setDispatchPolicy(\GwangJinKim\Multidispatch\DispatchPolicy::FirstWins);
// ...or...
$fn->setDispatchPolicy(\GwangJinKim\Multidispatch\DispatchPolicy::LastWins);
```

---

Why CLOS? (And What Makes It Different?)
----------------------------------------

[](#why-clos-and-what-makes-it-different)

Why all this talk about CLOS (the Common Lisp Object System)?
CLOS is the most advanced, flexible object system ever built—letting you combine, stack, override, and wrap behavior with surgical precision.
With these extensions, you can:

- **Dynamically stack hooks** before/after/around any method, for any type (logging, validation, timing, security, profiling, tracing, etc).
- **Control composition:** Choose which method(s) run and how their results are combined—no more black-box dispatch!
- **Open-Closed Principle, but supercharged:** Extend *any* package, for *any* type, without touching its code.
- **Stay DRY:** Cross-cutting concerns (security, logging, caching) are isolated, not tangled up in your logic.

It’s a friendlier, more composable way to program.
And it’s 100% optional—you can stick with classic style if you like!

CLOS-Style Method Combination
-----------------------------

[](#clos-style-method-combination)

Inspired by the Common Lisp Object System (CLOS), you can now combine multiple method types:

- **:primary** – The main methods, as before
- **:before** – Run before the primaries (in specificity order)
- **:after** – Run after the primaries (in reverse specificity)
- **:around** – Can wrap everything (get `$callNext` as first argument; call it to proceed)
- **call-next-method** – Provided to :around methods as `$callNext` (you must call it to continue the chain)

**Registration Syntax:**

```
$fn[ [Type1, ...], ':before' ] = function ($a, ...) { /* ... */ };
$fn[ [Type1, ...], ':primary' ] = function ($a, ...) { /* ... */ };
$fn[ [Type1, ...], ':after'  ] = function ($a, ...) { /* ... */ };
$fn[ [Type1, ...], ':around' ] = function ($callNext, $a, ...) { /* ... */ };
```

**Example:**

```
require "vendor/autoload.php";
use function GwangJinKim\Multidispatch\multidispatch;

interface IA {}
class A implements IA {}
class B extends A {}

$fn = multidispatch();

$fn[ [A::class], ':primary' ] = fn($a) => "Primary A";
$fn[ [A::class], ':before'  ] = fn($a) => print("Before A\n");
$fn[ [A::class], ':after'   ] = fn($a) => print("After A\n");
$fn[ [A::class], ':around'  ] = function($callNext, $a) {
    print("Around A (before)\n");
    $result = $callNext($a);
    print("Around A (after)\n");
    return $result;
};

echo $fn(new A());
```

**Output:**

```
Before A
Around A (before)
Primary A
Around A (after)
After A
Primary A

```

**How the Method Combination Works:**

1. All matching :before methods are called, from most specific to least.
2. All :around methods wrap the chain (most specific is outermost).
3. The most specific :primary method is called.
4. All matching :after methods are called, from least specific to most.

You can combine :before, :after, :primary, and :around on any type signature.

---

Extended CLOS Example
---------------------

[](#extended-clos-example)

Here is a full, extended example to clarify usage and show best practices—great for both CLOS and non-CLOS users.

```
require "vendor/autoload.php";
use function GwangJinKim\Multidispatch\multidispatch;

// Interfaces & classes
interface Animal {}
class Dog implements Animal {}
class Cat implements Animal {}

// Create a dispatcher
$battle = multidispatch();

// Register primary methods
$battle[[Dog::class, Dog::class], ':primary']   = fn($a, $b) => "Dog vs Dog: Bark!";
$battle[[Animal::class, Animal::class], ':primary'] = fn($a, $b) => "Generic animal fight";

// Before and after hooks
$battle[[Dog::class, Dog::class], ':before'] = fn($a, $b) => print("Sniffing each other\n");
$battle[[Dog::class, Dog::class], ':after']  = fn($a, $b) => print("Wagging tails\n");

// Around (wrapping the dispatch, controlling the flow)
$battle[[Animal::class, Animal::class], ':around'] = function($callNext, $a, $b) {
    print("Arena lights up\n");
    $result = $callNext($a, $b);
    print("Audience cheers\n");
    return $result;
};

// Run dispatch!
echo $battle(new Dog(), new Dog());
echo $battle(new Cat(), new Dog());
```

**What you'll see:**

```
Sniffing each other
Arena lights up
Dog vs Dog: Bark!
Audience cheers
Wagging tails
Arena lights up
Generic animal fight
Audience cheers

```

**How to Think About Each Method Type:**

- **:primary:** The "main event"—your usual handler, one per match.
- **:before:** Side-effects you want to happen *before* the main event, e.g., logging, setup, animation, etc.
- **:after:** Side-effects *after* the main event, e.g., cleanup, logging, summary.
- **:around:** Like a decorator or middleware: can do stuff before/after and even decide not to call the rest at all (by not calling `$callNext`).
- **call-next-method:** Only available inside :around. Lets you control the chain, e.g., by running or skipping underlying methods.

---

### Detailed CLOS Example With Explanations

[](#detailed-clos-example-with-explanations)

```
require "vendor/autoload.php";
use function GwangJinKim\Multidispatch\multidispatch;

// Define interfaces and classes
interface IA {}
class CA implements IA {}

// Make a dispatcher
$fn = multidispatch();

// :before hook (runs before primary)
$fn[[[CA::class], ':before']] = function($a) {
    echo "Logging before CA\n";
};
// :primary (main logic)
$fn[[[CA::class], ':primary']] = function($a) {
    echo "Handling CA\n";
    return "RESULT";
};
// :after hook (runs after primary)
$fn[[[CA::class], ':after']] = function($a) {
    echo "Cleanup after CA\n";
};
// :around wrapper (can see and control the whole call chain)
$fn[[[CA::class], ':around']] = function($callNext, $a) {
    echo "Start outer transaction\n";
    $result = $callNext($a); // This calls before/primary/after
    echo "End outer transaction\n";
    return "[[[$result]]]";
};

echo $fn(new CA()); // Try it out!
```

Output:

```
Logging before CA
Start outer transaction
Handling CA
Cleanup after CA
End outer transaction
[[[RESULT]]]
```

What’s happening?

- All hooks (:before, :after, :around) are run in the right order.
- $callNext in :around lets you run or skip the rest.
- You can combine as many hooks and layers as you want!

---

> **Pro tip:** Want several :around? Just register more! They stack “outside-in” (the last registered is the innermost).

---

Testing
-------

[](#testing)

Run your tests with PHPUnit:

```
./vendor/bin/phpunit tests
```

You’ll see all your dispatch rules and method combinations in action. Both classic and CLOS-style tests are included.

---

Troubleshooting
---------------

[](#troubleshooting)

- **Function not found errors?** Make sure you've run `composer dump-autoload` and are loading via `vendor/autoload.php`.
- **Order of registration:** If you register multiple methods for the same types, the *most specific* (class, subclass, interface) wins.
- **:around method argument order:** `$callNext` must come first.

If you get stuck, check out [the examples](examples/example.php) or open an issue.

---

Links &amp; References
----------------------

[](#links--references)

- [Official Packagist Package](https://packagist.org/packages/gwangjinkim/php-multidispatch)
- [Project on GitHub](https://github.com/gwangjinkim/php-multidispatch)
- [Installing PHP with phpenv (detailed guide)](https://medium.com/devops-dev/installing-php-8-with-phpenv-the-hard-way-but-right-920a0a8ea1e5?sk=7aa96bfe9bd8b5e3896833d91fb0eaa8)
- [Beyond if-else: Smarter function dispatch in PHP (Medium)](https://medium.com/data-science-collective/beyond-if-else-smarter-function-dispatch-in-php-814dadaf3600?sk=41c34e6c95134f653a494a0173f30026)
- [CLOS on Wikipedia](https://en.wikipedia.org/wiki/Common_Lisp_Object_System)
- [Julia Multiple Dispatch](https://docs.julialang.org/en/v1/manual/methods/)

---

Contributors / Authors
----------------------

[](#contributors--authors)

- Gwang-Jin Kim
- David J. Schwarz

---

Call to Action
--------------

[](#call-to-action)

**Ready to write cleaner, more extensible PHP—no more “if-else soup”?
Try it out!**

- Please star ⭐ the [GitHub project](https://github.com/gwangjinkim/php-multidispatch)!
- Try it out in your next side project
- File issues or request features or share feedback for improvements or contribute extensions

Whether you want classic dispatch, CLOS power features, or both, you’re covered.
Enjoy dispatching!

###  Health Score

25

—

LowBetter than 35% of packages

Maintenance50

Moderate activity, may be stable

Popularity6

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity31

Early-stage or recently created project

 Bus Factor1

Top contributor holds 87.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

4

Last Release

364d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/1bcb5d345f51641c9b1e0a1489658224bc9ffe8d456d4ab3ad2aba2c47e0a3f9?d=identicon)[gwangjinkim](/maintainers/gwangjinkim)

---

Top Contributors

[![gwangjinkim](https://avatars.githubusercontent.com/u/32566039?v=4)](https://github.com/gwangjinkim "gwangjinkim (34 commits)")[![d4s6](https://avatars.githubusercontent.com/u/2222258?v=4)](https://github.com/d4s6 "d4s6 (5 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/gwangjinkim-php-multidispatch/health.svg)

```
[![Health](https://phpackages.com/badges/gwangjinkim-php-multidispatch/health.svg)](https://phpackages.com/packages/gwangjinkim-php-multidispatch)
```

###  Alternatives

[myparcelnl/magento

A Magento 2 module that creates MyParcel labels

1860.2k](/packages/myparcelnl-magento)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
