How should a model be structured in MVC? [closed]
Asked Answered
M

5

578

I am just getting a grasp on the MVC framework and I often wonder how much code should go in the model. I tend to have a data access class that has methods like this:

public function CheckUsername($connection, $username)
{
    try
    {
        $data = array();
        $data['Username'] = $username;

        //// SQL
        $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";

        //// Execute statement
        return $this->ExecuteObject($connection, $sql, $data);
    }
    catch(Exception $e)
    {
        throw $e;
    }
}

My models tend to be an entity class that is mapped to the database table.

Should the model object have all the database mapped properties as well as the code above or is it OK to separate that code out that actually does the database work?

Will I end up having four layers?

Manvell answered 3/5, 2011 at 0:28 Comment(6)
Why are you catching exceptions just to throw them again?Solitude
@Elias Van Ootegem: huh? if it works with rethrow, it means that an upper layer catches the exception. But if there is one, then it would have catched it without that pointless rethrow... (if you still don't get it, please mock up a small test code)Hypercorrect
@Elias Van Ootegem: I have no idea what you're talking about, not handling an exception on a specific layer doesn't mean it will halt the app. please construct (or more precisely: fail to construct) a code example where that rethrow is necessary. let's stop this offtopic conversation, pleaseHypercorrect
@drrcknlsn: that's a valid argument, but in that case at least catch the exception you expect to be thrown, the generic Exception doesn't have much documentation value. Personally if I went down on that road I would choose PHPDoc's @exception, or some similar mechanism, so it shows up in the generated documentation.Hypercorrect
@KarolyHorvath: I don't disagree, necessarily, at least in the cases where only a specific exception could be thrown, but sometimes there could literally be dozens of different possible exceptions, and catching or documenting each of them explicitly would be somewhat impractical if your only intent is to rethrow them. If you're anticipating only one specific exception, or the library you're wrapping is using good exception handling and inheritance itself, then doing so would definitely be preferred.Disafforest
It is perfectly acceptable (and sometimes common) to catch exceptions to re-throw them again as a different exception, depending on what your object api's state are the thrown exceptions.Belovo
B
963

Disclaimer: the following is a description of how I understand MVC-like patterns in the context of PHP-based web applications. All the external links that are used in the content are there to explain terms and concepts, and not to imply my own credibility on the subject.

The first thing that I must clear up is: the model is a layer.

Second: there is a difference between classical MVC and what we use in web development. Here's a bit of an older answer I wrote, which briefly describes how they are different.

What a model is NOT:

The model is not a class or any single object. It is a very common mistake to make (I did too, though the original answer was written when I began to learn otherwise), because most frameworks perpetuate this misconception.

Neither is it an Object-Relational Mapping technique (ORM) nor an abstraction of database tables. Anyone who tells you otherwise is most likely trying to 'sell' another brand-new ORM or a whole framework.

What a model is:

In proper MVC adaptation, the M contains all the domain business logic and the Model Layer is mostly made from three types of structures:

  • Domain Objects

    A domain object is a logical container of purely domain information; it usually represents a logical entity in the problem domain space. Commonly referred to as business logic.

    This would be where you define how to validate data before sending an invoice, or to compute the total cost of an order. At the same time, Domain Objects are completely unaware of storage - neither from where (SQL database, REST API, text file, etc.) nor even if they get saved or retrieved.

  • Data Mappers

    These objects are only responsible for the storage. If you store information in a database, this would be where the SQL lives. Or maybe you use an XML file to store data, and your Data Mappers are parsing from and to XML files.

  • Services

    You can think of them as "higher level Domain Objects", but instead of business logic, Services are responsible for interaction between Domain Objects and Mappers. These structures end up creating a "public" interface for interacting with the domain business logic. You can avoid them, but at the penalty of leaking some domain logic into Controllers.

    There is a related answer to this subject in the ACL implementation question - it might be useful.

The communication between the model layer and other parts of the MVC triad should happen only through Services. The clear separation has a few additional benefits:

  • it helps to enforce the single responsibility principle (SRP)
  • provides additional 'wiggle room' in case the logic changes
  • keeps the controller as simple as possible
  • gives a clear blueprint, if you ever need an external API

 

How to interact with a model?

Prerequisites: watch lectures "Global State and Singletons" and "Don't Look For Things!" from the Clean Code Talks.

Gaining access to service instances

For both the View and Controller instances (what you could call: "UI layer") to have access these services, there are two general approaches:

  1. You can inject the required services in the constructors of your views and controllers directly, preferably using a DI container.
  2. Using a factory for services as a mandatory dependency for all of your views and controllers.

As you might suspect, the DI container is a lot more elegant solution (while not being the easiest for a beginner). The two libraries, that I recommend considering for this functionality would be Syfmony's standalone DependencyInjection component or Auryn.

Both the solutions using a factory and a DI container would let you also share the instances of various servers to be shared between the selected controller and view for a given request-response cycle.

Alteration of model's state

Now that you can access to the model layer in the controllers, you need to start actually using them:

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $identity = $this->identification->findIdentityByEmailAddress($email);
    $this->identification->loginWithPassword(
        $identity,
        $request->get('password')
    );
}

