MVC app from scratch

Assume that you are going to develop a MVC framework from scratch. This framework should allow further extension by somebody else. Below is my simple approach. I aim to create a simple structure with some simple scripts/ classes as a small part of a much bigger project. This simple structure imposes no restrictions on where any given class is located - as long as the autoloader can autoload the class. You can add more folders or tear apart them as you like.

Directory structure,

conf/
app/
public/
vendor/
test/
index.php

inside app/, in which you can populate your modules, and contains an example module - contact/,

app/
    adapter/PdoAdapter.php (database connection)
    controller/contact/
    mapper/contact/
    model/contact/
    client/contact/
    service/contact/
    strategy/
    view/contact/

Inside controller/contact, contains ContactController.php,

use Foo\Strategy\ControllerStrategy;

class ContactController implements ControllerStrategy
{
    public function setService( \Foo\Strategy\ServiceStrategy $ServiceStrategy )
    {
        $this->ServiceStrategy = $ServiceStrategy;
        return $this;
    }
    
    public function fetchContact( $options = [] )
    {
        $this->ServiceStrategy->fetchContact( $options );
    }
    
    public function fetchContacts( $options = [] )
    {
        $this->ServiceStrategy->fetchContacts( $options );
    }
    
    public function updateContact( $options = [] )
    {
        $this->ServiceStrategy->updateContact( $options );
    }
    
    public function deleteContact( $options = [] )
    {
        $this->ServiceStrategy->deleteContact( $options );
    }
}

Inside model/contact, contains ContactModel.php

in ContactModel.php,

namespace Foo\Model;

use Foo\Strategy\ModelStrategy;

class RetrieveContactModel implements ModelStrategy
{
    public $contact_id;
    public $name;
    public $phone;
    public $street;
    public $error;
    
    /*
     * Get contact name or other manipulations that only concerns itself only.
     */
    public function getName()
    {
        return ucwords($this->name);
    }
    
    // And other methods...
}

Inside view/contact, contains ContactView.php,

namespace Foo\View;

use Foo\Strategy\ViewStrategy;
    
class RetrieveContactView implements ViewStrategy
{
    /*
     * Set props.
     */
    private $contact;
    private $format;
    
    /*
     * Contruct the dependency.
     */	
    function __construct( \Foo\Strategy\ModelStrategy $ModelStrategy, $format = null ) 
    {
        $this->contact = $ModelStrategy;
        $this->format = $format;
    }
    
    /*
     * Render the page with data.
     */
    function render() 
    {
        switch( $this->format ) 
        {
            case "json":
                
                // Set header.
                header('Content-Type: application/json');
                
                if( !empty( $this->contact->error) )
                {
                    // Unset the empty properties.
                    unset($this->contact->contact_id);
                    unset($this->contact->name);
                    unset($this->contact->phone);
                    unset($this->contact->street);
                    
                    // Encode the data into json.
                    echo json_encode( $this->contact ); 
                    break;
                }
                
                // Unset the empty properties, in this case - $error.
                unset($this->contact->error);

                // Encode the data into json.
                echo json_encode( $this->contact );
                
                break;
            
            case "xml":
                
                // Set header.
                header('Content-Type: application/xml');
                
                // Format the data into xml...
                
                break;
            
            default:
                
                // Turn on output buffering
                ob_start();

                // Store the frogs model in the $frog handler.
                $contact = $this->contact;
                
                // Get the main template/ html document
                include  WEBSITE_DOCROOT . 'public/template/page/contact/index.php' ;

                // Get current buffer contents and delete current output buffer
                $html = ob_get_clean();

                // Return the content.
                return $html;
        }
    }
}

Inside mapper/contact, contains ContactMapper.php,

namespace Foo\Mapper;

use Foo\Strategy\PopulateStrategy;

class ReadContactMapper implements PopulateStrategy
{
    /*
     * Set props.
     */
    protected $PdoAdapter;
    
