MVC Refactor, Take 2

It doesn’t seem that way at all to me. In the Web MVC examples, the repository and template rendering objects are still being injected. But in your examples, you’re not just abstracting away the objects themselves, you’re trying to abstract away the invocations we make on those objects, and trying to abstract away the arguments we pass to the abstracted invocations. Just because Web MVC doesn’t go to that extreme doesn’t mean its dependency objects aren’t still injected.

Chances are, sure, but not guaranteed. The two might actually be very different that we prefer to keep them as separate templates. But your approach that you advertise as more flexible doesn’t give us this choice.

Compared to Fowler’s original album = Album.find(id), your alternative strikes me as significantly more complex. I hope we get a lot of bang for this buck.

This relies on the very big assumption that albums, blogs and products all use the exact same logic. But one might load by ID while another loads by slug, or another might want to load using a pre-loading query, any of which would foil your reusability and force you to make separate model classes. Or one might be loading a single entity but still have other input to consider such as session data, which would force you to make separate controller classes.

I’m tempted to call your approach “premature refactorization”. You start with the assumption that certain chunks of code will be identical, then refactor as appropriate. But they often won’t be identical, and your prematurely refactored code ends up making things more rigid rather than more flexible.


I feel like this topic is almost talked out. I think the code comparisons were a lot better than our usual hand wavy claims; hopefully others thought so too. But obviously I’m not sold.

I’ll let you have the last word.

This is exactly my thinking too and why the request in my example MVC is passed on to the view, controller and model. The information needed to get all jobs done is in the request. However, the request is just the returned/ modified answer from the previous response, which was formed by the model. The view (the actual response) is only a representation of the model, be it HTML, JSON, PDF, etc. When you talk in “page controller terms”, you simply can’t say that really. The response in web MVC is a mixed representation of model and in some cases, too much controller logic (the dreaded fat controller). In other words, having to figure out what representation/ response needs to be sent in the controller with which model means a single controller class automatically breaks SRP. It is also why controllers in web MVC in the popular frameworks are rarely portable and hard to test.

Scott

If you ask me, the template selection excerpt of Fowler’s code belongs in the view, in the main “Album” template itself. So, depending on what data and state the model holds, the proper representation would be selected. This controller class is also clearly breaking SRP, IMHO. If the template decision needs to change, like a third album type is added, this controller would need to change. If the model source needs to change, I’d need to change this controller too. Two reasons for change at two different times = broken SRP. Granted, the model source probably won’t change at all. But, that is beside the point.

Scott

I realize now, I missed the most important part in my post above. The client clock model class. Doh! :open_mouth:

I am almost finished with including parameters in the URL to control anything the client wants. I’ll post everything again anew.

Scott

You’re constructing a template with a hardcoded filename. Just because it’s not a class doesn’t mean it’s not the same as new Template(). The same is true of a model, in most web mvc implementations the model is chosen using a service locator, again, going against IoC because the decision of what to include is being made in the controller, which means that the controller logic cannot easily be used with another template.

That’s just not true though is it? there’s no reason the template can’t do this:

if ($album instanceof ClassicAlbum) include 'classicalbum.html.php'
else include 'album.html.php';

This is a lot better than having the logic in the controller because the same controller can be used with this template or another template that does something entirely different.

That’s rather disingenuous. If we’re doing an apples to apples comparison it’s the model I provided vs fowler’s controller. In fowler’s example, the developer would need to write the entire controller, in my first example the developer needs to write a slightly shorter model. In my second, the developer has to write only the router configuration.

Which is true. Let’s say one of the templates needs some session data. If that’s the case you need to write a whole new controller action that loads the template and passes the session. Let’s start with fowler’s example (ported to php)

class AlbumController {
	
	
	public function doGet() {
	    $album = Album::find($request->getParameter('id'));
	    if ($album === null) $this->template->render('missingalbumerror.tpl.php');
	    else {
	    	if ($album instanceof ClassicAlbum) $this->template->render('classicalbum.tpl.php', ['album' => $album]);
	    	else $this->template->render('classicalbum.tpl.php', ['album' => $album]);
	    }

	}

}

