Over a million developers have joined DZone.

Web App Evolution: From Spaghetti to Layered Cake - Part II

DZone's Guide to

Web App Evolution: From Spaghetti to Layered Cake - Part II

The aim for the second phase of the evolution plan is to effectively separate the view and logic from the nested spaghetti of a code base into a layered cake that does several things.

· Web Dev Zone ·
Free Resource

Have you seen our HERE Twitch channel to livestream our Developer Waypoints series?

This is the second part of the series. For Part I, go here.

As previously illustrated, the aim for the second phase of the evolution plan is to effectively separate the view and logic from the nested spaghetti of a code base into a layered cake that consists of: 

  • a PHP framework
  • a router
  • a view layer with layouts/templates/subview components, etc.
  • a service layer with various service classes
  • a data layer with various data access objects

As previously mentioned, the framework of choice here is slim — and just to refresh your memory, the reason for not choosing large frameworks such as Symphony or Laravel here is mainly because my goal is to make a web app that also works (with minimal to no modification at all) with native apps, which means all the business logic and data access should be placed outside the app so it can be later on moved into distributed (micro) services.

Laravel/Symphony can surely do all these but then your web app is locked with a set structure and it's gonna be really hard to bust out of the system if say, you would like to upgrade or switch to a different framework, while slim is basically a library that is installed by composer, and stays isolated inside/vendor directory, it will be fairly easy to switch to any other framework in the future if the needs are there (plus the PSR-7 support makes it even more charming).

Make New Codes Work With Old Codes in Harmony

Before I get to the view engine part, I would like to briefly explain how this new structure is integrated into the existing code base with minimal interference to it.

Our server runs Apache with mod_rewrite, if your server is running NGINX, there is 'try_files' command which is the equivalent of mod_rewrite. So what I did was:

1. Create a new 'App/' directory in project root, with a structure as follows:

├── Controller
│   └── Base.php  
│   └── StaticPage.php
├── composer.json
├── composer.lock
├── routes.php
├── themes
│   └── default
└── vendor

2. Create a new file 'app_bootstrap.php' under the 'public_html' directory of the project, which contains only one line: 

  require_once __DIR__ . '/../App/routes.php'; 

The public_html folder now looks like this:

├── .htaccess
├── app_bootstrap.php
├── index.html
├── index.php
├── faq.php
├── contactus.php  

It's rather reckless if one tries to overwrite the entire codebase with the new structure, so here's the plan: baby steps. So what I did was fairly simple - add yet another rewrite rule inside the already massive .htaccess (don't worry, eventually it will be deprecated), and essentially point /faq to our brand spanking new app_bootstrap.php.

RewriteRule ^(faq)$ /app_bootstrap.php/$1  [NC,L]

So this is it, now /faq will go to our new app boostrap and all other pages will remain where they were. Later on, we'll tackle them one by one.

Controller Support in Slim 3

This is actually a bit of a hidden feature — if you search on stack overflow, you'll see a lot of people asking about how to use controllers with Slim 3 framework and most of them suggest a middleware for autoloading controllers. The truth is, it's supported out of the box.

Have a look at the code below:

namespace App;
use Slim\App;

$app = new App();
$app->get('/faq', 'App\Controller\StaticPage:faq');

As you can see - simply by using 'controller:method' pattern it will automatically find the controller and invoke the method. This way, we'll end up with a very neat routes.php file.

Here's the simple controller for the faq page:

namespace App\Controller;
use Slim\Http\Request;
use Slim\Http\Response;
class StaticPage extends Base
    public function faq(Request $request, Response $response, $args)

And here's the base controller:

namespace App\Controller;

use Coreorm\Slim3\Theme;
 * base controller
class Base
     * main container
     * @var Slim/App
    protected $app = null;

     * rendering engine
     * @var Theme
    protected $theme = null;
     * Base constructor.
     * @param $app
    public function __construct($app)
        $this->app = $app;

     * setup the rendering engine
    private function setUpTheme()
    $this->theme = Theme::instance(__DIR__ . '/../themes', 'default');

The slim3-view Rendering Engine

If you look into the setUpTheme() method, it shows how to set up the renderer:

  1. the first line tells it where the themes are stored ('themes/' dir), and which theme is currently selected ('default'). Alternatively the theme can be set at runtime by calling '$this->theme->setTheme('theme-name')'
  2. the second line tells the renderer the current layout is 'default', which goes to 'themes/default/layouts/default.phtml' (note slim3-view uses .phtml as the extension for theme files, they are really just php files)
  3. the third line tells the renderer to share 4 files (default layout, header,nav,footer under views/bits folder). Note that these are the relative path to the theme directory, and this tells the renderer to look for fall backs when you switch to another theme and if any of these files are non-existent, they will fall back to the shared counterparts.

Now if you look back at the faq() method in StaticPage class, you'll notice that it's got a very simple command - render('faq'), this basically tells the engine to render views/faq.phtml and output to the browser.

So here's the layout file:

<!DOCTYPE html>
    <html lang="en">
        <?php $this->import('bits/header') ?>
                <?php $this->import('bits/nav') ?>
                    <div class="container"><?php echo $mainContent; ?></div>
                        <?php $this->import('bits/footer') ?>

see how simple it is? The API 'import()' renders a sub view and prints it out at the spot, so the possibility here is infinite.

Here's the entire themes directory structure and you'll find it fairly straightforward:

└── default
    ├── layouts
    │   └── default.phtml
    └── views
        ├── bits
        │   ├── footer.phtml
        │   ├── header.phtml
        │   └── nav.phtml
        └── faq.phtml

So imagine you have a new theme called 'custom-1' (I know, bad naming convention, but I bet some of you out there did this before), it'll be parallel to the default theme dir, with a similar structure. e.g:

├── custom-1
│   ├── layouts
│   │   └── new-layout.phtml
│   └── views
│       └── faq.phtml
└── default
    ├── layouts
    │   └── default.phtml
    └── views
        ├── bits
        │   ├── footer.phtml
        │   ├── header.phtml
        │   └── nav.phtml
        └── faq.phtml

Note it actually doesn't have the entire views/bits/ directory? Well, previously we told the renderer to share all the files under bits in the controller, so now if we decide to switch to the new custom theme, and the renderer will be smart enough to use the shared views inside the new theme. And how do we switch the theme and the layout? It's as simple as:


And that's it, now we have the new structure that supports theming, separate logic and views, and co-exists with existing codebase. I just need to take baby steps, page by page, section by section and migrate the rest of the spaghetti into this layered cake.

P.S. coreorm/slim3-view supports nice features like reusable HTML, true partial data passing (means you can do real data isolation), in memory rendering caching, just read the readme here, go check out the example repo (https://github.com/coreorm/slim3-view-example/), or head directly to the example site on Heroku (https://slim3-view-example.herokuapp.com/).

Developer Waypoints is a live coding series from HERE, which will teach you how to build with maps and location data.

php ,slim framework ,micro services ,view engines ,template engine

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}