    /*
     * Construct dependency.
     */	
    public function __construct( \Foo\Adapter\PdoAdapter $PdoAdapter )
    {
        $this->PdoAdapter = $PdoAdapter;
    }

    /*
     *  Map the data to model.
     */
    public function populate( \Foo\Strategy\ModelStrategy $ModelStrategy, $options = [] )
    {
        // Prepare the SQL.
        $sql = "
            SELECT *
            FROM contact AS c
            WHERE c.contact_id = ?
        ";
        
        /// Store the result.
        $result =  $this->PdoAdapter->fetchRow( $sql, array( 
            $options['contact_id']
        ) );
        
        $ModelStrategy->contact_id = $result['contact_id'];
        $ModelStrategy->name = $result['name'];
        $ModelStrategy->phone = $result['phone'];
        $ModelStrategy->street = $result['street'];
    }
}

Inside service/contact, contains ContactService.php,

namespace Foo\Service;

use Foo\Strategy\ServiceStrategy;

class ContactService implements ServiceStrategy
{
    public function setMapper( \Foo\Strategy\PopulateStrategy $PopulateStrategy )
    {
        $this->PopulateStrategy = $PopulateStrategy;
        return $this;
    }
    
    public function setModel( \Foo\Strategy\ModelStrategy $ModelStrategy )
    {
        $this->ModelStrategy = $ModelStrategy;
        return $this;
    }
    
    public function fetchContact( $options = [] )
    {
        $this->PopulateStrategy->populate( $this->ModelStrategy, $options );
        
        // Return the populated model to the requestor.
        return $this->ModelStrategy;
    }
    
    public function fetchContacts( $options = [] )
    {
        $this->PopulateStrategy->populate( $this->ModelStrategy, $options );
        
        // Return the populated model to the requestor.
        return $this->ModelStrategy;
    }
    
    public function updateContact( $options = [] )
    {
        $this->PopulateStrategy->populate( $this->ModelStrategy, $options );
        
        // Return the populated model to the requestor.
        return $this->ModelStrategy;
    }
    
    public function deleteContact( $options = [] )
    {
        $this->PopulateStrategy->populate( $this->ModelStrategy, $options );
        
        // Return the populated model to the requestor.
        return $this->ModelStrategy;
    }
}

Inside strategy/, contains a few interface classes, such as, ControllerStrategy.php,

namespace Foo\Strategy;

interface ControllerStrategy
{
    public function setService( \Foo\Strategy\ServiceStrategy $ServiceStrategy );
}

ServiceStrategy.php,

namespace Foo\Strategy;

interface ServiceStrategy
{
	public function setMapper( \Foo\Strategy\PopulateStrategy $PopulateStrategy );
	public function setModel( \Foo\Strategy\ModelStrategy $ModelStrategy );
}

and so on…

Inside client/contact, contains index.php, this is where how all the classes above being wired up and glued together,

// Composer autoloader.
require '../../vendor/autoload.php';

/*============================================================
MAKE DB CONNECTION
============================================================*/	

// Instance of PdoAdapter.
$PdoAdapter = new \Foo\Adapter\PdoAdapter( DSN, DB_USER, DB_PASS );

// Make connection.
$PdoAdapter->connect();

/*============================================================
PUT PIECES INTO A MODULE
============================================================*/

// Mapper.
use Foo\Mapper\ReadContactMapper;

// Model.;
use Foo\Model\RetrieveContactModel;

// Service.
use Foo\Service\ContactService;

// Controller.
use Foo\Controller\ContactController;

// View.
use Foo\View\RetrieveContactView;

// Declare the re-usealbe service and controller.
$ContactService = new ContactService();
$ContactController = new ContactController();

// Prepare contacts model.
$ContactModel = new RetrieveContactModel( $format );
$ContactMapper = new ReadContactMapper( $PdoAdapter );

// Inject mapper & model.
$ContactService->setMapper( $ContactMapper )->setModel( $ContactModel );
$ContactController->setService( $ContactService )->fetchContact( array(
    "contact_id"    => $id
) );