Now from this base point, suppose I want a different template which displays something from the session and the album. I now need to add another function in the controller:

class AlbumController {
	
	
	public function doGet() {
	    $album = Album::find($request->getParameter('id'));
	    if ($album === null) $this->template->render('missingalbumerror.tpl.php');
	    else {
	    	if ($album instanceof ClassicAlbum) $this->template->render('classicalbum.tpl.php', ['album' => $album]);
	    	else $this->template->render('classicalbum.tpl.php', ['album' => $album]);
	    }

	}


	public function doGetWithSession() {
	    $album = Album::find($request->getParameter('id'));
	    if ($album === null) $this->template->render('missingalbumerror.tpl.php');
	    else {
	    	if ($album instanceof ClassicAlbum) $this->template->render('classicalbum.tpl.php', ['album' => $album, 'session' => $_SESSION['whatever']]);
	    	else $this->template->render('classicalbum.tpl.php', ['album' => $album, 'session' => $_SESSION['whatever']]);
	    }

	}

}

Admittedly, it would be possible to refactor the class and move the repeated logic into its own method… but this takes time!

On the other hand, with my approach, there is no need for this refactoring, you’d add the model:

class AlbumRepositoryModel extends RepositoryModel {
	public function getSession() {
		return $_SESSION['whatever'];
	}
}

and add the route accordingly to pull in the new template and the new model.

Alternatively if you need to load via a URL slug or another field in the database then it’s simple enough to make a generic model and controller to handle it (If it’s a common function like that).

I’m not sure I agree with this. Having used this approach for several years now (After migrating away from Web MVC as I found myself repeating code far too frequently and having to write far too much code to do simple tasks). With controllers for: Forms, Result sets (With extensions for paginate, search) and single entities, this handles 90%+ of my use-cases. On the odd occasion I do have to write a controller but in web mvc I’d have had to have written that controller anyway, and I’d also have had to have written it the other 90% of the times where I didn’t need to.

One last question if you don’t mind: I feel that this topic has been me explaining the advantages of a more mvc-like architecture and you mostly saying “You can do that in WebMVC too if you make these changes”, what I would like is a brief list of what you think the advantages are of web mvc over my suggested approach.

I’ll do the same for my approach:

  • Have to write significantly less code when just viewing a static template
  • Have to write significantly less code when loading data from a model without any user input
  • Have to write less code for common use cases (Forms, pagination, loading a single entity by ID)
  • Have to write the same amount of code otherwise
  • The model, view or controller can easily be substituted without rewriting the controller action
  • Which overall significantly lowers development time
  • Programming around interfaces gives a much more standardised approach so it’s easier for new developers to understand
  • Controllers don’t break SRP
  • Controllers don’t break encapsulation (Knowing what response type the view is)
  • Controllers don’t break LoD/Tell, Don’t ask
  • Looser coupling overall (Controllers are tightly coupled to models and templates in web mvc most of the time)

Ok, here goes nothing #2. This is going to start over, explaining my idea for MVC without a router. It also entails the “what ifs” Tom threw at me like what if the format needs to be different or the data needs to come from somewhere else. So I made a parameter builder. I’ll explain later.

Ok. Again, our entry is a front controller like Symfony’s.

app.php

<?php

/*
 * Generic entry point/ front controller.    
 */

require_once('HTTPConverter.php'); #


$httpConverter = new HTTPConverter();
$request = $httpConverter->createRequest();
$response = $httpConverter->handleRequest();
$httpConverter->sendResponse();

We call HTTPConverter.php

HTTPConverter.php

<?php


/*
 * A converter to convert HTTP to a request object and handle it to get a response
 *
 */

require_once ('Request.php');
require_once ('View.php');
require_once ('Controller.php');
require_once ('Model.php');

class HTTPConverter
{

    private $request;
    private $response;
    private $controller;
    private $model;


    public function __construct()
    {

        // prepare anything necessary

    }


    public function createRequest()
    {
        // create the request object
        $this->request = new Request();

    }

    public function handleRequest()
    {
        $this->model = new Model($this->request);

        // if the request is a write request (i.e. a post, delete or put), do the controller first
        if ( $this->request->isWrite() ) {
            $this->controller = new Controller($this->request, $this->model);
        }

        // you'll always need a response, so call the view to get it.
         $this->response = new View($this->request);
    }