Your controllers have a very clear task: take the user input and, based on this input, change the current state of business logic. In this example the states that are changed between are "anonymous user" and "logged in user".

Controller is not responsible for validating user's input, because that is part of business rules and controller is definitely not calling SQL queries, like what you would see here or here (please don't hate on them, they are misguided, not evil).

Showing user the state-change.

Ok, user has logged in (or failed). Now what? Said user is still unaware of it. So you need to actually produce a response and that is the responsibility of a view.

public function postLogin()
{
    $path = '/login';
    if ($this->identification->isUserLoggedIn()) {
        $path = '/dashboard';
    }
    return new RedirectResponse($path); 
}

In this case, the view produced one of two possible responses, based on the current state of model layer. For a different use-case you would have the view picking different templates to render, based on something like "current selected of article" .

The presentation layer can actually get quite elaborate, as described here: Understanding MVC Views in PHP.

But I am just making a REST API!

Of course, there are situations, when this is a overkill.

MVC is just a concrete solution for Separation of Concerns principle. MVC separates user interface from the business logic, and it in the UI it separated handling of user input and the presentation. This is crucial. While often people describe it as a "triad", it's not actually made up from three independent parts. The structure is more like this:

MVC separation

It means, that, when your presentation layer's logic is close to none-existent, the pragmatic approach is to keep them as single layer. It also can substantially simplify some aspects of model layer.

Using this approach the login example (for an API) can be written as:

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $data = [
        'status' => 'ok',
    ];
    try {
        $identity = $this->identification->findIdentityByEmailAddress($email);
        $token = $this->identification->loginWithPassword(
            $identity,
            $request->get('password')
        );
    } catch (FailedIdentification $exception) {
        $data = [
            'status' => 'error',
            'message' => 'Login failed!',
        ]
    }

    return new JsonResponse($data);
}

While this is not sustainable, when you have complicate logic for rendering a response body, this simplification is very useful for more trivial scenarios. But be warned, this approach will become a nightmare, when attempting to use in large codebases with complex presentation logic.

 

How to build the model?

Since there is not a single "Model" class (as explained above), you really do not "build the model". Instead you start from making Services, which are able to perform certain methods. And then implement Domain Objects and Mappers.

An example of a service method:

In the both approaches above there was this login method for the identification service. What would it actually look like. I am using a slightly modified version of the same functionality from a library, that I wrote .. because I am lazy:

public function loginWithPassword(Identity $identity, string $password): string
{
    if ($identity->matchPassword($password) === false) {
        $this->logWrongPasswordNotice($identity, [
            'email' => $identity->getEmailAddress(),
            'key' => $password, // this is the wrong password
        ]);

        throw new PasswordMismatch;
    }

    $identity->setPassword($password);
    $this->updateIdentityOnUse($identity);
    $cookie = $this->createCookieIdentity($identity);

    $this->logger->info('login successful', [
        'input' => [
            'email' => $identity->getEmailAddress(),
        ],
        'user' => [
            'account' => $identity->getAccountId(),
            'identity' => $identity->getId(),
        ],
    ]);

    return $cookie->getToken();
}

As you can see, at this level of abstraction, there is no indication of where the data was fetched from. It might be a database, but it also might be just a mock object for testing purposes. Even the data mappers, that are actually used for it, are hidden away in the private methods of this service.

private function changeIdentityStatus(Entity\Identity $identity, int $status)
{
    $identity->setStatus($status);
    $identity->setLastUsed(time());
    $mapper = $this->mapperFactory->create(Mapper\Identity::class);
    $mapper->store($identity);
}

Ways of creating mappers

To implement an abstraction of persistence, on the most flexible approaches is to create custom data mappers.

Mapper diagram

From: PoEAA book

