DEV Community

Cover image for PHP - Elegant method call
Ruslan
Ruslan

Posted on • Updated on

PHP - Elegant method call

Often I find myself in a situation where I have a class in which I only have one public method and want to call it. Let's imagine we have a class to send mails to a specific user

class SendMail
{
    public function handle(User $user)
    {
        //
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we can call the method as follows

(new SendMail)->handle($this->user);
Enter fullscreen mode Exit fullscreen mode

but I don't like the noise for initiating a new object. Is there perhaps a better solution? πŸ€”

What if we include a tiny bit PHP-Magic ✨ and add a static function that creates the object for us and calls the method? Like this

class SendMail
{
    public function handle(User $user)
    {
        //
    }

    public static function execute(User $user)
    {
        (new static)->handle($user);
    }
}
Enter fullscreen mode Exit fullscreen mode

With the new static method we can call it like this now

SendMail::execute($this->user);
Enter fullscreen mode Exit fullscreen mode

Hooray πŸ™Œ this looks much cleaner in my eyes. But why are we going to all this trouble now? πŸ™„

(new SendMail)->handle($this->user);
// vs
SendMail::execute($this->user);
Enter fullscreen mode Exit fullscreen mode

Both lines do the same thing and readers with a trained eye would now ask why not just execute the code in the static method instead of creating a new object. Depending on the functionality it is not necessary to do it. However in my experience most functions are not as simple like in our example.

Have I sparked your interest? Just give it a try. Maybe you also like it πŸ˜‰

Edit:
Thank you all for the feedback πŸ‘Œ dev.to is really great 😁

I have taken a very simplified example in this article and added further explanation when to use this pattern in the following comment https://dev.to/suddenlyrust/comment/e58i

Top comments (20)

Collapse
 
jamiesonroberts profile image
Jamieson Roberts • Edited

Is there a reason you wouldn't just use the magic method __invoke()?

Example:

class Something
{
    public function __invoke($params)
    {
        // do something here
    }
}

And to invoke it you simply do

    $something = new Something($params);
Collapse
 
suddenlyrust profile image
Ruslan

Thank you for your comment. I tried your example. But somehow the invoke method is never called πŸ€”. Could you help me with that?

Here is a link to a "Online PHP Compiler": repl.it/repls/DisastrousViciousFeed

class Something
{
    public function __invoke($params)
    {
          var_dump('Hi there πŸ‘‹. I invoked myself 😁');
    }
}

$something = new Something(['dev.to', 'is', 'really', 'cool']);

var_dump('Did the invoke work? πŸ˜•');
Collapse
 
vlasales profile image
Vlastimil Pospichal • Edited
$something = Something($params);

Edit: Sorry, the right way is

$something = new Something($params); // Create object
echo $something(5); // Call the object as a function
Collapse
 
suddenlyrust profile image
Ruslan

I tried your example but it returns an Fatal Error. Do you have a working snippet for me?

Here is a link to a "Online PHP Compiler": repl.it/repls/ImpressionableCuddly...

class Something
{
  public function __invoke($params)
  {
    var_dump('Hi there πŸ‘‹. I invoked myself 😁');
  }
}

$something = Something(['dev.to', 'is', 'really', 'cool']);

var_dump('Did the invoke work? πŸ˜•');
Thread Thread
 
jamiesonroberts profile image
Jamieson Roberts

Try the following as a replacement of line 11.

$something = Something;
$something(['dev.to', 'is', 'really', 'cool']);
Thread Thread
 
suddenlyrust profile image
Ruslan

Still getting following fatal error

PHP Fatal error: Uncaught Error: Call to undefined function Something() in /home/runner/main.php:12

What am I missing? πŸ˜–

repl.it/repls/EssentialMarvelousWe...

Thread Thread
 
jamiesonroberts profile image
Jamieson Roberts • Edited

You are missing the class instantiation (new), but I think that is my fault, I copied the wrong thing.

<?php

class Something
{
  public function __invoke($params)
  {
    var_dump('Hi there πŸ‘‹. I invoked myself 😁');
  }
}

$something = new Something();
$something(['dev.to', 'is', 'really', 'cool']);

var_dump('Did the invoke work? πŸ˜•');
Thread Thread
 
suddenlyrust profile image
Ruslan

It's working now πŸ₯³ we found the missing key to the puzzle. The invoke variant looked promising but at the end it still requires 2 lines of code.

But thank you Jamieson for showing me this variant. Did not know how to call the magic invoke method #TIL. This can be handy for sure 🀝

Collapse
 
klauenboesch profile image
Christian • Edited

I think this approach is bad (for like 99% of the cases, like this one). How are you going to parametrize your send process? How are you going to switch from SMTP to something else?

If you would have properly structured the code, you would you DI (maybe an interface). When using static methods, you will never be able to easily switch the implementation. You are locked into your current implementation.

Collapse
 
suddenlyrust profile image
Ruslan • Edited

Thank you for your feedback Christian. Yeah for the use case of sending mails this makes no sense. It is a very simplified example. I use this method in the following case. Imagine you have a pretty big class. Like 500 or more lines and it is pretty hard to read it. You have many functions that perform different tasks. Now you can use this pattern to split the class in different little classes. Let's call the little classes "actions". Each action you can call now on your own and refactor it out of the big class.

// BigClass.php
class BigClass
{
    public function handle()
    {
        // Imagine a 500 line big file and plenty of functions

        // We have to parse a big XML-File

        // We have to harmonize the parsed Information

        // We have to store the values in different locations
    }
}

We can now refactor it like this.

// RefactoredBigClass.php
class RefactoredBigClass
{
    public function handle()
    {
        ParseXmlAction::execute();

        HarmonizeInformationAction::execute();

        StoreInformationAction::execute();
    }
}

// ParseXmlAction.php
class ParseXmlAction
{
    public function handle()
    {
        //
    }

    public static function execute()
    {
        (new static)->handle();
    }
}

// HarmonizeInformationAction.php
class HarmonizeInformationAction
{
    public function handle()
    {
        //
    }

    public static function execute()
    {
        (new static)->handle();
    }
}

// StoreInformationAction.php
class StoreInformationAction
{
    public function handle()
    {
        //
    }

    public static function execute()
    {
        (new static)->handle();
    }
}

In my eyes this helps to clean up my big classes. You get following benefits.

  • In the big class you can see very fast what is happening. What actions are executed here.
  • Every Action is now in a separate file and has a single task to do. No functions that are not used to solve the action.
  • You can reuse Actions in different places if it is required.
  • The best part is. You can test every action isolated.

I hope this explanation gives you a better understanding.

Collapse
 
klauenboesch profile image
Christian

While I understand your case (and I, for sure, have also written such code) I have two main concerns about your example:

  1. You say β€ža huge class that performs many tasksβ€œ. Iβ€˜ll just refer to β€žSingle Responsibility Principleβ€œ here: 1 Class -> 1 Purpose/Task

  2. How would you ever write a Unit test for your RefactoredBigClass? There is no way you could ever test your handle() function there.

Thread Thread
 
suddenlyrust profile image
Ruslan

Thank you for your concern, Christian.
At your first point: I don't know what you mean by that πŸ€”
And to your second point: I'm not unit testing this class 😁

Thread Thread
 
klauenboesch profile image
Christian

For the first point, see: scotch.io/bar-talk/s-o-l-i-d-the-f...
For the second point: Well, that's your fault ;)

Collapse
 
vlasales profile image
Vlastimil Pospichal

This is a wrong way in my opinion. You need split data structures too, coupled with methods.

Collapse
 
maxkruse profile image
Maximilian Kruse

this is exactly why people say "OOP is stupid and not used correctly". The object in this case has no business with any of its functions, its just a namespace-delivery-system.

A simple function send_email() would be enough, this adds about 5x the bloat and you dont even create objects, just call a single static function.

Collapse
 
suddenlyrust profile image
Ruslan

Thank you for your feedback Maximillian. You are absolutely right. For the use case of sending a mail this pattern does not have any benefit.

Further explanation of this approach if you are interested 😊dev.to/suddenlyrust/comment/e58i

Collapse
 
vlasales profile image
Vlastimil Pospichal • Edited

Better way:

$sendmail = new Sendmail();
$sendmail->handle($this->user);

Why? You may inject object $sendmail into another method. You may simple replace this object with $nullObject or $stubObject when you want run tests.

Collapse
 
maxart2501 profile image
Massimo Artizzu

I generally support this, as it can also allow extra management logic like sharing object instances (like what's usual with object factories so common in Java).

As long as the number of arguments is kept at an acceptable minimum, that is.

Collapse
 
dguhl profile image
D. Guhl

I find this approach difficult to maintain ever once somebody introduces a parametrized constructor: you would have to change the calls in the static methods on every change of the signature.

Collapse
 
vuong profile image
vuong β›ˆοΈ

I was accepting this idea in the past, but for now I think it makes me confusing between concept of static function and 'normal' function. Finally, keep it simple and open the eyes is better solution, IMO.