Note #1: I think you should read through this post. I will emphasize a core part of this answer: "The model is not a class or any single object"
Note #2: IMO this problem is something inherent to web MVC implementations. The first part is how I would go about this problem; the following describes conceptually why this problem exists.
My answer is: neither.
The MVC architecture is divided into 2 layers: the model layer and the presentation layer. A service object (these reside within the model layer) should instantiate/save pertinent captcha information to the client state. Since this is likely a web application, the presentation layer -- specifically a view object -- will need to request this client state from the model layer. Whether this happens through a service object or a presentation-specific data access object specially-tailored for client state responses is dependent on your implementation & best fit. It is here, within the view, where I would save the client state to the session via another data access object or a (Cookie/Session)Response
abstraction*. In this way, the view doesn't "access" the session data structure, but rather utilizes a component (the presentation-layer DAO or (Cookie/Session)Response
abstraction*) to do this work. Though we don't think of cookies/sessions as presentation ("they aren't visual"), the presentation layer is responsible for responses, which cookies/sessions are nothing short of.
It is difficult for me to speculate how this would go in your framework specifically, since there seems to be a conflation of the model layer. Your ContactModel
would certainly want the hash/captcha to save the client state. Your view would need neither of these, but an additional mechanism to "respond" the client state to the HTTP request in the form of a cookie/session.
* Symfony is generally helpful for this. I use both the presentation-layer DAO and a Symfony HttpFoundation
object, which may be overboard.
Conceptually: sessions as state vs. response
The fundamental problem you're tackling here is specific to web MVC implementations. Presentation layer in web MVC processes a "request" (delegated to the controller) and presents a "response" (decided upon by the view). In HTTP, cookies and sessions are sent with requests and altered in responses. This is embedded in the HTTP request/response nature.
If we put the session/cookie saving in the model layer, we are making our model layer dependent on HTTP and allowing it to "respond" to requests. Even if we abstract the superglobals and "inject" abstractions (like Symfony cookie/session objects), there is inherent dependence on cookies/sessions which are request/response implementations. You can do this all in the model layer, but I prefer not to. In other words, though the model is responsible for keeping track of states, the cookie/session state is actually a response to a request.
"But this is leaky logic since you are saving state in the presentation layer"
I think you can look at all methods as being somewhat "leaky" and this is just the most cohesive way. Sure we are "saving" state in presentation layer if you look at cookie/session as state. If you look at them as response, which they truly are in the eyes of MVC, then this is not leaky at all. I would argue that sending a response (saving the session/cookie state) in the model layer is worse.