    public function sendResponse()
    {
        echo  $this->response->render($this->model);

    }

}

This controls our MVC entry and exit points with request and response.

Like in the diagram I presented earlier.

We need the request object.

Request.php

<?php

/*
 * Create the Request object from global variables.
 */

class Request
{
    public $object;
    public $method;
    public $path;
    public $postData;
    public $urlParams;
    public $urlParamCount;

    public function __construct()
    {
       $this->setObject();
       $this->setURLParams();
       $this->filterPostData();
       $this->setMethod();

    }

    private function setObject()
    {
        $urlElements = explode('/', $_SERVER['REQUEST_URI']);
        $this->object = $urlElements[1];
    }

    private function setURLParams()
    {
        $urlElements = explode('/', $_SERVER['REQUEST_URI']);
        // because the object is always the first element and we already define its own variable, drop it
        $this->urlParams = array_slice($urlElements, 2);
        $this->urlParamCount = count($this->urlParams);


    }


    private function filterPostData()
    {
        //no filtering yet, but you get the point
        $this->postData = $_POST;
        return $this->postData;
    }

    private function setMethod()
    {
        switch ($_SERVER['REQUEST_METHOD']) {
            case 'POST':
                $this->method = 'update';
                break;

            case 'PUT':
                $this->method = 'create';
                break;

            case 'DELETE':
                $this->method = 'delete';
                break;

            case 'GET' :
                $this->method = 'read';
                break;

            default: 'read';
        }
    }

    public function isWrite()
    {

        return $this->method == 'update' OR $this->method == 'create' OR $this->method =='delete' ? true : false;

    }

    public function isRead()
    {
        return $this->method == 'read'? true : false;
    }

}

I added some logic to create the urlParams. They will be decisive in the later logic.

Now our first call to our application is only a read, so let’s show the view only.

View.php

<?php

/*
 * View class for creating an output
 *
 */

class View {

	private $template;
	private $model;
        public $headers;


	public function __construct(Request $request)
        {
            $this->template = $request->object . ".html.php";
	}

	public function render($model)
	{
            $headers = [];
            ob_start();
            require ( "./templates/".$this->template );

            return  ob_get_clean();
	}
}

In our HTTPConverter, we had first called on the initial model, which is all we need to render.

We have currently two choices (the parameter mapping needs to be expanded for more flexibility I know, but humor me on this). We can just call “/clock” or we can now call “/clock/ntp/date-only”. The initial result is the same.

Now we make a selection and need to go through a controller for the update and update the model.

Controller.php

<?php

/*
 * Our very, very, very thin controller! The thinnest controller ever!!!
 *
 */

class Controller {

	private $model;
        private $request;

	public function __construct(Request $request, Model $model) {
                $this->request = $request;
		$this->model = $model;
                $this->decideAction();
	}


        private function decideAction()
        {
            switch ($this->request->method){
                case "create":
                    $this->model->create();
                    break;
                case "update":
                    $this->model->update();
                    break;
                case "delete":
                    $this->model->delete();
                    break;
            }

        }

}

Here is our model.

Model.php

<?php

/*
 * A class for our model (building)
 *
 */

require_once('ClientModelFactory.php');



class Model {

	public $violations = [];
        private $request;
        public $clientModel;

        public function __construct(Request $request)
        {
            $this->request = $request;
            $this->buildClientModel();

        }

        public function buildClientModel()
        {
            $this->clientModel = ClientModelFactory::build($this->request);
            return $this->clientModel;
        }

	public function create()
	{
	    // create something new
	}

        public function update()
	{
	    $this->clientModel->update($this->request->postData);
	}

        public function delete()
        {
            // delete somthing
        }

}

And the ClientModelFactor.php

<?php

/*
 * A stimple factory to build our clientModels
 *
 */


class ClientModelFactory
{
    public static function build(Request $request)
    {
        $class = (ucfirst($request->object))."Model";
        require_once ("models/". $class .".php");
        if (class_exists($class)) {
            return new $class($request);
        }
        else {
            throw new Exception("Invalid object given.");
        }
    }
}

