Beyond If-Else: Smarter Function Dispatch in PHP
How to Implement and Package Multiple Dispatch for PHP—A Complete Practical Walkthrough
Part 1: Meet Multiple Dispatch — Your Secret Weapon
Let’s face it: PHP has a reputation. It’s often seen as that old-school, slightly messy language we all once wrote some Wordpress plugins with and quickly tried to forget about. But what if I told you that PHP is secretly cool, secretly powerful, and secretly functional?
Intrigued? Good—because today, I’ll let you in on a little secret: Multiple Dispatch.
With Multiple Dispatch, you can extend any package’s behavior without touching its code—just add new classes and methods, following the Open-Closed Principle in a way classical OOP can’t match.
Yes, that’s right, multiple dispatch—a technique usually reserved for sophisticated languages like Julia, CLOS (Common Lisp Object System), or those hipster functional languages everyone pretends to understand. Today, I’ll show you how we can harness this power for PHP and drastically simplify our lives as developers.
So, grab your coffee, crack your knuckles, and follow me into PHP’s shiny hidden lair.
Wait—What’s Multiple Dispatch Anyway?
Picture this:
You’re building an app that handles collisions between different game objects. Sometimes it’s a spaceship hitting an asteroid, sometimes it’s a player bumping into a treasure chest, or even a cat colliding with a sofa (not that unusual scenario if you own cats). How do you elegantly handle all these combinations without drowning in a nightmare of if-else chains or endless switch-case madness?
Let's Use Multiple Dispatch!
Multiple dispatch allows your function to dynamically pick the right implementation based on the types of multiple arguments—not just one (as traditional polymorphism does). It’s like having a Swiss Army knife that automatically pulls out the right tool depending on what you’re trying to fix.
Instead of writing this:
function collide($a, $b) {
if ($a instanceof Cat && $b instanceof Sofa) {
return "cat-meets-sofa logic";
} else if ($a instanceof Spaceship && $b instanceof Asteroid) {
return "kaboom logic";
} else if ($a instanceof Player && $b instanceof TreasureChest) {
return "treasure logic";
}
// and so on, forever...
}
You simply define implementations for each scenario separately, and PHP picks the right one automatically:
$collide = multidispatch();
$collide[[Cat::class, Sofa::class]] = fn($cat, $sofa) => "Fluffy destroys the sofa!";
$collide[[Spaceship::class, Asteroid::class]] = fn($ship, $asteroid) => "Kaboom! Space fireworks!";
$collide[[Player::class, TreasureChest::class]] = fn($player, $chest) => "You found gold!";
Now your collision logic is tidy, readable, and super easy to extend.
You might say it’s dispatching happiness (pun absolutely intended).
But Wait—PHP Doesn’t Do Multiple Dispatch, Right?
Good catch! By default, PHP is single-dispatch and heavily class-based, meaning methods are selected based only on the first object’s type (think $this). But here’s the kicker: with a little PHP wizardry, we can simulate true multiple dispatch elegantly.
That’s exactly what we’ll build together.
Getting Started: Setting Up Your PHP Workspace
First things first, let’s set up our project like true professionals. We’ll create a clean PHP workspace with Composer, some tests, and Git, so our multiple dispatch solution isn’t just powerful—it’s also neat, maintainable, and shareable.
Here’s how you do it. Open your terminal and let’s go step by step:
mkdir php-multidispatch
cd php-multidispatch
# Initialize Git
git init
# Create essential directories
mkdir src tests examples
# Create composer.json
nano composer.json
Paste this inside your composer.json:
{
"name": "yourname/php-multidispatch",
"description": "Multiple dispatch made easy for PHP",
"type": "library",
"license": "MIT",
"autoload": {
"psr-4": {
"Multidispatch\\": "src/Multidisptch/"
},
"files": ["src/Multidispatch/Multidispatch.php"]
},
"require-dev": {
"phpunit/phpunit": "^12.2"
}
}
Hit CTRL+O
, save, then CTRL+X
to exit. Let’s unpack quickly:
- autoload: Composer will auto-magically include our files, no manual requires.
- require-dev: We’ll test everything using
PHPUnit
. Trust me, it saves lives.
Now, install Composer dependencies:
composer install
Commit your setup:
git add --all
git commit -m "Initial project setup"
See? You’re halfway there already.
Writing Your Very First Multiple Dispatch Function
Create your main PHP file:
nano src/Multidispatch.php
Here’s your entry ticket to multiple dispatch glory:
<?php
namespace Multidispatch;
use ArrayAccess;
use Exception;
class Multidispatch implements ArrayAccess
{
private array $methods = [];
public function __invoke(...$args)
{
if (is_array($args[0] ?? null) && is_callable($args[1] ?? null)) {
return $this->offsetSet($args[0], $args[1]);
}
$types = array_map([$this, 'getTypeName'], $args);
$method = $this->resolve($types);
if (!$method) {
throw new Exception("No method for types: " . implode(', ', $types));
}
return $method(...$args);
}
// ArrayAccess methods
public function offsetSet($offset, $value): void
{
$key = implode(',', $offset);
$this->methods[$key] = $value;
}
public function offsetExists($offset): bool
{
$key = implode(',', $offset);
return isset($this->methods[$key]);
}
public function offsetGet($offset): callable
{
$key = implode(',', $offset);
return $this->methods[$key] ?? throw new Exception("No method for $key");
}
public function offsetUnset($offset): void
{
$key = implode(',', $offset);
unset($this->methods[$key]);
}
// Helper methods for dispatching
private function getTypeName($arg): string
{
if (is_object($arg)) return ltrim(get_class($arg), '\\');
$type = gettype($arg);
return match ($type) {
'integer' => 'int',
'double' => 'float',
'boolean' => 'bool',
'string', 'array', 'resource', 'NULL' => $type,
default => '*'
};
}
private function resolve(array $types): ?callable
{
$chains = array_map([$this, 'getChain'], $types);
foreach ($this->generateCombinations($chains) as $combo) {
$key = implode(',', $combo);
if (isset($this->methods[$key])) {
return $this->methods[$key];
}
}
$defaultKey = implode(',', array_fill(0, count($types), '*'));
return $this->methods[$defaultKey] ?? null;
}
// Generate type hierarchies
private function getChain(string $type): array
{
if ($type === '*') return ['*'];
if (class_exists($type)) {
return array_unique([
$type,
...class_parents($type),
...class_implements($type),
'*'
]);
}
return [$type, '*'];
}
// Create combinations
private function generateCombinations(array $chains): array
{
if (empty($chains)) return [[]];
$rest = $this->generateCombinations(array_slice($chains, 1));
$result = [];
foreach ($chains[0] as $type) {
foreach ($rest as $r) {
$result[] = array_merge([$type], $r);
}
}
return $result;
}
}
function multidispatch(): Multidispatch {
return new Multidispatch();
}
Save and exit.
Part 2: Testing, Real Examples, and the Art of Dispatching Elegance
In the first part, we laid the foundations of a powerful multiple dispatch system in PHP. If you haven’t checked it out yet—go back and read it. Seriously, it’s worth it.
Today we’re going deeper. We’ll test the hell out of our dispatch system, build some real-world examples, and explore how beautifully multiple dispatch handles scenarios that would typically leave us pulling our hair out.
Put on your lab coat—it’s testing time.
Step 0: Install PHP
This is how you quickly install PHP in your OS:
Linux (Ubuntu)
sudo apt update
sudo apt install -y php php-cli php-xml php-zip php-curl phpunit composer
I highly recommend, however, to install first phpenv
and then manage different versions of PHP as described here for Linux.
Windows (Scoop)
If you’re on Windows, install Scoop first, then run:
scoop bucket add extras
scoop install php composer
I highly recommend also here to install first phpenv
and then manage different versions of PHP as described here for Windows.
MacOS (Brew + GitHub)
One is tempted to do:
brew install php composer
But I would recommend to install first phpenv
and then be able to install and manage - switch between - different PHP versions. For this, check out our earlier detailed installation guide here.
Step 1: Install and Configure PHPUnit (Because Real Coders Test)
Every developer’s nightmare scenario is deploying untested code. Don’t worry, we won’t let that happen. PHPUnit
is your testing buddy who will ensure everything works as it should.
Let’s get it running:
composer require --dev phpunit/phpunit
Now, create your first test file:
nano tests/MultidispatchTest.php
Here’s a solid test setup. Let’s break things (but in a controlled way):
<?php
use PHPUnit\Framework\TestCase;
use function Multidispatch\multidispatch;
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
final class MultidispatchTest extends TestCase
{
public function testClassDispatch()
{
$fn = multidispatch();
$fn[[Dog::class, Dog::class]] = fn($a, $b) => 'Dog vs Dog';
$fn[[Animal::class, Animal::class]] = fn($a, $b) => 'Animal fight';
$this->assertEquals('Dog vs Dog', $fn(new Dog(), new Dog()));
$this->assertEquals('Animal fight', $fn(new Cat(), new Dog()));
}
public function testScalarDispatch()
{
$fn = multidispatch();
$fn[['int', 'string']] = fn($a, $b) => "$a and $b";
$this->assertEquals('1 and test', $fn(1, 'test'));
}
public function testDefault()
{
$fn = multidispatch();
$fn[['*', '*']] = fn($a, $b) => "default";
$this->assertEquals('default', $fn([], new stdClass()));
}
}
This file tests multiple dispatch with class instances, scalar types, and even a catch-all case. It’s like a Swiss Army knife of tests—covering all essential scenarios.
Step 2: Running Tests and (Hopefully) Seeing Green
Run your tests:
composer dump-autoload -o
./vendor/bin/phpunit tests
If everything works, you’ll see a glorious output similar to:
PHPUnit 12.2.5 by Sebastian Bergmann and contributors.
...
OK (3 tests, 3 assertions)
You did it! Tests are passing, meaning you have solid multiple dispatch functionality ready to go.
But let’s take a closer look at what’s going on here.
Why Our Tests Matter (Explained Simply)
The test above did something magical:
- Test 1 (testClassDispatch): Checks if PHP correctly selects the most specific function based on object types. It’s ensuring our dispatch engine respects class hierarchies.
- Test 2 (testScalarDispatch): Ensures dispatching works not only for classes but also PHP scalar types (int, string, etc.).
- Test 3 (testDefault): Verifies that a default “catch-all” case works if no specific matches are found.
This means our dispatch engine handles classes, scalar types, inheritance, and default fallback elegantly. Beautiful, isn’t it?
Step 3: Using Your Multiple Dispatch in a Real-Life Scenario
Now let’s see multiple dispatch in action. Create a simple real-world example:
nano examples/example.php
Here’s a fun scenario: a collision system for a game (told you it was coming back!):
<?php
require __DIR__ . '/../vendor/autoload.php';
use function Multidispatch\multidispatch;
class Spaceship {}
class Asteroid {}
class Cat {}
class Sofa {}
$collide = multidispatch();
$collide[[Spaceship::class, Asteroid::class]] = fn($ship, $asteroid) => "Kaboom! Goodbye spaceship.";
$collide[[Cat::class, Sofa::class]] = fn($cat, $sofa) => "Fluffy found her new scratching post!";
$collide[[Cat::class, Asteroid::class]] = fn($cat, $asteroid) => "Cat rides asteroid. Internet explodes!";
echo $collide(new Spaceship(), new Asteroid()) . "\n";
echo $collide(new Cat(), new Sofa()) . "\n";
echo $collide(new Cat(), new Asteroid()) . "\n";
Run this example with:
php examples/example.php
You’ll see output that perfectly fits each scenario:
Kaboom! Goodbye spaceship.
Fluffy found her new scratching post!
Cat rides asteroid. Internet explodes!
Isn’t multiple dispatch incredible? You effortlessly matched different types, without messy if-else spaghetti.
Step 4: Commit and Tag Your Version (Like a True Pro)
Always commit your beautiful working code:
git add --all
git commit -m "Multiple dispatch tested and working!"
And create a Git tag to mark your achievement:
git tag v0.1.0
git push origin main --tags
Now your multiple dispatch package is ready to be shared with the PHP world!
Part 3: Packaging, Publishing, and Sharing Your Dispatch Masterpiece
So far, you’ve built a powerful multiple dispatch system, rigorously tested it, and even launched a few cats into space—figuratively, at least. Now it’s time to do something even more exciting: share your brilliant creation with the PHP community.
Today, I’ll show you how to package your code properly, publish it on Packagist, and make it super easy for other developers to install and enjoy your dispatch magic.
Step 1: Final Touches and Composer Setup
Before we launch your creation, let’s ensure everything is set up perfectly. Open your composer.json file and make sure it includes these key details:
nano composer.json
Here’s a tidy, production-ready setup:
{
"name": "gwangjinkim/php-multidispatch",
"description": "Simple and elegant multiple dispatch for PHP with hierarchy resolution.",
"type": "library",
"license": "MIT",
"autoload": {
"psr-4": {
"Multidispatch\\": "src/Multidispatch/"
},
"files": ["src/Multidispatch/Multidispatch.php"]
},
"require": {
"php": "^8.1"
},
"require-dev": {
"phpunit/phpunit": "^12.2"
},
"authors": [
{
"name": "Gwang-Jin Kim",
"email": "gwang.jin.kim.phd@gmail.com"
}
],
"minimum-stability": "stable"
}
Why the "files" key? Remember earlier, PHP wasn’t finding your global multidispatch
function automatically? Specifying the file explicitly here means Composer includes it during autoloading. No more mysterious errors!
Save and regenerate your autoload files:
composer dump-autoload -o
Nice and optimized!
Step 2: Publishing on Packagist (Time for Fame!)
Your code deserves attention—let’s give it some! Packagist is PHP’s primary package repository and works beautifully with Composer.
- Push Your Code to GitHub
Ensure your GitHub repository is up to date:
git add --all
git commit -m "Ready to publish on Packagist"
git push origin main
git push origin main --tags
- Submit to Packagist
- Visit packagist.org and log in with your GitHub account.
- Click on “Submit” at the top.
- Enter your GitHub repository URL:
https://github.com/gwangjinkim/php-multidispatch
Click “Check” and then confirm your submission.
Voilà! Your package is now public and ready for everyone to install.
Step 3: How Your Users Will Install Your Package
With your package now publicly available, installation becomes effortless. Anyone can install your dispatch system with this one simple command:
composer require gwangjinkim/php-multidispatch
Step 4: Showcasing Usage in a README
Make your users’ life easier by providing crystal-clear documentation. Let’s quickly spice up your README.md
:
nano README.md
Here’s a concise yet friendly example:
# php-multidispatch
> Multiple dispatch for PHP — scalar-aware, class hierarchy-sensitive, and elegant. Inspired by Julia an$
## Features
- ✅ Multiple dispatch on all arguments
- ✅ Supports scalar types (`int`, `string`, etc.)
- ✅ Resolves using class hierarchy
- ✅ Wildcard fallback (`'*'`)
- ✅ Short syntax: `$fn[['int', 'string']] = fn(...)`
- ✅ Named functions via helper
---
## Installation
```bash
composer require gwangjin/php-multidispatch
```
---
## Usage
```php
use function Multidispatch\multidispatch;
$collide = multidispatch();
$collide[[Spaceship::class, Asteroid::class]] = fn($ship, $asteroid) => "Kaboom! Goodbye spaceship.";
$collide[['int', 'string']] = fn($a, $b) => "$a meets $b";
$collide[['*', '*']] = fn($a, $b) => "default collision";
echo $collide(new Spaceship(), new Asteroid()); // "Kaboom! Goodbye spaceship."
echo $collide(42, 'Answer'); // "42 meets Answer"
echo $collide([], new stdClass()); // "default collision"
```
---
## Named Generics (Optional)
```php
function greet() {
static $g = null;
return $g ??= multidispatch();
}
greet()[['string']] = fn($x) => "Hello $x!";
echo greet()("world"); // Hello world!
```
---
## Run Tests
```bash
composer install
composer test
```
Or:
```bash
composer install
./vendor/bin/phpunit tests
```
---
## License
MIT © Gwang-Jin Kim
---
Step 5: Maintenance and Future Improvements
You’ve officially launched your package, but a creator’s work never truly ends. Here are some ideas for future updates:
- Performance Optimizations: Consider caching dispatch resolutions.
- Better Error Messages: Make debugging even easier for your users.
- Additional Features: Implement type hints and method inspection for automatic validation.
Feel free to explore and expand. Your package is out there, ready to grow!
Why This Matters (And Why You’re Now Officially Awesome)
You’ve just built, tested, packaged, and published a robust multiple dispatch system in PHP.
With this package, developers around the world can easily write more readable, elegant code. You’ve given them a tool that dramatically simplifies complex dispatching logic without resorting to convoluted if-else chains or cumbersome switch statements.
Multiple Dispatch lets you import a package and seamlessly extend its behavior — without ever modifying its internal code, tests, or method definitions. Just add your own classes and methods. This powerful approach fully embraces the Open-Closed Principle — something that’s virtually impossible with traditional Java or C++-style OOP.
That, my friend, is powerful. And now, you’re officially awesome.
Wrapping It Up (But Just the Beginning)
Throughout this journey, you’ve learned to:
- Build clean, elegant dispatching mechanisms.
- Implement comprehensive tests ensuring code reliability.
- Publish your package professionally for the world to use.
You’ve come a long way, and I’m proud of you! Your code is now out there, making PHP projects everywhere better.
Feel accomplished—you’ve earned it.
That’s it! You’ve completed your epic dispatch journey. Take a moment, breathe, and appreciate what you’ve done—PHP developers everywhere thank you!
Now, go forth and dispatch with style.
Ready to Try It Yourself?
You’ve just learned how to build a powerful PHP package from scratch, mastered the nuances of multiple dispatch, and tamed the chaos of PHP environments across Mac, Linux, and Windows. Now it’s your turn:
- Fork the repo: gwangjinkim/php-multidispatch on GitHub
- Install via Composer: gwangjinkim/php-multidispatch on Packagist
- Star it if you like the idea!
Play around, break things, improve them, and open a pull request—or just use it in your next project to instantly level up your PHP codebase.
Curious about how it could fit into your real-world projects? Want to see a specific feature? Drop a comment, create an issue, or reach out. There’s a whole world of elegant, maintainable PHP code just waiting to be written—and you’re at the forefront.
Go ahead—make PHP awesome. The community (and your future self) will thank you for it.