• This repository has been archived on 14/Mar/2024
  • Stars
    star
    115
  • Rank 305,916 (Top 7 %)
  • Language
    PHP
  • License
    MIT License
  • Created about 5 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Method overloading in PHP

Overload logo

Method overloading for PHP

Run tests Latest Version on Packagist Total Downloads

NOTE: This is a beta release. It's Adam's original code almost exactly, and his docs; if a lot of folks are interested, we can, as a community, find its limits and edges and where it needs to grow. Please note that, while all credit goes to Adam for writing this, the responsibility for maintaining it is not on him. Tighten will do our best to keep it up, but if this goes anywhere it will be because of community support. This is a beta release and does not carry with it any promise that it doesn't have bugs or holes.

Installation

You can install the package via composer:

composer require tightenco/overload

Usage

This package gives you a declarative way to support multiple signatures for the same method.

Basic Example

Say we have a Ticket class with a holdUntil method that lets us put that ticket on hold until a certain date and time by passing in a DateTime object:

class Ticket extends Model
{
    // ...

    public function holdUntil(DateTime $dateTime)
    {
        $this->update(['hold_until' => $dateTime]);
    }

    // ...
}

...but now you decide it would be convenient if it could also accept a well-formatted date string.

Normally you'd do something like this:

class Ticket extends Model
{
    // ...

    public function holdUntil($dateTime)
    {
        if (is_string($dateTime)) {
            $dateTime = Carbon::parse($dateTime);
        }

        $this->update(['hold_until' => $dateTime]);
    }

    // ...
}

The overloadable trait allows you to essentially pattern match in a declarative way instead of conditionally checking arguments:

class Ticket extends Model
{
    use Overloadable;

    // ...

    public function holdUntil(...$args)
    {
        return $this->overload($args, [
            function (string $dateTime) {
               $this->update(['hold_until' => Carbon::parse($dateTime)]);
            },
            function (DateTime $dateTime) {
               $this->update(['hold_until' => $dateTime]);
            },
        ]);
    }

    // ...
}

If you wanted to avoid that duplication, you could even do this wild recursive madness:

class Ticket extends Model
{
    use Overloadable;

    // ...

    public function holdUntil(...$args)
    {
        return $this->overload($args, [
            function (string $dateTime) {
               $this->holdUntil(Carbon::parse($dateTime));
            },
            function (DateTime $dateTime) {
               $this->update(['hold_until' => $dateTime]);
            },
        ]);
    }

    // ...
}

A cooler example

You might be thinking:

"Uhhh bro, that looks like even more code."

Yeah, because that example is boring. This one is a bit more fun.

I've always wanted Laravel's validate controller helper to accept a closure as its last parameter that let me return whatever HTTP response I wanted if validation failed.

But the method signature for validate takes like a million things and I don't want to pass a ton of empty arrays, for example:

public function store()
{
    //                             Super grim! 😭
    //                                ⬇️  ⬇️
    $this->validate($request, $rules, [], [], function ($errors) {
        return response()->json([
            'someOtherInfo' => 'toInclude',
            'errors' => $errors
        ], 422);
    });
}

I'd love if I could just do:

public function store()
{
    $this->validate($request, $rules, function ($errors) {
        return response()->json([
            'someOtherInfo' => 'toInclude',
            'errors' => $errors
        ], 422);
    });
}

...and have it magically work, knowing I clearly don't care about the $messages or $customAttributes arguments, but can you imagine how gross it would be to add those checks inside the validate method to do all this argument counting and type checking?!

Check out how it would work with this badass trait from the gods:

trait ValidatesRequests
{
    // ...

    public function validate(...$args)
    {
        return $this->overload($args, [
            function ($request, $rules, Closure $callback) {
                return $this->validateRequest($request, $rules, [], [], $callback);
            },
            function ($request, $rules, $messages, Closure $callback) {
                return $this->validateRequest($request, $rules, $messages, [], $callback);
            },
            'validateRequest',
        ]);
    }

    // Move the real logic into a new private function...
    protected function validateRequest(Request $request, array $rules, array $messages = [], array $customAttributes = [], Closure $onErrorCallback = null)
    {
        $validator = $this->getValidationFactory()->make($request->all(), $rules, $messages, $customAttributes);

        if ($validator->fails()) {
            $this->throwValidationException($request, $validator, $onErrorCallback);
        }
    }

    // ...
}

Matching Options

Overloadable doesn't just work with closures; you can do all sorts of crazy stuff!

Check out this example from the test:

class SomeOverloadable
{
    use Overloadable;