The trick now is the added logic to the client’s ClockModel.php, where the client creates all the necessary logic. This is actually where I want the parameters (the configuration) too, as it is ALL in one place. No crazy config files needed really. But, we can discuss the need, as there will be some for sure. At any rate, what was missing in my above example was the client’s model.

ClockModel.php

<?php

/*
 * Our clock model class
 *
 */

require_once('ObjectParameters.php');


class ClockModel {

    private $request;
    public $timezones = ['Europe/London' => 'London',
                          'America/New_York' => 'New York',
                          'America/Los_Angeles' => 'Los Angeles'];

    // this is the URL structure. The first elements is always the object, everything after the object will be your object parameters
    // we'll need to come up with a more flexible method later
    public $urlPathMap = '/clock/dataSource/dateFormat';

    // since you have them in your path map above, you must also declare the variable defaults! This should also be automated!
    public $objectParams = ['dataSource' => 'default',
                                   'dateFormat' => 'default'];
    public $timezone;
    public $submitted = false;
    public $time;

    public function __construct(Request $request)
    {
        $this->request = $request;
        $this->getObjectParams();
    }

    private function getObjectParams()
    {
        $objectParameters = new ObjectParameters($this->request, $this->urlPathMap, $this->objectParams);
        $this->objectParams = $objectParameters->parameters;
    }

    public function create()
    {
        // create something new
    }

    public function update($postData)
    {
        if ( $this->isValid($postData) ) {
            $this->submitted = true;
            $this->timezone = $postData['timezone'];
        }
    }

    public function delete()
    {
        // delete something
    }

    public function isValid($post)
    {
        return (null !== isset($post['timezone'])) ? true : false;
    }

    public function getTime()
    {   //var_dump($this->objectParams->parameters);
         if ( $this->objectParams['dataSource'] === 'default' ) {
            $timezone = new DateTimeZone($this->timezone);
            $date = new DateTime;
            $date->setTimeZone($timezone);
            return $date;
        }elseif (  $this->objectParams['dataSource'] === 'ntp' ){
            $date = $this->getNTPTime();
            return $date;
        }
    }

    public function getNTPTime()
    {
        // go and get the time from some NTP server (for now just do the same as default as an example)
        $timezone = new DateTimeZone($this->timezone);
        $date = new DateTime;
        $date->setTimeZone($timezone);
        return $date;
    }

    public function getCity()
    {
        return $this->timezones[$this->timezone];
    }

    public function success()
    {
            $this->submitted = true;
    }
}

I realize this client model is getting very fat. But, it wouldn’t be at all hard to refactor and make some new sub-objects, like for the different tasks, like UpdateClockModel, DeleteClockModel, CreateClockModel.

The new part I’ve added it the Object Parameters object.

ObjectParameters.php

<?php

/*
 * This is where we setup the object parameters.
 */

class ObjectParameters
{
    public $parameters = array();
    private $request;
    private $clientURLPathMap;
    private $parameterKeys;
    public $defaultObjectParameters;

    public function __construct(Request $request, $urlPathMap, $defaulObjectParameters )
    {
        $this->request = $request;
        $this->defaultObjectParameters = $defaulObjectParameters;
        $this->clientURLPathMap = $urlPathMap;
        $this->setParameterKeys();
        $this->buildParameters();
    }

    private function setParameterKeys()
    {
         $mapElements = explode( '/',$this->clientURLPathMap);
         $mapLength = count($mapElements);
         $this->parameterKeys = array_slice($mapElements, 2, $mapLength - 1);

    }

    public function buildParameters()
    {
        // this is in flexible, as we can't set up for different parameter needs. Need to refactor.
        if ( $this->request->isRead() AND $this->request->urlParamCount === count($this->parameterKeys) ) {
            for($i=0; $i < $this->request->urlParamCount; $i++) {
                $this->parameters[$this->parameterKeys[$i]] = $this->request->urlParams[$i];
            }
       }else{
           $this->parameters = $this->defaultObjectParameters;
       }

       // this is ugly. Refactor later. But, needed to set up hidden fields for our parameters.
       if ( $this->request->isWrite() ) {
           foreach( $this->request->postData as $key => $value ) {
               foreach ( $this->parameterKeys as $parameterKey ) {
                   if ( $key === $parameterKey ) {
                       $this->parameters[$parameterKey] = $value;
                   }
               }
           }
       }
    }

}