In practice they are implemented for interaction with specific classes or superclasses. Lets say you have Customer and Admin in your code (both inheriting from a User superclass). Both would probably end up having a separate matching mapper, since they contain different fields. But you will also end up with shared and commonly used operations. For example: updating the "last seen online" time. And instead of making the existing mappers more convoluted, the more pragmatic approach is to have a general "User Mapper", which only update that timestamp.

Some additional comments:

  1. Database tables and model

    While sometimes there is a direct 1:1:1 relationship between a database table, Domain Object, and Mapper, in larger projects it might be less common than you expect:

    • Information used by a single Domain Object might be mapped from different tables, while the object itself has no persistence in the database.

      Example: if you are generating a monthly report. This would collect information from different of tables, but there is no magical MonthlyReport table in the database.

    • A single Mapper can affect multiple tables.

      Example: when you are storing data from the User object, this Domain Object could contain collection of other domain objects - Group instances. If you alter them and store the User, the Data Mapper will have to update and/or insert entries in multiple tables.

    • Data from a single Domain Object is stored in more than one table.

      Example: in large systems (think: a medium-sized social network), it might be pragmatic to store user authentication data and often-accessed data separately from larger chunks of content, which is rarely required. In that case you might still have a single User class, but the information it contains would depend of whether full details were fetched.

    • For every Domain Object there can be more than one mapper

      Example: you have a news site with a shared codebased for both public-facing and the management software. But, while both interfaces use the same Article class, the management needs a lot more info populated in it. In this case you would have two separate mappers: "internal" and "external". Each performing different queries, or even use different databases (as in master or slave).

  2. A view is not a template

    View instances in MVC (if you are not using the MVP variation of the pattern) are responsible for the presentational logic. This means that each View will usually juggle at least a few templates. It acquires data from the Model Layer and then, based on the received information, chooses a template and sets values.

    One of the benefits you gain from this is re-usability. If you create a ListView class, then, with well-written code, you can have the same class handing the presentation of user-list and comments below an article. Because they both have the same presentation logic. You just switch templates.

    You can use either native PHP templates or use some third-party templating engine. There also might be some third-party libraries, which are able to fully replace View instances.

  3. What about the old version of the answer?

    The only major change is that, what is called Model in the old version, is actually a Service. The rest of the "library analogy" keeps up pretty well.

    The only flaw that I see is that this would be a really strange library, because it would return you information from the book, but not let you touch the book itself, because otherwise the abstraction would start to "leak". I might have to think of a more fitting analogy.

  4. What is the relationship between View and Controller instances?

    The MVC structure is composed of two layers: ui and model. The main structures in the UI layer are views and controller.

    When you are dealing with websites that use MVC design pattern, the best way is to have 1:1 relation between views and controllers. Each view represents a whole page in your website and it has a dedicated controller to handle all the incoming requests for that particular view.

    For example, to represent an opened article, you would have \Application\Controller\Document and \Application\View\Document. This would contain all the main functionality for UI layer, when it comes to dealing with articles (of course you might have some XHR components that are not directly related to articles).