// Prepare the view.
$RetrieveContactView = new RetrieveContactView( $ContactModel, $format );

// Render the view.
echo $RetrieveContactView->render();

This proposal is very different from the popular PHP frameworks that you might be working on, but this is how I understand what MVC is.

My only question is - am I violating any design patterns and principles (such as SOLID)?

Whilst there’s a lot to take in there, I don’t actually think there’s enough to determine whether you’re violating any principles. My only concern principle-wise would be the possibility of duplicating the db adapter stuff in the future if you were to add any additional functionality ( /profile/, /orders/ etc).

I love the fact that you’ve gone for an interface based approach rather than the usual “here’s our god object, now extend it to make your own models” that other frameworks tend to promulgate.

However, I do have a worry that your interfaces tend to describe a ‘would-be-a’ kind of relationship that might be better served by creating an ‘is-a’ relationship through the use of abstract classes.

For example, your ControllerStrategy interface declares a setService() method, which is implemented in the ContactController class. If you created another Controller class, would it have to implement the same method again? Surely the answer must be yes, in which case you would be duplicating code and violating the DRY principle.

Would not an AbstractController class that implemented the setService method as defined in your controller better suit? In this case, I think yes. You still have an interface that concrete implementations should honour but you would also have the actual code in place to meet that interface obligation. Of course, if you provide an overriding method in a child class that breaks the interface, then, well…it’s broken but hopefully a unit test would pick that up anyway.

As a matter of personal preference, I prefer interface specifications to declare what something does, rather than what something is. ie. Observable, Listable, Transformable.

Other than that, my only other concerns would be around these sorts of things.

  • Trying to support the PSR-4 autoloading strategy from PHP-FIG would probably make life a bit easier. I’m a bit uncomfortable with the variance between filename and class name.
  • The actual code layout suggests to me that maintenance might be a bit troublesome in the future as the application grows. Having the files for a conceptual ‘module’ spread out amongst many folders and then having those folders shared by potentially many other modules may lead to a lot of to-ing and fro-ing and ultimately confusion. Not an issue if it’s just to remain a personal project of course, but consider how much more comprehensible the composer/packagist approach is - delivering discrete ‘modules’ in their own folders.

Those last two points though are purely my own feelings on the topic though. Thumbs up on the coding style (though I also prefer to see docblocks) - the lack of conditional statements alone suggests that the CRAP factor in this code is very low indeed. :slight_smile:

1 Like

@RavenVelvet thank you very much for the feedback.

I do agree with you regarding using the abstract class approach to comply with the DRY principle. Thanks for pointing that out.

I do use PSR-4 autoloading strategy for loading classes, but I don’t understand what you mean by the variance between filename and class name. do you mind explaining it a bit more?

I do use composer to load my class by configuring it to fetch all classes in the app/ folder. I don’t understand then how that maintenance might be a bit troublesome in the future as the application grows. and what you mean by delivering discrete 'modules' in their own folders.. Do you explaining this a bit more too? Do you mean that the current directory structure and the conceptual ‘module’ that I propose can lead to maintenance troublesome? If so, do you mind suggest a more comprehensible solution?

db adapter will only be instantiated once when each module is called. for instance when I have another ‘order’ module, the db adapter is instantiated once just like the index.php in client/contact/. Then db adapter is passed around by injecting into the classes that need it. It will never be instantiated in an individual class. so will it be the possibility of duplicating?

For PSR-4 compliance, the filename must equal the class name plus the ‘.php’ extension. Or in code

$filename = $classname . '.php';

The second class that you’ve listed here has a file called ContactModel.php containing a class called RetrieveContactModel, which doesn’t follow this scheme. Life certainly gets easier when you do :slight_smile:

With regard to the folder structure, I was noting that the contact ‘module’ was spread out over six directories. This is a common approach that other frameworks take, of course. Controllers go here. Models go there. I was wondering though whether there’d be value in exploring turning the folder structures around a bit

