Is there a preferred way of instantiating a class where most of its methods require a value from the Request object?


#1

I have a question on service-time vs run-time factory instantiation, perhaps, but really more about general OOP in the context of Expressive.

I am refactoring some old code. I have repository layer class that accepts a $quote_id as a constructor parameter, effectively tying a particular quote to the that repository instance.
I thought it was a good thing™, when I wrote it before Expressive, as it locks in the repository instance to a particular Quote object via the $quote_id. Another thing that I think is good is I do not have to specify $quote_id in each and every method of that repository.

However … Expressive does not allow to instantiate a repository this way through constructor at service-time, since there is no Request object yet available, where it is the request that contains a given $quote_id in my app.

One way to overcome this is to:

  1. create a QuoteRepositoryFactory accessible at run-time, where I can instantiate my QuoteRepository at run-time, i.e. in a handler:
    $quoteRepository = $quoteRepositoryFactory->getQuoteRepository($quote_id) $quoteRepository->getSomeData()

    $quoteRepository->getOtherData()

  2. Or I could drop that concept and have a plain QuoteRepository method instantiated at service-time with all the DB trimmings, and at run-time supply $quote_id to each and every method., i.e.
    $quoteRepository->getSomeData($quote_id)

    $quoteRepository->getOtherData($quote_id)


Also I note that I have wrestled with this before … In particular, way #2 was suggested by @ocramius:

My question here is: is one way preferred over another? Are they purely preference-based? Is there a good reason to go with one over the other? Is there any other way?


#2

I can give you my 2 cents…
…the short version…

I see expressive as a left to right pass-trough. aka handlers
At some point you introduce your own logic, aka. business logic, which I see as down and back up.
At that point, within your handler, you start building your VO’s (Quote object) and call your service/repository…
Right there, that’s where you have your factory, accept the VO, and do what needs to be done.
On the way back up (my description for services/repos/models when the job is done), outside of handlers, you can now prepare your return argument.

Long story short, the constructor/factory is not depended on things like the process method.
You can inject whatever you need and use it within the class


#3

Careful: if it is a service, you can inject only things that are immutable/not depending on current execution scope.

https://igor.io/2013/03/31/stateless-services.html


#4

Thanks. The way I understand this, is “do whatever works” with the value object acquired from Request as long as you do it inside the Handler(?).

$quote_id in my case stays within the session, is read by Request, and is the same from one Request to the next, until it is changed via the web app. So I could say that my repository in question depends on the $quote_id value if I initialize it inside the constructor. But then, since $quote_id remains the same throughout the request, created repo as in way #1 is in a way immutable(?) per each request. Can my repo be both immutable but depend on the current execution? For my repo to be entirely stateless maybe way #2 is the way to go, because in #1 the repo is tied to $quote_id…which makes it immutable? I don’t know.

On the way back up (my description for services/repos/models when the job is done), outside of handlers, you can now prepare your return argument.

Going back up though I am not sure on what you mean by handling things outside of the Handler. Are you talking about modifying Response object after the work of Handler is done? I haven’t had a use case for that yet, I have mostly been doing my payload inside the handler itself.


#5

What do I mean… good question.

It was late at night and I was tired :wink:

What Marco is saying and I agree, for your case, (I hope I make sense here and get it right) don’t mess with the responses outside the regular pipeline.
Let’s say, based on the skeleton example, when you reach your HomePageAction.php, that’s where you have your factory/constructor prepared to dive deeper into your “own” workflow. For example into a model to increase to the page request count for a specific user based on the values within your session.
When that is done, you are back in the process method and return the HtmlResponse as usual.
In other words your “addon” services/models shouldn’t care who is calling them, as long as they are called with the information (values) they need in order to work.

To add an example, this part of the pipeline has the UserModel injected.
The UserModel itself doesn’t care for the actual state of the pipeline, all it want’s to know is the username in order to return the data.

Yes, I just typed that up without checking… It sorta, kinda should work, at least as an example.
I hope I make any sense :smile:

    /** @var UserModel */
    private $userModel;

    /**
     * Initialize new instance of User Action
     *
     * @param UserModel    $userModel - ORM UserModel
     */
    public function __construct(UserModel $userModel)
    {
        $this->userModel = $userModel;
    }

    /**
     * {@inheritDoc}
     * @see \Interop\Http\ServerMiddleware\MiddlewareInterface::process()
     */
    public function process(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        if ($request->getMethod() !== RequestMethodInterface::METHOD_GET) {
        }

        $session = new Session();

        if ($session->isEmpty()) {
            return new JsonResponse(
                'No valid Session found',
                StatusCodeInterface::STATUS_UNPROCESSABLE_ENTITY,
                ['Content-Type' => 'application/problem+json']
            );
        }

        /** @var EmployeeMasterByUserName $result */
        $result = $this->userModel->fetch($session->read()['userName']);

        return new JsonResponse($result->jsonSerialize());
    }

Just to add to the madness, whatever is hiding behind UserModel could be moved over to a ZF2/ZF3 project without modification (or maybe with slight modification into SlimPHP or other frameworks). It does not depend on the current execution scope, as Marco pointed out.


#6

That article loses me when he says:

This means that if a service depends on the request, it is not stateless at all.

Not stateless? Isn’t the request is just another input for this particular request/response run? I always think of state as something that’s saved and available during the next request/response (e.g. session).


#7

Take the same application and run two requests (with separate session identifiers) through a single running process. In pseudo-code:

$app = App\Factory::gimme();

$response1 = $app->dispatch($request1);

$response2 = $app->dispatch($request2);

Will it still work? Please see https://igor.io/2013/03/31/stateless-services.html for more details about it.