Nimble Framework [NMB]: A PHP MVC Framework

 

INTRODUCTION

This is a collection of the planning documents and reference materials that I used in creating the intial version of the Nimble Framework.  It was my intention to follow a specific "curriculum" in learning programming techniques before embarking on the project, but (as with many good intentions) I ran out of time and what started as a fairly carefully planned document quickly became a scratch sheet for whatever I had going at the moment.  I have preserved the chaotic documents as-is for the moment but hope to work on refining this as I work on revising the framework.

 

PRELIMARY TOPICS

Before beginning the journey into building a suite of web applications, I first had to cram about object-oriented programming in PHP5.  I had done some in PHP4, but I surely didn't have a very good understanding of what I was doing.

If I had to provide a sort of curriculum outline for my reading, it would go something like this:

0. Style
    a. style conventions
    b. defining a namespace
    c. Documentation
1. Classes and Objects
    a. classes
    b. methods
    c. properties
    d. objects
2. Inheritance
    a. scope
    b. static properties and methods
    c. overloading
    d. polymorphism
    e. serialization
3. Design Patterns / Object Interaction
    a. (cover several basic php design patterns)
    b. set/get
    c. application design patterns (MVC, etc)
    d. good design (???)
4. PHP Specifics
    a. Frameworks, Libraries
    b. Error and Exception handling
    c. Security


References:

 

0. STYLE

Guidelines for writing slim OO PHP:


Good design:

 

[Where did step 1. go??]

 

2. INHERITANCE

When creating an inheritance tree, try using the "is a" test.  Say you have a class ParentObject() and ChildClass().  To test it, simply say "ChildClass() 'is a' ParentObject()."  If the sentance sounds absurd, then the child class likely does not belong in the inheritance tree.  "Inheritance should be used for objects of a similar type (i.e. parent types and subtypes...Mammal could be a sub-class of Animal for instance). It is *not* meant to be a convenient way of sharing methods across multiple classes."

"Favour composition over inheritance"

Some advice from a SitePoint user: Inheritance is best used to create different flavours of the same thing. It's not a good idea to join together classes with different responsibilities with in this way. Also, inheritance trees are usually best kept small - 2 or 3 three deep.

OOP is all about encapsulating different responsibilities in different classes but that creates a problem: if everything is parcelled up in discrete packages, how do you then get objects talking to each other? Reading up on design patterns can provide some ideas. Basically there are two options: if object A needs to send messages to object B you can either pass B into A or A can instantiate B.

A Registry can be useful but first I'd try to solve the problem by looking for another way to introduce different objects to each other. Observer might be a solution for the logging problem. I'd also be asking if I really need to log at all. One of the best ways to solve OOP design problems is to dump things you don't really need thus reducing the complexity (perhaps you do need it though).

 

3.  DESIGN PATTERNS / OBJECT INTERACTION

MVC
Each of these elements, at their most basic level, should function across as many individual environments as possible (with the exception of the controller, which will interact with the user solely through HTTP).  This means at least three things: (1) Using abstraction and interfaces in situations requiring specific applications of methods to environments (such as databases, authentication methods, or the needs of applications written in teh framework).  (2) Using "loose coupling" to insure higher-level methods are adaptable to new situations and needs.  (3) Creating or adapting generalized libraries to handle common, system-wide functions.

References:


CONTROLLER
Application Flow
Collects user input, controls model and view.


References:


MODEL
Business/Domain Logic
Deals exclusively with accessing or changing persistant data.


VIEW
Presentation Logic
Deals exclusively with styling and presenting raw data to the user.


References:
Article on templating and the 'Template View Pattern': http://www.phpwact.org/pattern/template_view

[And then all coherance ends...]

Random links:


Design an MVC framework to run a suite of web applications within.