app/ 
    adapter/PdoAdapter.php
    core/     (essential files required by one or more modules)
    contact/
        controller/
        mapper/
        service/
        view/
    strategy/

It would mean rearranging your namespaces to accommodate the change but it also packages the contact ‘module’ in a single directory. Whether you’d think this worth doing depends entirely on your project(s) and whether having an easy way to ‘pluck’ a module from one project to another has any value. There’s a side benefit too, since having the module in a single directory would allow you to ‘install’ it via composer (assuming the module had a repo)

Aye, but the code to establish the db adapter instance would be written into each module? It really is just this snippet of code from client/contact/index.php that I was referring to, rather than the actual instance that it creates.

If you moved that snippet into its own file, say conf/bootstrap.php and had each $module/index.php require it, then there’s only a single location to look for the adaptor instantiation code should you need to change it, as opposed to having $numModules locations to edit.

Doing so would offer a little future protection should you ever need to do things like modify your db adapter to provide read & write connections instead of just a single connection for both types of operation. Read and write connections come into play when your app starts using replicated databases for scaling purposes.

Oh that’s right. That is my mistake. I should have called it class ContactModel shouldn’t I? or I should create a folder called retrieve/ then I can use class RetrieveContactModel?

Btw, PSR-4 was designed to help remove the directory clutter as far as I understand. So I don’t have a directory call Foo/ just as the vendor name suggested in my namespace, such as \Foo\Adapter\PdoAdapter, am I right?

I know that other popular frameworks such as Zend does have a directory called Zend/ to match their namespaces. The thing is I am not going to have a folder called Bar/ or Foo/ because I want to make it flexible. So is this compying with PSR-4 then?

I do quite like this idea. Thanks, Then what should I call the controller in contact/controller/ to comply with PSR-4?

Let’s say I’m going to name it controller.php, so the namespace must be,

namespace Foo\Contact\Controller;

class Controller {...}

or should I still name it as before - ContactController.php?

namespace Foo\Contact\Controller;
    
class ContactController {...}

The reason why I do so because some modules may not require the db connection, so I thought it might be good just to put it in the module’s index.php. Maybe it is not a good idea. I think put it in the conf/bootstrap.php is a good idea. or instead put it in the global bootstrap.php - conf/bootstrap.php, I could have the bootstrap.php in each module?

@RavenVelvet

In this case, shouldn’t I capitalise some of the folders to match with the namespaces? such as for namespace like,

namespace Foo\Contact\Controller;

class Controller {}

its folders must be like this below then?

app/
    Contact/
         Controller/

Or I don’t have to actually?

Yes, sorry, you should. The subnamespaces that you use should map to folder names.

So…

app/
    Contact/
        Controller/
            ContactController.php

would map to

namespace Foo/Contact/Controller;