Bomb answered 3/5, 2011 at 0:56 Comment(72)
Of course, because the business logic of making "Invoice" does not depend on if the data comes from Excel file or from some Oracle mega-cluster.Passional
Do you have a small example of the process your describing above?Manvell
well i still think my code is correct it may not be pure mvc has u say but it does it job . u can read about it here framework.zend.com/manual/en/zend.db.table.htmlAccording
@According , you will notice, that nowhere in that link, anything said about Model (except in one comment). It's only "an object-oriented interface to database tables". If you try to mold this in a Model-like thing, you end up violating SRP and LSP.Passional
+1 for your overall explanation, very good. Personal preference but I don't really like using factories to quite that excess as in your example (but appreciate what you were demonstrating). Lastly the section on services (within the Model heading) not too sure I agree totally with all your explanation, as when I think of services I think in terms of a 'Service layer' so hence something which sits 'above' the model layer, not along side it. Also isn't it the job of the Mapper to speak to the domain object and dba layer, or did I miss your point? I like to use services when a complicated...Thay
domain structure is required (as in a number of Mappers are required, etc). @Rinzler: I think you should do a bit more research for better practices using the Zend framework, for a start have a look at: survivethedeepend.com/zendframeworkbook/en/1.0Thay
@SteveH , as you saw there, I'm going by Fowler's definition of mappers, where mapper is responsible to exchange of data between storage (which might be a DB) and a domain object. Kinda like described here. As for services, I did not have a better name, but they are structures, which isolate controller from business logic. Controller is no supposed to receive information or other feedback from Model layer. Should the code from AuthenticationService::login() be in the controller?Passional
@tereško: I guess the only thing that stuck out was your line "Services are responsible for interaction between Domain Objects and Mappers" which I felt needed some clarification as in my opinion the Mapper should be dealing with the domain object by itself. However I agree with your Auth Service (you're right - keep as a service not in controller) but I personally would rather see the actual Mapper taking care of validation and actually creating the domain object itself. Sorry I'm just nit picking really, here's link for others to view: martinfowler.com/eaaCatalog/dataMapper.htmlThay
ha just realised you already included that dataMapper link (damn 5 minute comment cut off period!). However here is a different link I found about the Service layer: martinfowler.com/eaaCatalog/serviceLayer.htmlThay
@SteveH , no-no-no. Domain object must validate themselves. That is not part of storage logic. And each domain object can contain even collections of other domain objects. This would force data mappers to take up factory behavior, thus - violating SRP. Seems to me like bad idea.Passional
@SteveH , as for Service layer, this is another case of "can you think of a better name?". First of all, the description of it in PoEAA book was kinda .. emm .. murky. And, what Fowler refers to as "services", are things like REST interface or SOAP. I would have used name "Components", but it has already been used in some many different contexts, that it would only succeed in creating more confusion.Passional
@tereško: interesting points, but my main point was that validation for a domain object AND a data storage object may require different validation techniques. Granted for many examples there is normally a 1:1 relationship between validation of data in your domain model and storage system but it is quite valid (and logical) that data stored within your domain model (although being valid for the dm) is not valid for use with your storage medium. I would also personally argue that I'd rather keep my domain model's relatively dumb (as in they are not self-aware that the data that they contain...Thay
is 'valid' just that they contain data). I would employ the DataMapper to actually 'create' the domain model and if required validate any data at that stage. One advantage of doing this is the DataMapper is already in charge managing data between the domain model and db / storage layer, so why not have all validation in a central location? I Agree I have seen many people employ the way you have described, and I am definitely not saying that is wrong - far from it. In regards to 'collections' of domain objects, perhaps look to use repositories: martinfowler.com/eaaCatalog/repository.htmlThay
@SteveH , storage logic validation (like ensuring uniqueness) does not happen on the mapper, but the the level of storage itself. That rises an exception in the interface, which was used for communication(like PDO), and is handled inside the Mapper (changing the state of domain object). But mapper itself does not perform any validation.Passional
@SteveH , with collection of domain objects i meant situation, where one or more User objects gets associated to Group. Or when you are fetching a collection of documents for a specific Category. It is not the responsibility of mapper to create domain objects. Only to ensure the information exchange. Other you end up with situation, when you have to provide mapper with instance of Document, so that it can create a Category and inject said document. And then execute validation to make sure that Document fits the logical constraints.Passional
@tereško: No I agree with you, what I was meaning was its the DataMapper's job to 'convert/modify' your domain object to suit the expectations of your storage mechanism, which I would loosely term 'validating' as the DataMapper may have to manipulate the data (so may call such validation methods from the storage layer - although obviously won't do the heavy lifting itself). You could argue that if the domain model knows how data should be validated are you not breaking SRP by doing so as in my mind a domain model should only 'know' one thing - what it is, not how it is to be used, etc.Thay
@Bomb It's important to note that each tier within a system can (and should) have a differing representation of the domain you are modelling. Front-end code (e.g. stuff running in a browser), you probably have a differing architectural pattern Model-View-ViewModel. For your persistance layer, you may use ActiveRecord. Both of these tiers require a mapping of your models. p.s. This is a fantastic answer and I hope it becomes a community favorite.Silvio
@Silvio only situations, when it is reasonable to employ ActiveRecord pattern is for prototyping. When you start to write the code that is mean for production, it becomes an anti-pattern, because it mixes storage and business logic. And since Model Layer is completely unaware of the other MVC parts. This does not change depending on variation on original pattern. Even when using MVVM. There are no "multiple models" and they are not mapped to anything. Model is a layer.Passional
@Bomb My point was that you need to re-evaluate the architectural pattern depending on the tier (not layer). ActiveRecord was just an example for the persistance layer of the application. Use SQL, don't use SQL, it'll affect the pattern you use for persistence (which is the responsibility of the Model layer). The Model layer needs to provide an abstraction for this, typically through a DAO, since we're crossing a boundary and the representation of our data will change from an object graph to a relational or document form.Silvio
Short Version - Models are Data Structures.Misogynist
@EddieB congratulations. You somehow managed to miss the whole 'There are no "models"` point in this post. Also, while not bad, the book is about application modeling (as in UML and whatnot). It touches on MVC only in single chapter.Passional
Well seeing that he invented MVC the article may have some merit.Misogynist
@Bomb Why should certain Service know about the DataMapperFactory and DomainObjectFactory? Why not to build dependencies in ServiceFactory via DataMapper/DomainObject Factoryand pass them to the constructor of Service? Why should Controller and View know about your ServiceFactory instead of their direct dependencies?Paco
@an1zhegorodov .. could you write in a less confusing manner!? Lets see if I understood what you were asking. You pass in factories in he controller, services and views because there are more the one type of each. Also, in controllers and views, the service that they require tend to vary for each method. For example, while to view the comments for an article, you do not need to be authorized, to post a new comment, authorization can be mandatory. But both operations can be handled by same controller-view pair. Basically, injecting factories lets you use lazy loading.Passional
@an1zhegorodov the alternative would be to employ DIC within the factories for services, views and controllers. But that would also mean that you are either initializing each dependency every time (even when they are not really used) or you add extra complexity to provide lazy-loading wrappers for each of those dependencies.Passional
In my controller, I had the idea to do something like: /* retrieve fields from form / $firstname = $form->getInput('firstname'); $lastname = $form->getInput('lastname'); / create Member object / $member = new Member($firstname, $lastname); / save Member in DB */ $this->_daoFactory->get('Member')->save($member); So compared to your solution, my controller uses domain objects directly and whenever it needs to store/fetch/update the DB it gets the appropriate DAO object through a Factory. What do you think about that?Subminiature
The model is not a class or any single object. While I'm confirm with this in my projects, I would not generalize this. In the simplest from the model can be just a single class.Stopwatch
... or even just a set of functions. MVC does not require to be implemented in an OOP style, although it is mostly implemented that way. The most important thing is to separate layers and establishing the right data and control flowStopwatch
@Stopwatch , If the entirety of your model can be represented as a single class (assuming you are adhering to SRP), then you should not be using MVC to begin with. It is not a pattern for "hello world" scale application. In such cases you should simply follow the general "good practices" of programming style in that particular application (be it procedural, object-oriented, functional or some other). Applying fully realized MVC in such situations is a waste of resource (unless you are studying).Passional
@Bomb Do you have any pointers to articles about a good directory structure or PHP framework that supports this way of thinking?Spotter
@Bomb If request is 'GET', view fetches data from Model Layer through Services too (same as with 'POST') or it can skip Services? ThanksEspecial
MVC triad should not care about the source of user input. Only the fact that there is such an input. "HTTP" is just a transport mechanism for this input. That's why the example features an abstracted Request instance. Depending on how you implement it, the Request instance contains all necessary parameters from POST and GET queries. Same goes for PUT and DELETE. When you pass the $request to appropriate controller, it should not really care about the origin of a specific parameter.Passional
@Bomb so basically you are using servicefactory (services sub-layer?) always and for everything? I asked this because of your answer hereEspecial
That chat line seems really badly articulated and mostly wrong (it's also two years old).Passional
@Especial also, I would like to note that I haven't been using service factories for some time already.Passional
@Bomb what are you using instead of service factories now?Especial
@Especial in the current long-term project I am still using a service factory (because I could not adopt Auryn and my own DI Container's implementation is till a "work in progress"). But I will be switching to Container-driven assembly for controllers in my next green-field project,Passional
@teresko what was wrong (not enough) with service factories that you decided to use other approaches?Especial
@Especial the problems begin when you are attempting to use services, that are actually wrappers for 3rd party libraries (for example: Mailer service). They have different set of dependencies. Same applies to cases, when you would like to share a number of domain objects between specific services. It boils down to the fact, that factories are limited when it comes to ways how you can instantiate objects. All this points to either choosing a service locator (to pull in dependencies) or use of DI container (to supply dependencies directly). And later seems a better approach.Passional
@Bomb would you be so kind and add an example of the if the user is-logged-in part? I'm having trouble with this part. Thanks for the "article", it's awesome. :)Richellericher
What about view models? Do you recommend to map domain objects to them?Sleight
@MajoB "viewmodel" is something you replace the controller with in the MVVM architectural pattern. You are probably referring to presentation objects. And those I don't not see as something you would be linking directly with domain objects, because I do not want the domain objects to be exposed to the anything outside of model layer.Passional
@Bomb << [A view] acquires data from the Model Layer and then, based on the received information, chooses a template and sets values. >> Isn't it the job of controller to choose particular content template? (I am aware of the fact that a resulting web page is usually composed of several of template files)Escort
@MikhailBatcer this should explain it: https://mcmap.net/q/74278/-understanding-mvc-views-in-phpPassional
so does this apply to modern day objective c and iOS MVC design?Riendeau
Its been a while since I've read such a good answer. Trust me I read a lot of them.Kelwin
That's right, most people(include me) always thought that the model may be just a POJO class ,this is also some mvc tutorial used. While the Model means a dynamical progress to interacte with your persisted data, so I think the Model should be the Service+Dao+POJO in a typical SpringMVC application.Skite
In the first code block you provided, the request class reminds me of a domain object. Is this accurate/what functionalities should be built into the request class so it isn't reminiscent of a domain object?Henleigh
@Jeremy the Request class (as I implement it) deals with abstracting the user's input (get and post parameters, cookies, uploaded files and extracted data, when client is pushing data as application/json ContentType .. that sort of things). Also, in by own projects, the Request instance (after it's initialization/preparing in the bootstrap stage) doesn't get altered in the MVC triade.Passional
why does the controller get an instance of the view passed to it?Henleigh
@Jeremy it shouldn't. It's a mistake,Passional
it seems to me that services should be changing the state of the view. should they not?Henleigh
@Bomb in your example of how to interact with the model, I see that you create a response object, but I don't see it reference again until it is passed to the view. I assume that the view will get it's models through some interaction with a DI container. If this is the case then what is the responsibly of the Response class/instance?Un
@Un a very simple example is here but that's just on the controller side of things. As for view ... well ... the best I can do is sample on of the projects I am currently working on: gist.github.com/teresko/c13a801dadf595d2b515e1b30d1f3b49/… (reposted link, copied from wrong branch), because the alternative would be simply direct you to this answer, but that's just a wall of textPassional
What if my modellayer needs a database and an api connection? Is it a good choice to extend the servicefactory and add the api-class to it?Waiter
These days I would advise against using service factory as whole and instead use DI Containers, with each of controllers having specified services as dependencies in the constructor. But, if you need to add a different data source, it's not the service initialization code, that has to change. Instead you should alter the data mapper factory, so that it can produce persistence abstraction both for database and API. (I should probably update the answer this year)Passional
Thx. Yes, i meant this. f.e. $serviceFactory = new ServiceFactory( new DataMapperFactory(new Database(),new HTTPRequest), new DomainObjectFactory() );Waiter
But i agree, using DI Containers is the better approach.Waiter
That's the general approach - yes. But if can fully cover your domain objects with unit test and then don't have any specific dependencies for them, you could get away with just using new in services for instantiating those domain objects.Passional
The approach in the original example is something of a "what to do in worst case scenario". Also, reading PoEAA book would help a lot.Passional
@Bomb Your IdentificationService is very anemic. Almost every method call within your loginWithPassword method takes the $identity as an argument. It could easily be refactored so that the Identity has the loginWithPassword( $password ) : string method itself. This makes more sense anyway right? After all, who/what is logging in? This is the problem with employing Domain Services. Often, they work against the ideas of DDD by separating data and behavior. A better example of a Domain Service would be one that is responsible for creating the $cookie, not "doing" something.Lend
@Lend no, it does not. Because login operation interacts with persistence. Identity has no awareness of it (and why would it return a string?!). You might not know it, but ActiveRecord is an anti-pattern. Also, this has nothing tho do with DDD (it not being mentioned anywhere in the answer, should have given it away already). And if you want to make a "better example" write your own answer.Passional
@Bomb It needn't return anything. Like I said, a service can create cookies. And whose says it has to interact with persistence? You are confused. I am not advocating for removing the service. Your "service" could be reduced to 3 lines of code: $identity->login($password); $this->updateIdentityOnUse($identity); return $this->createCookie($identity);. That is objectively better design. Period. Lower coupling. Higher cohesion. As an aside, a Domain Service should not be aware of persistence either. It follows the same rules as all the other domain objects.Lend
This answer seems far more confusing than explanatory to me, esp. considering that there seem to be a HUGE variety of conflicting and competing explanations for just what this MVC pattern really is - so which is the "most right one"? And furthermore this seems to presuppose a lot of background and not just someone who is learning the pattern fresh (which is where I am at). Is there an easier introduction that nonetheless is actually correct? As I'm having a hard time figuring how all these things work together in detail, e.g. how does the "service" object get (cont'd)Oberammergau
(cont'd) inside the "domain object" to pull its data out and send it through the "data mapper" so it can be written out (or conversely, read from the data mapper to the domain object), without creating at least some degree of "knowledge" of the "domain object" that it's going to be giving out data somewhere ? Or are "domain objects" entirely stateless, data-less aggregations of functions (so not necessarily even an "object" in the OOP sense) which the services then feed data to and from?Oberammergau
And moreover, what might a very simple stripped down version of this look like in code (which might help to make it clear) with exactly one of each element? (Also, I'm not so much interested in this from PHP context, but others and am not very familiar with PHP, so the PHP examples are not very useful to me. "Pseudo-code" might be better, with clear indicators/markers pointing at what is a service object, domain object, and data mapper.)Oberammergau
Right now, the vague idea I have of this is that it looks like a "tee", where that the "service" objects communicate to/from (read data in and out) of the "data mapper" objects, and to/from the "domain objects" (sending data to be crunched, because they're state-less, and receiving the result of that crunching), thus forming a "bar" shape, and then the "tee" comes off in that they also provide an interface to be grabbed onto by the controllers which communicate with Views. Is this right?Oberammergau
@Bomb Very nice answer. After reading all comments, it seems nobody asked this (which might mean it's stupid) : Doesn't the controller contain business logic in your example ? It knows it needs to find the identity, and then try to login with that identity, and then if it fails it should redirect to somewhere else. Doesn't it feel like this is business rule ?Brom
You can't make your Service public while it doesn't have any validation... validation must be placed in Service/BL layer.Autoroute
Just when I thought I got to grips with PHP, I read this answer and now I feel like I'm 5 years old again.Leekgreen
@Bomb Nice detailed answer! In your example of a method for a View, where is the isUserLoggedIn() method of the identification Service getting that information from? In other words, how/where are the state changes made by the Controller "saved" so that the View can access them later? I would think there would have to be some in-memory structure that both the Controller and View can access/modify.Exaltation
@Exaltation short answer - "it depends". But for a simple case - in the session. When your service runs the isLoggedIn(), inside of it it checks against data mapper, that is using the session. Keep in mind, that this kind of "complete separation" inside of the UI layer between Controllers and Views is quite often an overkill. It only becomes valuable, when you are making something huge. And in such case this "state juggling boilerplate" becomes not only insignificant in terms of effort, but actively beneficial.Passional
S
37

Everything that is business logic belongs in a model, whether it is a database query, calculations, a REST call, etc.

You can have the data access in the model itself, the MVC pattern doesn't restrict you from doing that. You can sugar coat it with services, mappers and what not, but the actual definition of a model is a layer that handles business logic, nothing more, nothing less. It can be a class, a function, or a complete module with a gazillion objects if that's what you want.

It's always easier to have a separate object that actually executes the database queries instead of having them being executed in the model directly: this will especially come in handy when unit testing (because of the easiness of injecting a mock database dependency in your model):

class Database {
   protected $_conn;

   public function __construct($connection) {
       $this->_conn = $connection;
   }

   public function ExecuteObject($sql, $data) {
       // stuff
   }
}

abstract class Model {
   protected $_db;

   public function __construct(Database $db) {
       $this->_db = $db;
   }
}

class User extends Model {
   public function CheckUsername($username) {
       // ...
       $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE ...";
       return $this->_db->ExecuteObject($sql, $data);
   }
}

$db = new Database($conn);
$model = new User($db);
$model->CheckUsername('foo');

Also, in PHP, you rarely need to catch/rethrow exceptions because the backtrace is preserved, especially in a case like your example. Just let the exception be thrown and catch it in the controller instead.

Seismic answered 3/5, 2011 at 0:42 Comment(7)
My structure is very similar, I think I just separate it out a bit more. The reason why I was passing around the connection was because I needed to have chunks run in transactions. I wanted to add a user and then add the user to a role, but role back if one failed. The only way I could sort that out was to pass the connection.Manvell
-1: it also happens to be completely wrong. Model is not an abstraction for a table.Passional
The User class basically extends the model, but itsn't an object. User should be an object and has properties like: id, name ... You're deploying User class is a helper.Glaciate
I think you understand MVC but don't understand what's OOP. In this scenario, like i said, User stands for an object, and it should have properties of an User, not methods like CheckUsername, what should you do if you want to create new User object? new User($db)Glaciate
@Glaciate OOP doesn't mean objects are required to have properties. What you're describing is a design pattern, one which is irrelevant to the question or an answer to that question. OOP is a language model, not a design pattern.Seismic
@Bomb Model is not an abstraction for a table do you have any ref/source for that?Autoroute
"Everything that is business logic belongs in a model, whether it is ... calculations" then what are services ever going to be used for other than pointless overkill for the sake of calling model? Services can do calculations and other things with business data, they just shouldn't directly request or alter itSulphurize
R
21

In Web-"MVC" you can do whatever you please.

The original concept (1) described the model as the business logic. It should represent the application state and enforce some data consistency. That approach is often described as "fat model".

Most PHP frameworks follow a more shallow approach, where the model is just a database interface. But at the very least these models should still validate the incoming data and relations.

Either way, you're not very far off if you separate the SQL stuff or database calls into another layer. This way you only need to concern yourself with the real data/behaviour, not with the actual storage API. (It's however unreasonable to overdo it. You'll e.g. never be able to replace a database backend with a filestorage if that wasn't designed ahead.)

Rehnberg answered 3/5, 2011 at 0:41 Comment(2)
link is invalid (404)Event
This works from WebArchive: web.archive.org/web/20101229204648/https://stackoverflow.com/…Fabianfabianism
P
7

More oftenly most of the applications will have data,display and processing part and we just put all those in the letters M,V and C.

Model(M)-->Has the attributes that holds state of application and it dont know any thing about V and C.

View(V)-->Has displaying format for the application and and only knows about how-to-digest model on it and does not bother about C.

Controller(C)---->Has processing part of application and acts as wiring between M and V and it depends on both M,V unlike M and V.

Altogether there is separation of concern between each. In future any change or enhancements can be added very easily.

Phellem answered 30/6, 2014 at 13:9 Comment(0)
S
0

In my case I have a database class that handle all the direct database interaction such as querying, fetching, and such. So if I had to change my database from MySQL to PostgreSQL there won't be any problem. So adding that extra layer can be useful.

Each table can have its own class and have its specific methods, but to actually get the data, it lets the database class handle it:

File Database.php

class Database {
    private static $connection;
    private static $current_query;
    ...

    public static function query($sql) {
        if (!self::$connection){
            self::open_connection();
        }
        self::$current_query = $sql;
        $result = mysql_query($sql,self::$connection);

        if (!$result){
            self::close_connection();
            // throw custom error
            // The query failed for some reason. here is query :: self::$current_query
            $error = new Error(2,"There is an Error in the query.\n<b>Query:</b>\n{$sql}\n");
            $error->handleError();
        }
        return $result;
    }
 ....

    public static function find_by_sql($sql){
        if (!is_string($sql))
            return false;

        $result_set = self::query($sql);
        $obj_arr = array();
        while ($row = self::fetch_array($result_set))
        {
            $obj_arr[] = self::instantiate($row);
        }
        return $obj_arr;
    }
}

Table object classL

class DomainPeer extends Database {

    public static function getDomainInfoList() {
        $sql = 'SELECT ';
        $sql .='d.`id`,';
        $sql .='d.`name`,';
        $sql .='d.`shortName`,';
        $sql .='d.`created_at`,';
        $sql .='d.`updated_at`,';
        $sql .='count(q.id) as queries ';
        $sql .='FROM `domains` d ';
        $sql .='LEFT JOIN queries q on q.domainId = d.id ';
        $sql .='GROUP BY d.id';
        return self::find_by_sql($sql);
    }

    ....
}

I hope this example helps you create a good structure.

Sidekick answered 3/5, 2011 at 0:47 Comment(5)
"So if I had to change my database from MySQL to PostgreSQL there won't be any problem." Uhhhmmm with above code you would have a huge problem changing anything imo.Arcadian
I see my answer makes less and less sense after edit, and as time goes by. But it should stay hereSidekick
Database in the example is not a class. It is just a wrapper for functions. Also, how can you have "table object class" without an object?Passional
@Bomb I have read many of your posts and they're great. But, I cannot find any complete framework anywhere to study. Do you know of one that "does it right"? Or at least one that does it like you and some others here on SO say to do? Thanks.Lurlene
I may be way late, but i'd like to point out that PDO almost solves the issue of having to create a DB 'layer' in order to facilitate future changes.Loosen

© 2022 - 2024 — McMap. All rights reserved.