DEV Community

Cover image for PHP - Elegant method call

PHP - Elegant method call

Ruslan on August 10, 2019

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 t...
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.