HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

Call Method from Multiple Implementations

ShadowdareShadowdare r_j MVP

Say I have an addon with an interface called IFun with a couple method declarations, one being doThis(). There's a sub-folder called "FunImplementations" with classes implementing this interface inside the addon. If necessary and if Vanilla doesn't autoload these classes, I could require them in the addon's bootstrap.php.

Next, I have some code in the addon that should call doThis() from each class that implements IFun in that sub-folder. How would you implement this? My main question is: is there a way with Garden's factory methods? Otherwise, reflection, go by file names, or something else?

Add Pages to Vanilla with the Basic Pages app

Comments

  • R_JR_J Ex-Fanboy Munich Admin

    As far as I have understood it, using factory is deprecated and dependency injection is used instead: https://github.com/vanilla/vanilla/issues/7459

    Maybe you find some inspiration looking
    1. at the new Container class in /vendor/vanilla/garden-container/src/Container.php or
    2. directly at the repo: https://github.com/vanilla/garden-container

  • ShadowdareShadowdare r_j MVP
    edited December 2018

    I didn't know that the Garden Container was added. This is amazing! Thanks for the info, @R_J! =)

    I came up with a solution for this issue using this. In my case, FunImplementations should be loaded dynamically as more can be dropped into the folder at any time.

    Knowing that Vanilla autoloads classes before the addon's bootstrap, specifically including files in addon sub-folders named "library" and "settings":

    • I created a FunLoader class in "library". I set DI rules for FunLoader to make it a shared instance (singleton) in the addon's bootstrap.php. FunLoader autoloads files in the "FunImplementations" folder, stores their class names in an array if they implement IFun, and creates DI rules for them to be singletons (for my use case).
    • Some code in the addon (e.g., in an event handler) retrieves the FunLoader instance from the container and grabs the list of FunImplementations from it. From here, the code loops through the list of FunImplementations, instantiates a FunService (that has an argument for IFun) and passes a FunImplementation to it by retrieving an instance from the container. FunService is meant to be a transient, so I don't need to have it in the DI container.

    Add Pages to Vanilla with the Basic Pages app

  • R_JR_J Ex-Fanboy Munich Admin

    I guess I would have done it the other way around: instead of creating a "loader" class, I would have created a "container" class and added a register() method to the interface, forcing each implementation to add itself to the container.

    From the addon I then would have taken a look at the container and which implementations have registered. But in the end I assume there is not much difference.

  • ShadowdareShadowdare r_j MVP
    edited December 2018

    @R_J, that might be cleaner than storing their class names in an array, which I used for retrieving each shared instance that gets instantiated when get($className) is called on the Garden container. In both cases, the files would still have to be loaded/required from the "FunImplementations" folder, but the Loader code could be moved to bootstrap.php and also store info of the implementations in a "container" class.

    The first way seems to only create the initial instances when get($className) is called, which might not be every page depending on the event handler, so less memory is used if there's a lot of FunImplementations.

    In your example, adding a register() method to the implementations (not the "container" class) would lend itself better to extending an abstract class along with the interface. Would the container class hold instances of each implementation when they're registered?

    Add Pages to Vanilla with the Basic Pages app

  • R_JR_J Ex-Fanboy Munich Admin

    I "grew up" with computers that had very limited memory resources and therefore I always tend to store only what is needed. I would only store the class name and use the addon itself as the container.
    I even thought of using Gdn::set('FunImplementations', ... as the container :mrgreen:

    But from my understanding the clean approach would have something to do with inversion of control, dependency injection and all those buzz words and therefore I assume a container class with objects in it would be considered as best practice. And since Vanilla has a class Container, I gave that hint, not because I had a clean approach using that container class in my mind...

Sign In or Register to comment.