I am not too proud of this class, as I know it is mofugly code. But, I hope it gets the point across at what I am attempting to accomplish.

Edit: Of course, it also works.

:blush:

Scott

This is an interesting approach but having the model decode the URL does feel like mixing responsibilities to me, the model is now making decisions about the application flow based on the path. Personally I’d split that into two different classes (because then you could have both the NTP model and normal model in different classes with the same interface)… effectively reintroducing a router. However, what I like about this is that it’s not a centralised routing table, each MVC triad has its own set of routes and makes its own decisions.

Without trying this in a real project it’s difficult to assess if there will be any fundamental problems, but the lack of a controller entirely does feel like it would cause repeated logic somewhere and the fact you had to combine the two model variants is a red flag.

My other comment is this:

class Model {

	public $violations = [];
        private $request;
        public $clientModel;

        public function __construct(Request $request)
        {
            $this->request = $request;
            $this->buildClientModel();

        }

        public function buildClientModel()
        {
            $this->clientModel = ClientModelFactory::build($this->request);
            return $this->clientModel;
        }

	public function create()
	{
	    // create something new
	}

        public function update()
	{
	    $this->clientModel->update($this->request->postData);
	}

        public function delete()
        {
            // delete somthing
        }

}

This should almost certainly be an interface rather than a class. Let the application developer implement the interface rather than having these wrapper methods that don’t really do anything.

interface Model {
	public function create(Request $request);
	public function update(Request $request);
	public function delete(Request $request);
}

I am really glad you are open to the direction I’m looking at.

There definitely needs to be a logic for taking the purpose of the intended web page request and turning it into a correct output. Let’s not call it “URL decoding” or even a router/ controller mix, but application processing logic or workflow logic. It is the model of how the application/ framework works internally. I’d say this needs a real home too. Putting it in a router and then using the resulting parameters from the Request and the Router (objects) in a controller, the model and/ or the View is splitting the processing workflow out into 4 different areas. That, to me, avoids KISS.

The fact I have called for the application processing logic in the Model is only because I feel that is the right time to call it. But, theoretically, the application processing logic can be called to be built anywhere after the Request object is built.

I’d like to point out too, the “decoded URL” logic, as you called it, or rather the application processing logic, as I’d call it, is also needed, in most cases, in the View, which, according to the definition of MVC, should get its application state and data from the model. This is another reason for why I felt the call to build the application processing logic needs to be done in the Model.

LOL! Now we are sort of getting into the realm of Tony’s argument. :open_mouth: Is a model class that calls on the object to build application workflow logic (along with data and state) AND building the client’s Model (along with data and state) doing too much? I’d say no. Because, the definition of MVC calls for it. The base Model Class will need to do even more actually, like validation and authorization. However, it shouldn’t all be done in the single class, but through a good number of sibling classes (which can also be extended), from which the Model will get its functionality.

Absolutely and the actual beauty of the idea of doing a whole lot more, practically everything, in the model (and doing the absolute minimum, practically nothing, in the controller). This is what you opened my eyes to. The clock model can easily be refactored into separate classes, without having to duplicate any code. Doing the same with a router would be very close to duplicated code.

Agreed. I was thinking the whole time the client Model would need to follow a certain pattern/ interface, and I am going to be totally honest and say, I actually am not completely sure how to implement it properly. :blush: I am most definitely playing the student role here and am loving it! Thanks for taking the time to teach and discuss the alternatives. I hope Jeff isn’t terribly upset with me working in my ideas here too. :smile:

Scott

Ok. I’ve been thinking about the “ObjectParameters” a bit more and have decided this isn’t modeling the situation properly. For example, any parameters needed for View logic shouldn’t really be in a set of “object” parameters, but rather in a “view” parameter set. This would simplify calling the parameters in the view for any necessary formatting purposes.

I am considering using Symfony’s HTTP foundation to create the request object. It sets up ParameterBag objects within the Request object and in this fashion, I am thinking we need to have two additional sets of parameters added to the request object to make my suggested MVC system work properly. A view and an object parameter bag.

However, before we can create these parameter bags, we’d need to send off the request to a system, which would decode the request into the client developer’s intended “conditions”. This would be along the lines to the well known routing logic, but instead of saying “Ok, because the call to the server is this, do controller XYZ”, we simply create the necessary “conditions” for the view and the model and leave out the controller, because we don’t need it. These new parameters are then used to control either formating in the view or to add logic to help formulate or format the model.

Actually, let’s call it just that. FormatConditions.

This is what I am looking at now.

Now to put it into (proper) code.

Scott

Ok. I started to work on the code for the above diagram, but decided to work on how the MVC part works together.

I’ve done this.

Now, everyone might be saying, “But Scott, what is the difference between calling a group of actions in a page controller setup and calling a number of classes (actions) in the model?” Well, you only have 3 ModelAction types. Create, Update, Delete.

I’ve refactored the code to now work with the following client code directory format.

Hmmm, we could actually get rid of the word “Model” now too.

I think that is a logical and simple way to build objects and the actions for them and it is standardized. This allows for easy manipulation of the model and whatever else you want to do from a business logic standpoint. It has nothing to do with a view and has nothing to do with a controller. The controller calls the ModelActions, they in turn, modify or create, or delete the model and the View “picks up” the result. It is the way I think MVC should be.

And now, the controller is the slimmest web MVC controller in the world! And actually, I’m calling this attempt webMVc. :grin:

<?php

/*
 * Our very, very, very thin controller!
 *
 */

require_once('ClientModelActionFactory.php');


class Controller
{

    public function __construct(Request $request, Model $model)
    {
            ClientModelActionFactory::doAction($model, $request);
    }

}

Now to go and do the rest of the refactoring. I’ll also be putting this up on Github for anyone who wants to follow along.

Scott

Oh, one important refactor I did. I decided I didn’t like the “clientModel” name and thought the client model objects should be named after the objects themselves. So, I did this in the Model.

<?php

/*
 * A class for our model (building)
 *
 */

require_once('ClientModelFactory.php');



class Model {

    private $request;
    public $clientModelName;

    public function __construct(Request $request)
    {
        $this->request = $request;
        $this->clientModelName = $this->request->object;
        $this->buildClientModel();

    }

    public function buildClientModel()
    {
        $this->{$this->clientModelName} = ClientModelFactory::build($this->request);
        return $this->{$this->clientModelName};
    }

}

As you can see, the clientModel is now named after the actual object being manipulated or called upon. So, the template now looks like this.

 <p>See what time it is!</p>

	<form action="/clock" method="post">
        <?php
        foreach ($model->clock->objectParams as $key => $value) { ?>
            <input type="hidden" name="<?php echo $key; ?>" value="<?php echo $value; ?>">
        <?php
        }
        ?>
	<label>Select a city</label>
	<select name="timezone" onchange="this.form.submit();">
            <option value="">Pick one!</option>
	    <?php foreach ($model->clock->timezones as $timezone => $city) {
	    ?>
	    <option value="<?php echo $timezone; ?>" <?php echo $model->clock->timezone === $timezone ? 'selected="selected"' : ''; ?>><?php echo $city; ?></option>
	<?php } ?>
	</select>
	</form>

<?php
if ($model->clock->submitted) { ?>

	The time in <em><?php echo $model->clock->getCity(); ?></em> is
        <strong>
        <?php
    if ($model->clock->objectParams['dateFormat'] === 'date-only') {
        echo $model->clock->getTime()->format('Y-m-d');

    }elseif ($model->clock->objectParams['dateFormat'] == 'default') {
        echo $model->clock->getTime()->format('Y-m-d H:i:s');
        ?>
        </strong>
	<?php
    }
}
?>

I am also inclined to dissect any client objects out of the model object, so that the call to the client model would be just $objectName->property (or ->method() ). Like $clock->getCity();

Scott

