1

Over the past two years, I have become fairly familiar with PHP MVC style architecture, and have developed all my projects using MVC structures since then.

One question that has continued to bother me is how to group functions and database calls. I run into needing to perform the same actions across models. I would prefer not to duplicate these operations and sql query inside each of the models, but would rather group all user operations into a separate class.

For example, say I have a website with a forum, a blog, and a profile page, each with a separate model, view, and controller. However, say each of these pages needs to perform the same operation to the user table.

My Model class is constructed with a database object automatically. If I need to call a function from the user class, is it ok to pass the db object to that new User class? ... to do something like the following? I am not sure if passing objects like I am doing is fine, or is there a much better way of setting things up? Am I wasting resources, or is this a clumsy way of doing things?

Profile Model

class Profile_Model extends Model{


  public function __construct() {
         parent::__construct();
    }

    public function someFunction(){

         $this->db->insert( "SOME SQL" );

         $user = new User( $this->db ); // OK TO PASS DB OBJECT LIKE THIS?
         $user->setSomething();

    }

    public function anotherFunction(){

        //do something else that does not need a user object

    }

}

User Class

class User{

    public function __construct($db){
         $this->db = $db; // OK TO SET DB OBJECT AS CLASS VARIABLE AGAIN?
    }

    public function setSomething(){
         $this->db->insert( "SOME SQL" );
    }

}
0

4 Answers 4

4

I'm trying to give you a really basic example of how I'd implement this architecture; Since it's really basic and I'm just a passionate developer and nothing more it could be I'm breaking some architectural rules, so please take it as a proof of concept.

LET'S START quickly with the Controller part where you get some request. Now you need someone that takes care of doing the dirty work.

As you can see here I'm trying to pass all the "dependencies" via constructor. These way you should be able to easily replace it with Mocks when testing .

Dependency injection is one of the concepts here.

AND NOW the Model (please remember Model is a layer and not a single class)

I've used "Services (or cases)" that should help you to compose a group of behaviors with all the actors (Classes) involved in this behavior.

Idendifying common behaviours that Services (or Cases) should do, is one of the concepts here.

Keep in mind that you should have a big picture in mind (or somewhere else depending on the project) before starting, in order to respect principle like KISS, SOLID, DRY, etc..

And please pay attention to method naming, often a bad or long name (like mine for example) is a sign that the class has more than a single Responsability or there's smell of bad design.

//App/Controllers/BlogController.php
namespace App\Controllers;

use App\Services\AuthServiceInterface;
use App\Services\BlogService;
use App\Http\Request;
use App\Http\Response;

class BlogController
{
    protected $blogService;

    public function __construct(AuthServiceInterface $authService, BlogService $blogService, Request $request)
    {
        $this->authService = $authService;
        $this->blogService = $blogService;
        $this->request = $request;
    }

    public function indexAction()
    {
        $data = array();

        if ($this->authService->isAuthenticatedUser($this->request->getSomethingRelatedToTheUser())) {
            $someData = $this->blogService->getSomeData();
            $someOtherData = $this->request->iDontKnowWhatToDo();
            $data = compact('someData', 'someOtherData');
        }

        return new Response($this->template, array('data' => $data), $status);
    }
}

Now we need to create this Service that we've used in the controller. As you can see we're not talking directly with the "storage or data layer" but instead we're calling an abstraction layer that will handle that for us.

Using a Repository Pattern to retrieve data from a data layer, is one of the concepts here.

this way we can switch to whatever repository (inMemory, other storage, etc) to retrieve our data without changing the interface that the Controller is using, same method call but get data from another place.

Design by interfaces and not by concrete classes is one of the concepts here.

//App/Services/BlogService.php
<?php

namespace App\Services;

use App\Model\Repositories\BlogRepository;

class BlogService
{
    protected $blogRepository;

    public function __construct(BlogRepositoryInterface $blogRepository)
    {
        $this->blogRepository = $blogRepository;
    }

    public function getSomeData()
    {
        // do something complex with your data, here's just simple ex
        return $this->blogRepository->findOne();
    }
}

At this point we define the Repository that contains the persistance handler and knows about our Entity.