- Program will be Object Oriented in PHP5 (see: http://php.net/manual/en/language.oop5.php )

- Remember to create my own psuedo-namespace (NMB_Class).

- Learn about how to create and use a PHP Front Controller design pattern.
    How to make clean URLs without mod_rewrite: http://www.phpfreaks.com/tutorials/149/0.php  Also look into using a .htaccess file that php generates.

    The index page interprets requests either directly via GET or indirectly via a disguised method (such as WordPress uses for its more realistic-looking URLs.)  For instance, I could request: index.php?get=page .  In this instance, the index.php file would create all of the various global classes it needs, run any application-wide tasks (such as checking that a user is logged in), then use an index file to locate and load the class associated with "page."  The class associated with "page" runs and any output is passed to the template which then constructs the view and passes it to the user.  (See "Request Flow" here: http://www.onlamp.com/pub/a/php/2004/07/08/front_controller.html?page=2 )

- Learn about how to create and use a PHP templating system (or just use Smarty).

- Learn more about MVC frameworks as a whole.

    Applications run as modules within a larger framework.  (See http://www.onlamp.com/pub/a/php/2005/09/15/mvc_intro.html )


- Make sure everything is secure.

    ( http://www.oreilly.com/catalog/0596007418/?CMP=ILL-4GV796923290&ATT=/php/2004/07/08/front_controller.html )


ERROR HANDLING / LOGGING: http://www.phpfreaks.com/tutorials/117/0.php

 

NIMBLE CMS PLANNING

 (Marionette 2.0) [Should be called "Nimble Site Manager"]

[Note that everything below was initial planning--the actual framework was quite different in the end.  Actual documentation for it is available... somewhere.]


The Nimble CMS will be the base application in the Nimble Framework.  Several other applications, such as a new version of Nimble Storyteller, should extend several of the UI and filesystem functions developed for the Nimble CMS.

Master Config File
- change admin password [admin should be a group-level user with all privaleges to every site]
- Specifies Default URL
- Specifies Authentication Method (speicfy LDAP server, if apropriate)
- Specify MySQL server, username, and password
- Specify default connection method (ftp / ftps?)
- Speficies DB type (?)
- Specifies PHP version (?)
- Specify global scripts file?

Login
- LDAP login
- User-Creation
- Groups
    - Permissions (Allow users to [post/edit events, post/edit news stories,] post/edit pages (by page), edit site options, publish)

Set-Up / User Options / Site Options
- Allow multiple sites per user  If a user has more than one site OR if they are in a group, they should be presented with a menu asking them which site they wish to login to.
- Specify URL of website to edit (defaults to ~username on localhost)
- provide username (defaults to username)
- provide password (do not set a default - offer option to save the password)
- select connection method and connection address (ftp is default - ftp.localhost is default)
- select template (user should be presented with a default template(s) or by offered the opporunity to upload a custom template.  Custom templates consist of a header, footer, nav file, and css.)

Edit Functionailty
Once a user logs in and chooses a site to edit, they should be dropped directly to that site's index page (or a blank index page if it is a new site).  There should be a thin frame at the top with file system features etc.  Otherwise editing should be WYSIWYG.

Top Frame Options: Save / Publish / Logout / Delete / Images / Edit HTML / Buttons Allowing you basic formatting functions (need a special button to input a table)


User / Site Data should be stored centrally in the database.  Pages generated by marionette should be php with meta-data comments mirroring their associated DB information.  When the user logs-in, scan the meta-data quickly to look for changes?  Pages should follow the same basic templating system I have used for the college site with the env include file being generated by marionette based on site options.



DB STRUCTURE

- Marionette admin is treated just like a normal user insofar as being stored in the DB goes...
- The users table, if using LDAP, is auto-populated with data from LDAP.
- Groups exist independant of sites so that one group can control multiple sites, but only one user or group can be assigned to a site.
- sites/ftps is a 1/0 indicator of whether the site should be connected to using secure ftp.
- pages/lock is a 1/0 indicator of whether a page is "locked."  If it is locked, it can only be editted by the site owner or group admin.



MARIONETTE DB

users
id | name | fname | lname | email

groups
id | name

group_membership
id | group_id | name_id | edit_pages | publish | edit_options | group_admin

sites
id | owner_id | group_id | name | url | ftp_url | ftps | username | password | template_css | template_header | template_footer | template_nav

pages
id | site_id | pathname | title | contents | lock

folders
id | site_id | pathname | lock


CLASSES
COMMON
Config File to Env Constants
Database Abstratction Layer
Database Data Extraction
LDAP Authentication
DB User Authentication
Input Filtering
Templating??

SPECIAL
Constructor? (instantiates base classes needed for a given project)

 

RANDOM NOTES

- used only cookie-based authentication (not session).  For users to elect to save their session, set an expiry date in the future.  For other users, set no expiry and the cookie will expire when the browser is closed.  Logout overwrites the cookie with null.

- Remember to store login data in the cookie encrypted

- look up set_cookie() (automatically encodes data) or setrawcookie() (data must be encoded via urlencode() beforehand) to get all the info for how to set the cookie

- all cookie info is available in $_COOKIE

- example of setting a relative expiry date:

setcookie('version', phpversion(), time() + 21*24*60*60);

or set the cookie based on the client's time value with this Javascript (script passes the detected value to a php script)

<script language="JavaScript"
  type="text/javascript"><!
  var epoche = (new Date()).getTime();
  epoche = Math.floor(epoche / 1000);
  location.replace("setcookie-specific.php?time=" +
    epoche);
//></script>


- to delete the cookie, send a new null cookie with the same name and an expiry date in the past.  To avoid browser handling differences, it may be wise to send two seperate cookies with the same name to achieve this.

- If you do not set a domain, then it defaults to the current domain.  (or I could just use the config constant)

- Since creating or destroying cookies happens over two pageloads (one to set the cookie, one to for the change in state to be reflected), use header() and a redirect to force the second pageload so that the state change occurs immediately.  You can also use this to check that the browser accepts cookies.

EXAMPLE:
<?php
  if (isset($_GET['step']) && $_GET['step'] == '2')
   {
    $test_temp = isset($_COOKIE['test_temp']) ?
      'supports' : 'does not support';
    $test_persist = isset($_COOKIE['test_persist'])
      ?
      'supports' : 'does not support';
    setcookie('test_temp', '', time() -
      365*24*60*60);
    setcookie('test_persist', '', time() -
      365*24*60*60);
    echo "Browser $test_temp temporary cookies.<br
       />";
    echo "Browser $test_persist persistent cookies.";
  } else {
    setcookie('test_temp', 'ok');
    setcookie('test_persist', 'ok', time() +
      14*24*60*60);
    header("Location:
      {$_SERVER['PHP_SELF']}?step=2");
  }
?>


- You can store an array of data in a cookie using serialize() and unserialize()  Here's a small library that allows for this functionality:

<?php
  require_once 'stripCookieSlashes.inc.php';

  function setCookieData($arr) {
    $cookiedata = getAllCookieData();
    if ($cookiedata == null) {
      $cookiedata = array();
    }
    foreach ($arr as $name => $value) {
      $cookiedata[$name] = $value;
    }
    setcookie('cookiedata',
      serialize($cookiedata),
      time() + 30*24*60*60);
  }

  function getAllCookieData() {
    if (isset($_COOKIE['cookiedata'])) {
      $formdata = $_COOKIE['cookiedata'];
      if ($formdata != '') {
        return unserialize($formdata);
      } else {
        return array();
      }
    } else {
      return null;
    }
  }

  function getCookieData($name) {
    $cookiedata = getAllCookieData();
    if ($cookiedata != null &&
      isset($cookiedata[$name])) {
        return $cookiedata[$name];
      }
    }
    return '';
  }
?>