    public function someMethod(...$args)
    {
        return $this->overload($args, [
            // Call this closure if two args are passed and the first is an int
            function (int $a, $b) {
                return 'From the Closure';
            },

            // Call this method if the args match the args of `methodA` (uses reflection)
            'methodA',

            // Call this method if the args match the args of `methodB` (uses reflection)
            'methodB',

            // Call methodC if exactly 2 arguments of any type are passed
            'methodC' => ['*', '*'],

            // Call methodD if 3 args are passed and the first is an array
            'methodD' => ['array', '*', '*'],

            // Call methodE if 3 args are passed and the last is a closure
            'methodE' => ['*', '*', Closure::class],
        ]);
    }

    private function methodA($arg1)
    {
        return 'Method A';
    }

    private function methodB(\DateTime $arg1, array $arg2, int $arg3)
    {
        return 'Method B';
    }

    private function methodC($arg1, $arg2)
    {
        return 'Method C';
    }

    private function methodD($arg1, $arg2, $arg3)
    {
        return 'Method D';
    }

    private function methodE($arg1, $arg2, $arg3)
    {
        return 'Method E';
    }
}

Methods are matched in the order they are specified when you call overload.

Notes from Adam's original work

I'm still just hacking around with this and there's probably a bunch of things I'm missing.

For example, it just occurred to me that I haven't really considered how the reflection-based detection stuff should handle optional arguments, and off the top of my head I don't even know what it should do ¯\(ツ)

Either way, I think it's some pretty fun code and I thought it was pretty cool that we could even come up with an API for it at all.

Upcoming plans:

  • Release beta with Adam's exact code
  • Discover known shortcomings and document as issues (for starters, optional arguments and the forthcoming union types)
  • Fix ^^
  • Profit? 🤣 OK, not profit.

Testing

composer test

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

Credits

The idea of method overloading comes from other languages that have it natively. I (Matt) have heard about it multiple times, including from my friend Adam Wathan, so when I decided to finally build something about it, I got a few hours in and then paused and asked Adam if he'd ever seen anyone build it. Turns out... he had.

He sent me a link to this gist. However, Adam didn't want to maintain a package, so, with his blessing, I spun this off to make it more accessible to the rest of the world.

License

The MIT License (MIT). Please see License File for more information.

More Repositories

1

ziggy

Use your Laravel routes in JavaScript.
JavaScript
3,816
star
2

jigsaw

Simple static sites with Laravel’s Blade.
PHP
2,115
star
3

takeout

Docker-based development-only dependency manager. macOS, Linux, and WSL2-only and installs via PHP's Composer... for now.
PHP
1,590
star
4

collect

A Collections-only split from Laravel's Illuminate Support
PHP
1,506
star
5

parental

Use single table inheritance in your Laravel app
PHP
1,327
star
6

mailthief

A fake mailer for Laravel Applications for testing mail.
PHP
685
star
7

lambo

Quick new application creation with Laravel and Valet
PHP
612
star
8

tlint

Tighten linter for Laravel conventions.
PHP
517
star
9

duster

Automatic configuration for Laravel apps to apply Tighten's standard linting & code standards.
PHP
409
star
10

novapackages

PHP
334
star
11

quicksand

Easily schedule regular cleanup of old soft-deleted Eloquent data.
PHP
294
star
12

gistlog

GistLog - simple, easy blogging based on GitHub gists
CSS
274
star
13

symposium

Management of proposals, bios, photos, etc. for conference speakers.
PHP
177
star
14

nova-google-analytics

Google Analytics integration with Laravel Nova
PHP
161
star
15

onramp

Easing the onramp for new or non-PHP developers to become Laravel devs.
PHP
157
star
16

nova-stripe

Easily show information about Stripe charges and balances in your Nova dashboard
JavaScript
103
star
17

giscus

Notifications for Gist Comments
PHP
103
star
18

laravelversions

PHP
94
star
19

jigsaw-blog-template

Starter template for a blog, using Jigsaw by Tighten
Blade
91
star
20

confomo

ConFOMO is a simple tool that makes it easy to track your friends at conferences.
JavaScript
74
star
21

ozzie

Open source project monitor for Tighten
PHP
56
star
22

nova-package-development

A forum for talking about the process of Nova Package Development
52
star
23

jigsaw-docs-template

Starter template for a documentation site, using Jigsaw by Tighten
Blade
44
star
24

craft-build-query

A plugin for Craft CMS, demonstrating how to build complex or optimized queries by modifying an ElementCriteriaModel.
PHP
39
star
25

liftoff

A quick start for Laravel development in a new environment
Shell
38
star
26

builtwithjigsaw