Again decoupling storage Persister and knowledge of an entity (what "can" be coupled with a mysql table for example), is one of the concepts here.

//App/Model/Repositories/BlogRepository.php

<?php

namespace App\Models\Respositories;

use App\Models\Entities\BlogEntity;
use App\Models\Persistance\DbStorageInterface;

class DbBlogRepository extends EntityRepository implements BlogRepositoryInterface
{
    protected $entity;

    public function __construct(DbStorageInterface $dbStorage)
    {
        $this->dbStorage = $dbStorage;
        $this->entity = new BlogEntity;
    }

    public function findOne()
    {
        $data = $this->dbStorage->select('*')->from($this->getEntityName());

        // This should be part of a mapping logic outside of here
        $this->entity->setPropA($data['some']);
        return $this->entity;
    }

    public function getEntityName()
    {
        return str_replace('Entity', '', get_class($this->entity));
    }
}

At the end a simple entity with Setters and Getters:

//App/Model/Entities/BlogEntity.php
<?php

namespace App\Models\Entities;

class BlogEntity
{
    protected $propA;

    public function setPropA($dataA)
    {
        $this->propA = $dataA;
    }

    public function getPropA()
    {
        return $this->propA;
    }
}

AND NOW? how can you inject this classes passed as dependencies? Well, this is a long answer. Indicatively you could use Dependency Injection as we've done here have a init/boot file where you define things like:

// Laravel Style
App::bind('BlogRepositoryInterface', 'App\Model\Repositories\DbBlogRepository');
App::bind('DbStorageInterface', 'App\Model\Persistence\PDOStorage');

or some config/service.yml file like:

// Not the same but close to Symfony Style
BlogService:
     class: "Namespace\\ConcreteBlogServiceClass" 

Or you may feel the need of a Container Class from where you can ask the service you need to use in your controller.

function indexAction () 
{
    $blogService = $this->container->getService('BlogService'); 
    ....

Dulcis in fundo here are some useful links (You can find tons of docs about this):

Sign up to request clarification or add additional context in comments.

Comments

2

Whenever you need to use an object from another class there is only one safe way to do it: Dependency Injection.

Example:

Instead of having:

public function myMethod(){
   $anotherObject = new Object();
}

You should inject the object with the constructor:

function __construct($dependency) {
   $this->anotherObject = $dependency;
}

Once you have this structure you can use type hint and an Inversion of Control container to build thing automatically, e.g. define:

function __construct(DependencyInterface $dependency) {
   $this->anotherObject = $dependency;
}

And then set your IoC container to inject the right dependency when you need to use this object

1 Comment

Sorry for my confusion, but is that not what I am doing? I am injecting the User object with the DB object from the Model.
1

Do you use any frameworks? If not, try having a look at some popular ones, like Zend Framework or Symfony. You'll find they solve your problem and probably many more and are a great way to expand your knowledge on how to structure your project.

That aside you are close. Although adding the database directly to your User-model is probably not want you want to do. If you can get Martin Fowler's Patterns of Enterprise Application Architecture (PEAA) you will find a whole chapter outlining how to connect your models to your database. I prefer a Gateway-class (search for the Gateway-pattern or look at Zend_Db) when building something on my own, as it is relatively easy to implement and build.

Basically you have a class which performs queries and then will pass the data to your model. Just look at Data Source Architectural Patterns in Martin Fowler's pattern catalog (http://martinfowler.com/eaaCatalog/) to get a quick glance how to structure it and definitely read the book to get a real understanding when and how to use the patterns.

I hope this helps.

Comments

0

Part of the answer is to use dependency injection, but there is more to it than that. Cognitively speaking, grouping starts in the mind and is teased out better by brainstorming and modeling: Entity Relationship Diagrams and UML Diagrams.

Grouping of methods into classes and delegating tasks to injected objects makes sense, but there is usually room for one level of inheritance (at minimum). The use of abstract super classes and a Strategy Pattern for child classes that inherit base functionality from the abstract parent can help reduce code duplication (DRY).

All that being said, this is one reason why dependency injection containers are popular. They allow you to obtain the objects, and hence functionality, you need anywhere, without coupling object instantiation to usage.

Do a search for Pimple in Google. It may give you some ideas.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.