0

The language is PHP. I have the certain, in my opinion, code smell, it doesn't feel right: In my controller class that ingests objects of the same type (interface, it's ViewBlockInterface), I check if these certain objects have certain methods and based on that, do stuff, if not, just skip that object in the array.

if( method_exists( $loaded_module, 'getBlockViewStylesFilePath' ) ) {
    //logic with $loaded_module->getBlockViewStylesFilePath()
}

The problem with this approach is that, unless you read the documentation (which, of course, almost no one does), you won't know about it. I don't want to throw errors, but in my interface, ViewBlockInterface, I don't want to define these, because it's not necessary that every block have CSS / scripts attached to it.

In short, I want the developer to know that if he defines these functions, stuff will happen, but it's not required to have them. I also believe that the scope of this sub-system is so small that I don't want to create 4+ interfaces just for this.

What can I do here?

7
  • 1
    what kind of crazy non type safe language is this?
    – Ewan
    Commented Apr 10, 2019 at 14:16
  • @Ewan PHP. 5.6 at that. Can't use 7.0.
    – coolpasta
    Commented Apr 10, 2019 at 14:21
  • Ho will the developers know what methods they must implement without reading the documentation? Commented Apr 10, 2019 at 14:21
  • @Goyo Well, with an interface, the script just breaks. If they're trying to create an object that's, first, not of the interface I need: it breaks, second, if it doesn't respect the interface, then it breaks again, but here lies the issue: I don't want it to break if they don't implement these optional functions, yet I'd still like to let them know that they exist.
    – coolpasta
    Commented Apr 10, 2019 at 14:22
  • you could use constructor overloading... oh wait
    – Ewan
    Commented Apr 10, 2019 at 14:28

3 Answers 3

5

I would prefer using interfaces to checking method_exists like your example. First, the getBlockViewStylesFilePath should be defined in a dedicated interface. Both the interface and the method should be documented to tell other developers what happen when implementing this interface in their classes.

interface HavingStyleInterface {
    /**
     * PHPDoc here
     */
    public function getBlockViewStylesFilePath() {};
}

Next, instead of checking the existence of a method, I would prefer to check if an object implements an interface. This check supports type-hinting in IDEs and can be refactored easier.

if ($loaded_module instanceof HavingStylesInterface) {
    // do stuff
}

Now, you can tell other developers that if he/she implements the HavingStylesInterface, something will happen. Obviously, implementing that interface is optional.

Finally, remember the I in SOLID principles. Multiple small interfaces are prefered than a big one.

7
  • I believe this is overengineering of my code. I really don't care if an object is, let's say I made them implementing a "loadCssFilePath" by another interface, each small thing will then be bound to an interface that's honestly just useless. I don't care if an object is of the "BlockStyles" interface.
    – coolpasta
    Commented Apr 10, 2019 at 14:25
  • I didn't say that we should create a new interface for every public method that the class implements. My main point is that checking interface implementation is better (clearer and easier to maintain) than checking the existence of a method by method_exists.
    – Hieu Le
    Commented Apr 10, 2019 at 14:34
  • I agree, but that's an issue in my case: I don't want to create these interfaces, as such, I will not be able to do an instanceof for these optional methods I'd want others to know about. Makes sense?
    – coolpasta
    Commented Apr 10, 2019 at 14:36
  • 3
    I got your point now! Personally, I feel good with implementing multiple interfaces in one class. In fact, if the developer wants to implement the getBlockViewStylesFilePath method in their class, by introducing the HasStylesInterface you also help him/her to know how to define the method signature correctly.
    – Hieu Le
    Commented Apr 10, 2019 at 15:00
  • 2
    @coolpasta "my system only cares that you're ViewBlockInterface, not that you're HasStylesInterface" Actually, your system does care about that interface, its just an implicit, hidden interface. When you check "does method DoX exist", you're checking if the object is implementing a specific (conceptual) "interface", just without using a (technical) interface. Having an interface to enforce your "interface" makes everything easier - and just costs you 4 lines of code per interface; totally worth it in my opinion. Commented Apr 11, 2019 at 11:55
0

Since the question originally did not mention PHP (just a code block without language specification), I've tried to come up with a couple of solutions in a few languages I've used:

  • Objective-C's version of interfaces, called 'protocols', can have optional methods.

  • In Swift, you can define a protocol extension with empty functions as default implementations for optional methods. They can (but do not have to be) overridden by other developers using the protocol (see here).

  • Java, version 8 and above, allow interfaces to have default methods which, like the Swift case, you should leave empty but might be overridden by other developers.

-1

I would go with construction parameters

Provide a default constructor and overload it so you can optionally pass in more functions. All your objects can have the function, but by default it does nothing

class A 
{ 
    function __construct() 
    { 
        $a = func_get_args(); 
        $i = func_num_args(); 
        if (method_exists($this,$f='__construct'.$i)) { 
            call_user_func_array(array($this,$f),$a); 
        } 
    } 

    function __construct1($getBlockViewStylesFilePath) 
    { 
        //set optional function to be what was passed in
    } 

} 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.