A list of sites built with Jigsaw
Blade
35
star
27

react-gif-search-engine

React GIF Search Engine
JavaScript
33
star
28

react-gif-search-engine-old

JavaScript
32
star
29

jigsaw-site

Jigsaw Documentation Site
Blade
30
star
30

blink-my-lights

Quick proof-of-concept for flashing/blinking lights with Laravel and IFTTT
PHP
26
star
31

laravelm1

Tracking what works and doesn't in the Laravel ecosystem on M1
Blade
25
star
32

configs

Standard config files for Tighten projects
PHP
24
star
33

built-with-laravel

Real organizations and companies building with Laravel.
PHP
24
star
34

nova-releases

A package to provide a card and a tool giving information about Nova releases, including whether you're current
Vue
20
star
35

consoles

Quick shortcut list to API developer consoles
PHP
19
star
36

json-api-examples

Implementing JSON:API in laravel
18
star
37

laravel-react-demo

Demo of how to include React components in a Laravel project with Elixir
PHP
17
star
38

laravel-mix-jigsaw

Laravel Mix plugin for Jigsaw.
JavaScript
16
star
39

nova-package-discovery

Nova card for showing stats from novapackages.com
PHP
16
star
40

simplecast-php

Simplecast PHP SDK
PHP
15
star
41

php-package-skeleton

Tighten's PHP Package skeleton -- inspired by & some source from spatie/skeleton-php
Shell
15
star
42

checkmate

Check the version of all your Laravel apps and notify if they're out of date
PHP
14
star
43

tighten-coding-standard

A PHP Code_Sniffer configuration for Tighten's coding standard.
PHP
14
star
44

saasaas

SaaSaaS - create your next "AirBnB for ___" idea
PHP
14
star
45

laravel-elixir-webpack-react

A package to provide support for compiling React JSX files in Laravel Elixir 6
JavaScript
13
star
46

podcast-subscriber

Easy podcast subscriber app
PHP
10
star
47

laravel-mix-react

Easy alternative Laravel Mix configuration for projects using React
JavaScript
10
star
48

laravel-preset-jest

Front-end presets that include Jest for React and Vue
PHP
9
star
49

laraveldrivers

List all third-party Laravel drivers
PHP
9
star
50

blade-style-guide

"PSR-Blade"--style guide for Laravel Blade
9
star
51

easy-embeddable-polls

easy-static-polls
Vue
8
star
52

valet-fallback

Fallback web site for Valet
PHP
8
star
53

phpreleases-action

GitHub action that integrates with the PHP Releases API.
7
star
54

dev-battle-1-react

Tighten Co. Senior Dev Battle - React app
PHP
7
star
55

dev-battle-1-vue

Tighten Co. Senior Dev Battle - Vue app[
JavaScript
6
star
56

duster-action

Dockerfile
6
star
57

react-native-time-input

A simple time input component with autoformatting.
TypeScript
6
star
58

jigsaw-collections-demo

CSS
5
star
59

tlint-plugin

TLint PHPStorm Plugin
Kotlin
4
star
60

gif-gif-vue

Samantha Geitz Jest testing in Vue Laracon talk
PHP
4
star
61

hyperspoon

Lua
4
star
62

gif-gif-react

React version of Samantha Geitz's Jest Testing Laracon talk
PHP
4
star
63

postit

PHP
3
star
64

laravel-elixir-elm

JavaScript
3
star
65

pickr-api

PHP
3
star
66

laravel-tricks

Laravel Tricks rewrite
PHP
3
star
67

tallstack

The new web site for tallstack.dev
PHP
2
star
68

jazz

JavaScript-inspired development utilities
PHP
2
star
69

nova-tighten-themes

Tighten's Nova Themes
CSS
2
star
70

learnwebdevelopment

LearnWebDevelopment.org
CSS
2
star
71

tlint-sublime-plugin

tlint-sublime-plugin
Python
2
star
72

intro-to-react

Intro to React workshop given by Samantha Geitz at PeersConf 2018
JavaScript
2
star
73

omgphp

OMGPHP.com
CSS
1
star
74

borsh-php

1
star
75

phpreleases

API endpoints with support information for PHP major/minor versions and releases 5.6 and later
PHP
1
star
76

pickr-nativescript

NativeScript version of Pickr for Tighten Dev Battle 2
CSS
1
star
77

automation-scripts

Lua
1
star
78

pickr-reactnative

JavaScript
1
star
79

mon-petit

PHP
1
star
80

whyphp

CSS
1
star
81

laravelstar

Vue
1
star
82

dev-battle-landing

Landing page for the Tighten Dev Battle
PHP
1
star