2

Does the below code show an acceptable way to cache both fully built pages and database queries?

The caching of built pages is started with the __construct in the controller and then finished with the __destruct, in this example all pages are cached for a default of 15 minutes to a file.

The query caching is done with apc and they are stored in memory for the specified amount of time per query. In the actual site there would be another class for the apc cache so that it could be changed if required.

My aim was to build the most simple possible mvc, have I failed or am I on the right sort of track?

Controller

//config
//autoloader
//initialiser - 

class controller {

    var $cacheUrl;

    function __construct(){

        $cacheBuiltPage = new cache();
        $this->cacheUrl = $cacheBuiltPage->startFullCache();
    }

    function __destruct(){

        $cacheBuiltPage = new cache();
        $cacheBuiltPage->endFullCache($this->cacheUrl);
    }
}

class forumcontroller extends controller{

    function buildForumThread(){

        $threadOb = new thread();
        $threadTitle = $threadOb->getTitle($data['id']);

        require 'thread.php';
    }
}

Model

class thread extends model{

    public function getTitle($threadId){

        $core = Connect::getInstance();
        $data = $core->dbh->selectQuery("SELECT title FROM table WHERE id = 1");

        return $data;
    }
}

Database

class database {

    public $dbh;
    private static $dsn  = "mysql:host=localhost;dbname=";
    private static $user = "";
    private static $pass = '';  
    private static $instance;

    private function __construct () {
        $this->dbh = new PDO(self::$dsn, self::$user, self::$pass);
    }

    public static function getInstance(){
        if(!isset(self::$instance)){
            $object =  __CLASS__;   
            self::$instance = new $object;
        }
        return self::$instance;
    }

    public function selectQuery($sql, $time = 0) {

        $key = md5('query'.$sql);

        if(($data = apc_fetch($key)) === false) {

            $stmt = $this->dbh->query($sql);
            $data = $stmt->fetchAll();

            apc_store($key, $data, $time);
        }
        return $data;
    }
}

Cache

class cache{

    var url;

    public function startFullCache(){

        $this->url = 'cache/'.md5($_SERVER['PHP_SELF'].$_SERVER['QUERY_STRING']);   

        if((@filesize($this->url) > 1) && (time() - filectime($this->url)) < (60 * 15)){
            readfile($this->url);
            exit;
        }

        ob_start();

        return $this->url;
    }

    public function endFullCache($cacheUrl){

        $output = ob_get_contents();
        ob_end_clean();

        $output = sanitize_output($output);

        file_put_contents($cacheUrl, $output);

        echo $output;
        flush();
    }

}

View

<html>
<head>
<title><?=$threadTitle[0]?> Thread - Website</title>
</head>
<body>

    <h1><?=$threadTitle[0]?> Thread</h1>

