Skip to content

Conversation

@live627
Copy link
Contributor

@live627 live627 commented Apr 1, 2024

Motivation

To cut down on the use of static properties as much as possible and implement well-defined methods for adding sub-actions. The goal here would be to hook into one entry point probably in SMF\Forum where new functions could be implemented that return the instance of the action class.

Definition

This is the interface for future reference (I suck at naming things):

/**
 * Interface for all action classes to register their sub-actions.
 *
 * Any action class that delegates tasks to different sub-actions should
 * implement this interface and use  ProvidesSubActionTrait.  Use the public
 * methods defined in this interface to aad sub-actions.
 */
interface ProvidesSubActionInterface
{
	/****************
	 * Public methods
	 ****************/

	/**
	 * Set a default sub-action.
	 *
	 * The default sub-action is the one executed if no sub-action was requested.
	 *
	 * @param string $sa
	 */
	public function setDefaultSubAction(string $sa): void;

	/**
	 * Add a sub-action.
	 *
	 * A sub-action is another term for a route where a URL parameter, usually "sa", has
	 * its value mapped to some function.
	 *
	 * @param string $sa
	 * @param callable $cb
	 */
	public function addSubAction(string $sa, callable $cb): void;

	/**
	 * Determines whether the provided sub-action exists.
	 *
	 * @param string $sa
	 *
	 * @return bool
	 */
	public function hasSubAction(string $sa): bool;

	/**
	 * Finds a sub-action that is associated with the given keyword.
	 *
	 * If no sub-actions match or nothing was provided, the internal variable is set to
	 * either the previously specified default or the first registered sub-action.
	 *
	 * @param string|null $sa
	 */
	public function findRequestedSubAction(?string $sa = null): void;

	/**
	 * @return string|null
	 */
	public function getSubAction(): ?string;

	/**
	 * Executes the sub-action.
	 *
	 * @uses findRequestedSubAction() if $sa is null.
	 *
	 * @param string|null $sa
	 *
	 * @return mixed
	 */
	public function callSubAction(?string $sa = null): mixed;
}

Action objects that use sub-actions are encouraged to implement this.

Example Usage

<?php

namespace SMF;

class SomeActionClass implements ActionInterface, ProvidesSubActionInterface
{
    use ActionTrait;
    use ProvidesSubActionTrait;
    use BackwardCompatibility;

    public function __construct()
    {
        // Add sub-actions
        $this->addSubAction('doSomething', [$this, 'doSomething']);
        $this->addSubAction('doAnotherThing', [$this, 'doAnotherThing']);

        // Set a default sub-action
        $this->setDefaultSubAction('doSomething');
    }

    /**
     * Execute method to call the appropriate sub-action
     */
    public function execute(): void
    {
        // Call the sub-action based on 'sa' request parameter
        $this->callSubAction($_REQUEST['sa'] ?? null);
    }

    /**
     * An example sub-action method
     */
    public function doSomething(): void
    {
        // Implementation of the sub-action
        echo "Doing something!";
    }

    /**
     * Another example sub-action method
     */
    public function doAnotherThing(): void
    {
        // Implementation of the sub-action
        echo "Doing another thing!";
    }
}

In this example:

  • The SomeActionClass implements ProvidesSubActionInterface and uses ProvidesSubActionTrait.
  • Sub-actions are added using the addSubAction method.
  • A default sub-action is set using the setDefaultSubAction method.
  • The execute method calls the appropriate sub-action based on the 'sa' request parameter.
  • The sub-action methods (doSomething and doAnotherThing) are defined within the class.

This should give you a basic idea of how to use the provided trait and interface.

@live627 live627 added the Housekeeping SMF code reorganization label Apr 1, 2024
@live627 live627 self-assigned this Apr 1, 2024
@live627 live627 force-pushed the subaction-interface branch from 74b3920 to 10e6949 Compare April 2, 2024 14:17
@live627 live627 force-pushed the subaction-interface branch from 10e6949 to 99321ad Compare May 7, 2024 03:29
@live627
Copy link
Contributor Author

live627 commented May 7, 2024

@tyrsson I would like your thoughts on this implementation, how it might work with PSR-14. Should we stay with the current way off plastering hooks everywhere (nearly one for each sub-action implementation) or would a single entry point be best, where a conditional check for the right class instance must be made? Or perhaps this approach is bad and should be scrapped?

@tyrsson
Copy link
Collaborator

tyrsson commented May 7, 2024

Will try to look into it tomorrow. Here is some general thoughts on it though.

Your psr 14 events/listeners can become first class actors rather than simply a means to get arrays passed around.

The subactions could actually be executed as a psr 14 event. Mod authors then just attach listeners to said event. If they have a reason to do so.

ie
$eventIdentifier = 'some.subaction'; // fires at priority = 0
Given two listeners, one at priority = -1 and one at priority = 1 we can then run code before and after some.subaction with ZERO other modifications to the code based solely on the priority of the attached listener. Works great for stuff like caching and such.

You are introducing a SubActionInterface paired with an expressing trait. Composition over inheritance. I like it. The application should only care that the expressing class honors the contract defined by the interface. That should be the only required check. Now, to get more specific than that you would need more than a single interface. Where subaction interface would be an extension of other interfaces. Say a ConfigVarAwareInterface, which means it's capable of returning a configvar array for consumption. That the application can depend on. It's the contract defined by that interface. Only an instanceof check is required to determine if this action/subaction provides any.

As you are doing. SMF needs to be built to the interface, mod authors should be the only ones (generally speaking) typing more specific than the interface.

As a general rule of thumb. SMF classes do about a 1000 more things than they should. SRP is a thing for a reason ;)

@tyrsson
Copy link
Collaborator

tyrsson commented May 10, 2024

Another minor thought in regards to naming. Its not very SMF but I would suggest either.

SubActionProviderInterface
or
SubActionAwareInterface

@live627
Copy link
Contributor Author

live627 commented Jul 23, 2024

@Sesquipedalian can I have your thoughts on this please? Do you think this could be a good change, having an interface to explicitly build sub-actions with?

@live627 live627 force-pushed the subaction-interface branch from 99321ad to 133e42e Compare January 7, 2025 05:37
@live627 live627 force-pushed the subaction-interface branch from 133e42e to cb2b903 Compare February 8, 2025 21:09
@jdarwood007
Copy link
Member

I like the idea, although I would have just called it SubActionInterface to line up the naming convention of ActionInterface. It feels like it needs a folder to group them, such as "SMF\Routable" or something. I would then suggest also implementing one for "area".

@jdarwood007 jdarwood007 changed the title Subaction interface [3.0] Subaction interface Oct 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Housekeeping SMF code reorganization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants