MW - a lightweight MVC solution.

The Idea

If you are reading this page, you are pheraps aware of what MVC is - it's the same ol' story since Smalltalk. Anyway, my goal was to make a giant leap in my web-development understanding, so I had to master these topics, or at least have a clue on them.

  • PHP OOP, namespaces, lambda functions;
  • HTTP routing;
  • Secure login and firewalling;
  • Templating engines (as Smarty or Twig);
  • HTML5 / CSS3 magic.

After some months of hard coding, I may [proudly] say that the goal has been reached: you can find MW on Github.

Keeping in mind that MW was not born as a killer piece of code, but rather as a self-teaching experiment, I focused on solving a common need in small projects developing: dealing with prehistoric hosting services with little-to-zero setting management, no PHP cache allowed, and so; having full control of the code without any outer dependencies makes me feel more secure about moving that little real-estate agency service on the production server.

The Structure

This is the folder structure of a basic MW project:

\[project-name]
	\src
		\App
			\Entity
			\Controller
			\Repository
			\View
			\...
		\MWCore
			\boostrap.php
			\Component
			\Interfaces
			\Kernel
			\...
	\web
		\css
		\img
		\js
		\...
	\index.php
	\.htaccess

The main elements are:

  • The App folder, containing the application models, controllers, repositories, created on purpose;
  • The MWCore, with all the basic classes for routing, fetching views, connecting to db, sending mail, handling HTTP reqs and so;
  • The web folder with all the client side stuff as css, js, pictures;
  • The index.php file, holding almost all the configuration and serving as unique access point for the whole application.

The .htaccess file just strips the ugly index.php from URLs.

Feature Overview

Like all respectful MVC framework, MW has many robust features. Let's see:

  • Routes are the beating heart of MW [and of the Internet itself!].
    $routes = array(
    	new MWSingleRoute(
    		"login",
    		"App\Controller\SecurityController",
    		"login"
    	),		
    	new MWSingleRoute(
    		"pages/{page}",
    		"App\Controller\PagesController",
    		"showPage"
    	)
    );
    

    Each route is built up of a pattern, an associated controller and an action to be perfomed. Route support parameters, indicated by curly brackets [see {page} in the code above].

    Whenever anyone points his or her browser to www.yourcooldomain.com/pages/foo, MW looks in its route table for a route like pages/{something}: of course there is, and it understands that "foo" is the param to pass to the showPage method of the PagesController: now the controller takes care of all the business logic, then fetches the associated view and renders it out cleverly to screen.

    Handling requests this way, you get a lot of benefits: single access point, firewall setup allowed [read: kickout anybody pointing at hyper/private/area if he or she is not logged in], hide inner logic, get cool and SEO-friendly urls.

  • Say goodbye to old $_VAR world: that's unsafe, unmaintainable, un-a-lot-of-things. Instead, MW wraps requests and sessions in safe classes, whose cleanup methods [strong against XSS, injections, CSRF] can be updates on purpose.

    // [inside any Controller method]
    // returns 'GET' or 'POST'
    $this -> request -> getMethod();
    // returns value if set in the request
    $this -> request -> get($value);
    // stores values in session
    $this -> session -> set($value);
    

    Making extensive use of singletons, you can edit, retrieve and store such data in any part of the code, with no fear of inconsistence or failure.

  • Users should see what's happening behind the scenes somehow: it's time for Views to join the party!

    Let's pretend we have a HelloWorldController with a sayHelloAction($name) method, pointed to by hello/{name} route:

    class HelloWorldController extends MWController
    {
    	
    	public function sayHelloAction($name)
    	{
    
    		$this -> requestView('App\View\Views/index',
    			array(
    				'name' => $name
    			)
    		);
    		
    	}
    	
    }
    

    The controller fetches the index.php file in the Views folder, passing all the params in a $data array:

    // index.php content
    <html>
      <head>
        <!-- stuff -->
      </head>
      <body>
        <h1>Hello, <?php echo $data['name']?>!</h1>
      </body>
    </html>
    

So pointing to www.yourcooldomain.com/hello/julia will print "Hello julia!", that easy. Got it: now you can do the same with data retrieved from persistence layer, and so. Beside example's sake, be aware that passing raw data from routes to html is the best way to get raped by XSS, so take your measures against.

Views support template inheritance, thanks to the PHP Template Inheritance simple yet effective library [one of the very few libs I used in MW]. Tools such as Twig are way more readable and manteinable I know, and I make large use of them in bigger environments, but MW wanted to stay minimal.

What I kept off MW (and why)

MW still lacks some handy features, and I'm still on the go about developing them or not. That's just because I don't want to reinvent the wheel - symfony already exists!, and I can't work full time on things such as:

  • a robust ORM like Doctrine: it does really save you *weeks* of work, and I make use of it in enterprise environment. In MW I still have to hardcode a lot of data-layer substrate - my goal is at least to leave all the MySQL table boilerplate to reflection over entity annotations.

  • A fixture loader manager, and even a migration service;
  • A full test-driven approach: I know I should, but I had no time yet. I will do, I promise.

I think it's pretentious for a single developer to rebuild existing open source tools *from scratch*, as long as they perform well: he should better join the team and help them to improve the existing product - a day has 24h, and we still have to sleep and to watch soccer anyhow.

Feel free to use it/tell me your opinion about, it would be very appreciated.