</body>
</html>
Dan
  • 11,914
  • 14
  • 49
  • 112
  • I am not sure what you have there, but it definitely is no MVC. You might be unaware of it, but MVC **does not** stand for "My Very Code". – tereško Feb 20 '13 at 15:46
  • possible duplicate of: [How would you cache content in an MVC project](http://stackoverflow.com/q/13497825/727208) – tereško Feb 20 '13 at 16:00
  • not really ... you still seem to be under the impression that template is a "view" and database abstraction is a "model". And I am quite confused about what your "controller" is supposed to be doing. – tereško Jan 15 '14 at 19:29
  • Well the view is a template here because I think it's all that's needed? I don't think database is a model but I just grouped the code there because the model interacted with the database but I guess it's not in any of the 'mvc'? As I said, this is a first and there's no simple explanations of building a simple php mvc so it's tricky. Updated again, making any sort of sense now? – Dan Jan 15 '14 at 19:43
  • Your `$core` seems to be a god object (http://en.wikipedia.org/wiki/God_object) – Yang Jan 16 '14 at 07:13

4 Answers4

8

"Outsource" the caching

First of all, you have to understand that caching of GET request is usually done all over the internet. Especially if your user is connecting via some kind of proxy.

And then there is also ability to set long expire time, so that user's browser does the caching of the HTML page and/or the media files.

For starting to look into this, you should read these two articles.

What is your goal?

Before you begin attempting to add cache, make sure that you actually need it. Do some benchmarking and look into what are your bottlenecks. Optimization for sake of optimizing is pointless and often harmful.

If you KNOW (and have data to back it up .. it's not about "feeling") that your application is making too many SQL queries, instead of jumping to query caching, you should begin by examining, what are those queries for.

For example:
If you see, that you are performing slow query each page-view just to generate a tag cloud, you instead should store the already completed tag cloud (as HTML fragment) and update it only, when something has changed.

Also, "add cache" should never be your first step, when trying to improve performance. If your queries are slow, use EXPLAIN to see if they are using indexes properly. Make sure that you are not querying same data multiple times. And also see, if queries actually make sense.

This is not MVC

I am not sure, where did you learn to write this way, but you seem to be missing the whole point of MVC:

  • "view" is not a template
  • "model" is not a database abstraction
  • "controller" is not application logic

You also seem to be missing the meaning for the word "layer". It is NOT a synonym for "class". Layers are groups of reusable components that are reusable in similar circumstances [1].

You might benefit from reading this and this post. They should help you understand the basic of this architectural pattern.

Where to cache in MVC architecture?

There are basically two points at which you can do the caching, when working with MVC (or MVC-inspired) architecture: views and persistence logic.

Caching for views would mostly entail reuse of once rendered templates (each view in MVC would be juggling multiple templates and associated UI logic). See the example with tag cloud earlier.

The caching in persistence logic would depend on your implementation.

  • You can cache the data, that is supposed to be passed to the domain objects, withing services:

    Note: in real application the new instances would not be here. Instead you would be using some factories

    $user = new User;
    $user->setId( 42 );
    
    $cache = new Cache;
    
    if ( !$cache->fetch( $user ))
    {
        $mapper = new UserMappper;
        $mapper->fetch( $user );
    }
    
    $user->setStatus( User::STATUS_BANNED );
    $cache->store( $user );
    $mapper->store( $user );
    
    // User instance has been populated with data
    
  • The other point for cache would be Repositories and/or Identity maps, if you expand the persistence layer beyond use of simple mapper. That would be too hard explain in with a simple code example. Instead you should read Patterns of Enterprise Application Architecture book.

.. some other bad practices in your code:

  • Please stop using singletons for establishing DB connection. It makes impossible to write unit tests and causes tight coupling to specific name of a class. I would recommend to instead use this approach and inject the DB connection in classes, that require it.

  • Also, if your query has no parameters, there is no point in preparing it. Prepared statement are for passing data to SQL .. but none of your example has any parameters.

    This issue arises mostly because of your magical Database class. Instead you should separate the persistence logic in multiple data mappers. This way you would not be face with self-inflicted problem of having single method for all queries.

  • The var keyword is an artifact of PHP4. Nowadays we use public, private and protected.

  • You are hard coding the connection details in your code. It is basically a violation of OCP (the dumbed-down version: here).

Community
  • 1
  • 1
tereško
  • 58,060
  • 25
  • 98
  • 150
  • Thanks for this information, I'm still learning how best to approach building an mvc. I do think I created the question wrongly though so I've had to modify it, do you think my 'mvc' is a little more inline now or is it way off the mark? I'll certainly be thoroughly reading your examples! – Dan Jan 15 '14 at 19:14
  • 1
    "view" is not a template "model" is not a database abstraction "controller" is not application logic LOL What?! The model is typically used to represent an entry of a table stored by the database. BUT you need also an database abstraction layer, in order to connect to it. And that´s also a part of the model. There are also two diffent philosophies about the MVC pattern: Business logic can be implemented in the controller, as well as in the model in form of passive methods. The controller needs to call it. They do nothing alone. In the first one mentiod you got huge controllers in the other not – alpham8 Jan 15 '14 at 22:49
  • MVC is an architectural pattern. The basic premise is separating presentation layer (which then is separated in views and controllers) and model layer (which will probably contain [domain objects](http://c2.com/cgi/wiki?DomainObject), services and persistence abstraction - probably data mappers). The controllers are tasked with handling user input, views are responsible for managing UI logic (optimally, by juggling templates and [presentation objects](http://martinfowler.com/eaaDev/PresentationModel.html)) and model layer as whole contains the domain business logic. – tereško Jan 16 '14 at 05:04
  • @tereško: As I said, 2 different philosophies. Everyone is choosing his oder her flavor here. Zend says, keep controller as small as possible and my boss says please place the businnes logic into the controller, because we are using an ORM, and all self-implemented logic in the modell classes will be getting lost, because these classes are generated by the ORM. And yes, in both the controller "controls" everything. That means it chooses the right view to display and initializes the data objects from the model. This is what I know. A third philisophie is to make one more layer for only the BL. – alpham8 Jan 16 '14 at 06:57
  • 2
    @alpham8 ORM is anti-pattern http://seldo.com/weblog/2011/08/11/orm_is_an_antipattern . As for MVC you can read this : http://poincare.matf.bg.ac.rs/~andjelkaz/pzv/cas4/mvc.pdf – Yang Jan 16 '14 at 07:04
3

Update

Is this an acceptable way to cache both queries and built pages in a PHP mvc?

The caching of built pages is started with the __construct in the controller and then finished with the __destruct, in this example all pages are cached for a default of 15 minutes to a file.

The destructor will be called when the all references are freed, or when the script terminates. I assume this means when the script terminates properly. I would say that critical exceptions would not guarantee the destructor to be called.

for example

 class A 
 {
    public function __construct()
    {
       echo "Construct\n";
    }

    public function __destruct()
    {
      echo "Destruct\n";
    }
 }

test code:

   $test = new A();
    die( "Dead\n");  // Will output Construct; dead; Destruct

   $test = new A();
   throw new Exception("Blah\n"); // Construct, Fatal error (no destruct)

   $test = new A();
   require_once( 'invalid_file.php'); // Construct, Fatal error (no destruct)

You'd better use register_shutdown_function() to go for sure. but be careful

Multiple calls to register_shutdown_function() can be made, and each will be called in the same order as they were registered. If you call exit() within one registered shutdown function, processing will stop completely and no other registered shutdown functions will be called." There's no guarantee that your shutdown function will be called if some other shutdown function runs before yours.

IMHO, you are reinventing the wheels there is a lot of frameworks already. do you want a simple and small one ? well there is Silex

which is built from the same guy who created Symfony. trust me you will waste your time.

or why not try using Laravel you can choose only the packages you want not all the framework they already have a well designed caching system see http://laravel.com/docs/cache

To understand how caching is done right in MVC style Symfony 2's documentation has a very good explanation

Also read this question Caching strategies in MVC Framework

In terms of how necessary any of this is; it really depends on your situation. In my experience, unless you're expecting large numbers of users (say, more than a few dozen on your site at the same time), you don't need caching. A site like StackOverflow, on the other hand, would not be able to function without a very well thought out caching strategy.


How would you create a build function to cache the entire built page?

as i understood from your question you want to cache the entire page ?

How would you create a build function to cache the entire built page?

you can simply use output buffering to achieve this.

an example is that you have index.php code:

<?php

 ob_start();   

 // All your mvc and application logic here

 $output = ob_get_contents();
 ob_end_clean();

the entire page output between ob_start() and ob_get_contents() is now captured in $output

you can save it in a file and do what ever you want

you can read more about ob_get_contents() in PHP Website

Community
  • 1
  • 1
Hmmm
  • 1,774
  • 2
  • 13
  • 23
2

It all depends on where your performance problems lie. If they are within your DB queries, then cache those - but of course your controller needs to be prepared for dirty data.

If you were to cache stuff at the controller layer, then that will perform better, but you'll probably have more stuff to cache (your DB data is going to be smaller than your HTML). Then of course the user will need to be prepared for seeing dirty data.

Unfortunately you can't really have any hard and fast rules because each solution has different requirements. My advice is to only start caching data when you really need to. Have a close look at where your performance bottlenecks are and do your caching appropriately, and make sure you can scale outwards (more machines) not just upwards (increasing spec of machine).

Rocklan
  • 7,888
  • 3
  • 34
  • 49
1

I add to what CodingInsane have posted!

He is right this way you can easily make your own little cache Mechanism.

just write the content of $output (in the CodingInsane post) to a file like: the_file.php.cache and next time in the the_file.php read the content of the_file.php.cache and display it the user and exit.

However any cache mechanism needs a way to update (rebuild the page). To do so you just have to keep track if the content has changed or check the last time the content has changed and compare it with the last time the the_file.php.cache has been modified filemtime().

Good Luck!

Sed
  • 19
  • 1
  • 5