The more I think about this, the more it seems to make a whole lot more sense than what frameworks like Symfony and Laravel do with the router. For instance, let’s say a “page”, which equates to an “object” in webMVc, is not “viewable” due to authorization restrictions. The view calls the model on that certain object, the model say, “whoah, that object needs authorization”, so it tells the view to actually change the request to the “/login” object. The view says, “Ok. Give me the “/login” object”, which means the model is happy and give the view everything needed to “login”. That kind of model view communication logic is a whole lot simpler than what is currently going on in Symfony or Laravel, isn’t it?

Scott

The only problem is routes can be cached for speed and matched against regex for example something like:

 /dir/:param[+d]/page/:param2[0-9]

/dir/something/page/43 - ok
/dir/89/page/something - error
/dir/2/page/0 - error
/dir/2/page/ - error

Granted. And my idea with the FormatConditions to control any view or model formatting conditions will also need to allow for similar flexibility.They will also be cacheable, as they will also be compiled to PHP in the end, similar to how Symfony works with routes defined in any format other than PHP.

Scott

Any chance you could stick the entire working code on github so we can play around with it without needing to copy/paste and create all the files? :slight_smile:

Absolutely! I’ll upload it this evening. I’ll add a branch with the example clock too. I haven’t done much work since my last post though.

Scott

I am going to have to get to the repo tomorrow morning. I want to make some changes before I do and want a fresh mind to do it and I do better work in the morning. :blush:

Scott

1 Like

Ok. Here it is. I’ve added autoloading and I figure, I need to have testing, so the start is made. I just need to add tests.

https://github.com/SkooppaOS/webMVc

Scott

Some thoughts and suggestions.

  • I noticed there’s a lot of baked-in relationships between the URL and the classes. For example, the first path segment ($request->object) is used almost everywhere when deciding what classes to load and run. But what if we need more flexibility with the URLs? What if we need, for example, /community/forums, /community/articles, and /community/users? Every one of those would load classes based solely on the first path segment, “community”. And what if we needed an even more complex URL, such as /affiliates/{affiliate_slug}/community/forums? I strongly suggest you try to remove any hardcoded relationships with the URL. Instead, we should be able to configure which URLs are associated with what logic. That’s usually what the router does. :wink:

  • I’d also suggest trying to make a clear separation between pure domain logic and HTTP-specific logic. In traditional Web MVC, for example, the model layer is ignorant that it’s being used for an HTTP app. Ideally, the model layer could just as easily be used for a CLI app.

  • I want to emphasize that the model is a layer, and it encompasses a variety of kinds of objects, such as entities, repositories, forms, validators, commands, listeners, and so forth. But when you name every class in the model layer with a “Model” suffix, then I think it gets harder to discern what role it’s supposed to serve. For example, ClockModel vs UpdateClockModel. Is the former an entity? Is the latter an action? When so many classes are called “Model”, then the word becomes generic, loses meaning, and no longer communicates what the class represents.

I am really glad you took the time to dig into this.

What you mention above is the only realm where I am also not completely certain the concept is practical. I think it is, but, I need to work on some practical use cases. What is floating around in my head is a system of “formatting” or “parameter” configuration that entails the following:

  • the application (like community, in your example)
  • the object (like article or forum)
  • the parameters (from and matched to the HTTP request)
    • object/ model (to control what the model should be doing)
    • view (for view formatting)

At some point, there has to be a URL format and this will be completely up to the client coder and all the things above will be at her disposal as she sees fit.

Ah, this is a really good point and yes, the question begs to be asked. Where is the separation point? No matter what logic we have for any MVC, there is always a request and a response from an application, as MVC is basically the back end for a GUI. The fact HTTP “logic” follows this is just coincidental IMHO. There needs to be a request and there needs to be a response. Nothing else matters really. I’ve also noted in my readme.md, that one of my ToDos is to get the objectparameters out of the model. I think this goes along the lines of this point, so I agree. Whether or not what I come up with is still “enough separation” will be another question.

This is another great point. And for me, we need only a different request and response interface for CLI. It shouldn’t be an HTTPConverter, but rather maybe a ShellConverter. The fact we are using a different interface only means the request and the response need to be handled differently. The model would be the same. Completely the same. However, if we did need a different model, we just define different parameters, like the object or application config settings.

Scott