class ContactController implements ControllerStrategy
{
    ...

Then you only need tell composer that the vendor namespace ‘Foo’ is found in the ‘app/’ directory like this

{
    ...
    "autoload": {
        "psr-4": {
            "Foo\\" : "app/"
        }
    }
}

and autoloading is taken care of for you.

You should at least be using composer to handle dependencies and auto-loading. It is the industry standard at this point for dealing dependencies and auto-loading in php. If you don’t know that than you’re probably not qualified to build your own framework unless it is for fun or a learning exercise.

I can’t stand when people (normally unexperienced) try to reinvent the wheel with this belief that what they create will be “faster” or “simpler” than existing, proven solutions. Perhaps starting off when there are no business requirements but as real-world applications evolve they do get complex, and become less manageable. That is a real world fact. The better approach would be to use something that already exists which directly caters to the needs of real-world application scaling and maintanience rather than trying to create the simplest little framework possible which caters to hardly any actual business and/or development needs.

As a learning exercise something like this is fine but to use something like this for a new, real-world project would be amatuer and inconsiderate to future devs who might one day inherit said mess.

@RavenVelvet no worries. Thank you.

That was my mistake. I should have called it class ContactModel shouldn’t I? or I should create a folder called retrieve/ then I can use class RetrieveContactModel?

Btw, PSR-4 was designed to help remove the directory clutter as far as I understand. So I don’t have a directory call Foo/ just as the vendor name suggested in my namespace, such as \Foo\Adapter\PdoAdapter, am I right?

I know that other popular frameworks such as Zend does have a directory called Zend/ to match their namespaces. The thing is I am not going to have a folder called Bar/ or Foo/ because I want to make it flexible. So is this compying with PSR-4 then?

ALL core framework classes, interfaces, functions, and traits should be within a dedicated namespace to avoid clashing with other components defined outside the core framework. You should have some type of dedicated namespace that all your core components will be within ie. MyFramework\Core

Than if you were to use composer your framework would live under a directory lib or src and that directory would be mapped to MyFramework for autoloading. So MyFramework\Core\FrontController would map to a class inside src/Core/FrontController.php.

There is an article on Sitepoint that goes over properly setting up a project with composer but I can’t find it. Perhaps someone else knows the article I’m referring to.

The app directory will only contain code that is specific to the application. However, the framework code that can be reused would go into a directory like src. That is the way most modern frameworks separate things out so the framework code can be managed using composer and is separate from boilerplate application code that would go under an app directory.

@oddz

Do you mean something like this?

app/
   MyVendor/
        Adapter/PdoAdapter.php
        Core/     (essential files required by one or more modules)
        Contact/
            Controller/
            Mapper/
            Service/
            View/
        Strategy/

or

app/
   src/
        Adapter/PdoAdapter.php
        Core/     (essential files required by one or more modules)
        Contact/
            Controller/
            Mapper/
            Service/
            View/
        Strategy/

You might want to watch this series of tutorials on youtube: https://www.youtube.com/watch?v=Aw28-krO7ZM

I’ve been using it as a basis for an MVC app, which I’ll increase the functionality of over time

1 Like

I think the point oddz is getting at is that you should distinguish between the framework and an app that uses the framework. They are two distinct projects.

Your framework might have a structure such as this:

MyFramework/
    src/
        Core/
        Adapter/
        ...
    vendor/
        (other projects that your framework depends on will be put here by composer)
    composer.json

And an app that uses this framework might have a structure such as this:

MyApp/
    src/
        Controller/
        Model/
        View/
        ...
    public/
        images/
        css/
        ...
    vendor/
        myframework
        (other projects that your app depends on -- including the framework itself -- will be put here by composer)
    composer.json
2 Likes

Another couple of good resources (that frankly I like better than this video series) are:

@Jeff_Mott

Thanks for writing the example.

In an app that uses MyFramework, shouldn’t it be like this?

...
vendor/
        MyFramework/
        (other projects that your app depends on -- including the framework itself -- will be put here by composer)

According to http://www.php-fig.org/psr/psr-4/

When loading a file that corresponds to a fully qualified class name …

  • A contiguous series of one or more leading namespace and
    sub-namespace names, not including the leading namespace separator,
    in the fully qualified class name (a “namespace prefix”) corresponds
    to at least one “base directory”.
  • The contiguous sub-namespace names after the “namespace prefix”
    correspond to a subdirectory within a “base directory”, in which the
    namespace separators represent directory separators. The subdirectory
    name MUST match the case of the sub-namespace names.
  • The terminating class name corresponds to a file name ending in .php.
    The file name MUST match the case of the terminating class name.

I think we don’t need a directory with a name that matches the Vendor name in our namespace.

In this example http://www.php-fig.org/psr/psr-4/examples/

I don’t see a directory with name of Foo

Unless I understand it incorrectly??

What Jeff_Mott said.

The framework is nothing more than another dependency for the application. The framework should not rely on the application but application can rely on the framework. These are implicit patterns enforced by using modern, well known open source libraries like Symfony, Laravel, SwiftMailer, Doctrine, etc.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.