The MVC Mini Toolkit

The MVC Mini Toolkit

The mini toolkit you are about to meet makes sticking to an MVC design pattern a whole lot easier. The toolkit concerns itself only with the infrastructure of the MVC methodology. Later, you'll meet an off-the-shelf PEAR package called Smarty that allows for an effective implementation of the model and view components.

Introducing the Toolkit

The mini toolkit allows you to handle the connection between controller and model in a consistent, clean, and clear manner. The core of this is a request object that seeks to provide a more object-oriented perspective on the user's input into your application. As well as sporting the usual means for interrogating the user's input directly, it supports the concept of applying constraints to each parameter, such that you may easily specify in your code what you are and are not prepared to accept.

Such error-checking practices are almost certainly not new to you, of course. However, the concept of constraints allows you to separate the controller and model. The model may now assume that its input is within expected parameters, allowing you to divorce cleanly such code from methods within the model, safe in the knowledge that it has been vetted by the infrastructure of your MVC design pattern.

This is an entirely appropriate approach. After all, sanity checks are not core to your model's functionality. By leaving responsibility for such checks well outside the model itself, you can also apply different rules for different controllers and different views. This is very much the philosophy behind MVC.

You may want to extend or modify these classes to suit your own needs better; what is provided here is a very basic implementation. For example, you may want to examine the feasibility of adding methods to automatically perpetuate GET and POST variables using hidden form parameters, without having to turn to session variables. This may prove useful for the purposes of pagination forward and backward through a data set of search results, maintaining those search criteria with each request for a new page. Such functionality could easily be integrated into the request object itself.

In addition, the prepackaged constraint types supplied here are not exhaustive. However, a quick glance at the code should make it quite clear how easily you can add your own, should your application require them.

Constants

You do need to define a number of constants for your classes to work seamlessly together. They define the three types of parameter (GET, POST, and Cookies) as well as the various types of constraint you can apply. As previously mentioned, you should feel free to define additional constraints that may suit your application, as long as you also remember to implement them in the Request class.

Examine the following code, in a file called constants.phpm:

   <?php    // Constants    define(VERB_METHOD_COOKIE, 1);    define(VERB_METHOD_GET, 2);    define(VERB_METHOD_POST, 4);    define(CT_MINLENGTH, 1);    define(CT_MAXLENGTH, 2);    define(CT_PERMITTEDCHARACTERS, 3);    define(CT_NONPERMITTEDCHARACTERS, 4);    define(CT_LESSTHAN, 5);    define(CT_EQUALTO, 6);    define(CT_MORETHAN, 7);    define(CT_NOTEQUALTO, 8);    define(CT_MUSTMATCHREGEXP, 9);    define(CT_MUSTNOTMATCHREGEXP, 10);    ?> 

Note the two numbering schemes as an approach in defining these constants. These are an exponential sequence to define the three verbs of parameters offered in an HTTP request and a purely sequential sequence to define the constraint types.

The reason for the two differing approaches touches briefly on the properties of bitwise logic. Because this subject has not been touched upon elsewhere in the book, we touch on it briefly here.

Consider the following piece of code:

   $var3 = ($var1 && $var2); 

The conditional statement here clearly requires that $var1 is true (either literally, or as some nonzero or non-empty value), and that $var2 is true (employing traditional use of the && operator to express Boolean logic). If both are true, $var3 will also be true. This if fairly basic, but there is a second form of comparison logic that uses the same basic operators, AND and OR.

Consider the following:

   $var3 = ($var1 & $var2); 

Note the single ampersand. This indicates that you wish to perform bitwise rather than Boolean logic to effect the comparison. What then takes place, assuming that $var1 and $var2 are integers, is that the corresponding binary bits of each value are subject to the traditional Boolean comparison (with 1 meaning true and 0 meaning false). If $var1 is 34 and $var2 is 11, the following applies:

   34      = 00100010    11      = 00001011    34 & 11 = 00000010      =      2 

Accordingly, the bitwise logic of 34 and 11 yields 2, because it is only in the 2 column where both bits are 1 in each number.

But what does this have to do with constants?

Put simply, it allows us to combine constants together to form a single value, which consists of the arithmetic sum of its constituent constants, and then easily determine later what those constituent constants are.

If each constant represents the incrementing decimal value of a single bit of a binary number (essentially, a power of two), then when summed together, the resulting binary number will have bits set to 1 representing those constants that have been included in that sum.

   CONST_A =         1      =     00000001    CONST B =         2      =     00000010    CONST C =         4      =     00000100    CONST D =         8      =     00001000    CONST E =        16      =     00010000 

Say that you wish to define a parameter consisting of constants B, D, and E combined (whatever that might mean). Simply add the values 2, 8 and 16 together to get a value of 26. Take a look at what 26 looks like in binary, next to its constituent components:

   CONST B =         2    =      00000010    CONST D =         8    =      00001000    CONST E =        16    =      00010000    CONST_B+D+E =    26    =      00011010 

Notice how you can clearly recognize that the constituent constants of the combination are B, D, and E. But how do you test for this in code?

Enter and run the following code from the command line (rather than in a Web browser):

   define('CONST_A', 1);    define('CONST_B', 2);    define('CONST_C', 4);    define('CONST_D', 8);    define('CONST_E', 16);    $myCombinedConstant = CONST_B + CONST_D + CONST_E;    if ($myCombinedConstant & CONST_A) {     print "Combined constant contains A\n";    };    if ($myCombinedConstant & CONST_B) {     print "Combined constant contains B\n";    };    if ($myCombinedConstant & CONST_C) {     print "Combined constant contains C\n";    };    if ($myCombinedConstant & CONST_D) {     print "Combined constant contains D\n";    };    if ($myCombinedConstant & CONST_E) {     print "Combined constant contains E\n";    }; 

You will see output similar to the following:

   Combined constant contains B    Combined constant contains D    Combined constant contains E 

As you have probably deduced, the test to determine whether a combined constant contains a particular original constant is simply to perform a bitwise AND against that original constant. This will produce a nonzero (that is, true) value only if the bit in question is set both in the original constant and in the combined constant you are testing.

We have adopted this tactic in our constants for expressing request methods, but not in our constants used to express constraint types. This is because the code might allow the combination of request methods as a parameter for a method at some stage in the future. However, the nature of constraint types means that one constraint object has one constraint type. To combine constraints, therefore, their objects must be combined, not the constants behind them. Accordingly, we have adopted a linear (1, 2, 3, 4, 5) numbering system for these constants, because they will never be added together in the way described in this section.

The Request Class

The request object itself represents the request made by the user in order to generate the page. Those of you who have ever worked in ASP are familiar with using a preinstantiated object to access parameters, but this Request class is a somewhat more sophisticated.

Unlike ASP, you will have to instantiate the request object yourself, but you will notice that the constructor written requires no parameters. It examines the existing $_REQUEST, $_COOKIE, $_POST and $_GET hashes itself quite successfully in order to populate its various member variables.

You could start to make use of the request object right away. By using the various methods supplied to retrieve parameters, you could get on and build your application. To do this, however, is to disregard the syntax validation methods provided by the Constraint class and its sister ConstraintFailure class.

Examine the following source code, called request.phpm, in detail:

   <?    require_once("constants.phpm");    require_once("constraint.phpm");    require_once("constraintfailure.phpm");    class request {      private $_arGetVars;      private $_arPostVars;      private $_arCookieVars;      private $_arRequestVars;      private $_objOriginalRequestObject;      private $_blIsRedirectFollowingConstraintFailure;      private $_blRedirectOnConstraintFailure;      private $_strConstraintFailureRedirectTargetURL;      private $_strConstraintFailureDefaultRedirectTargetURL;      private $_arObjParameterMethodConstraintHash;      private $_arObjConstraintFailure;      private $_hasRunConstraintTests;      function __construct($check_for_cookie = true) {        // Import variables        global $_REQUEST;        global $_GET;        global $_POST;        global $_COOKIE;        $this->_arGetVars = $_GET;        $this->_arPostVars = $_POST;        $this->_arCookieVars = $_COOKIE;        $this->_arRequestVars = $_REQUEST;        if ($check_for_cookie) {          if ($this->_arCookieVars["phprqcOriginalRequestObject"]) {            $cookieVal = $this->_arRequestVars["phprqcOriginalRequestObject"];            $this->_blIsRedirectFollowingConstraintFailure = true;            if (strlen($cookieVal) > 0) {              $strResult = setcookie ("phprqcOriginalRequestObject", "",    time() - 3600, "/");              $origObj = unserialize(stripslashes($cookieVal));              $this->_objOriginalRequestObject = &$origObj;              $this->_arRequestVars["phprqcOriginalRequestObject"] = "";              $this->_arGetVars["phprqcOriginalRequestObject"] = "";              $this->_arPostVars["phprqcOriginalRequestObject"] = "";            };            $this->_blIsRedirectOnConstraintFailure = true;          } else {            $this->_blIsRedirectOnConstraintFailure = false;          };        } else {          $this->_blIsRedirectOnConstraintFailure = false;        };        $this->_arObjParameterMethodConstraintHash = Array();        $this->_arObjConstraintFailure = Array();        $this->_blHasRunConstraintTests = false;      }      function IsRedirectFollowingConstraintFailure() {        return($this->_blIsRedirectOnConstraintFailure);      }      function GetOriginalRequestObjectFollowingConstraintFailure() {        if ($this->_blIsRedirectOnConstraintFailure) {          return($this->_objOriginalRequestObject);        };      }      function SetRedirectOnConstraintFailure($blTrueOrFalse) {        $this->_blRedirectOnConstraintFailure = $blTrueOrFalse;      }      function SetConstraintFailureRedirectTargetURL($strURL) {        $this->_strConstraintFailureRedirectTargetURL = $strURL;      }      function SetConstraintFailureDefaultRedirectTargetURL($strURL) {        $this->_strConstraintFailureDefaultRedirectTargetURL = $strURL;      }      function GetParameterValue($strParameter) {        return($this->_arRequestVars[$strParameter]);      }      function GetParameters() {        return($this->_arRequestVars);      }      function GetCookies() {        return($this->_arCookieVars);      }      function GetPostVariables() {        return($this->_arPostVariables);      }      function GetGetVariables() {        return($this->_arGetVariables);      }      function AddConstraint($strParameter, $intMethod, $objConstraint) {        $newHash["PARAMETER"] = $strParameter;        $newHash["METHOD"] = $intMethod;        $newHash["CONSTRAINT"] = $objConstraint;        $this->_arObjParameterMethodConstraintHash[] = $newHash;      }      function TestConstraints() {        $this->_blHasRunConstraintTests = true;        $anyFail = false;        for ($i=0; $i<=sizeof($this->_arObjParameterMethodConstraintHash)    -1; $i++) {          $strThisParameter = $this-    >_arObjParameterMethodConstraintHash[$i] ["PARAMETER"];          $intThisMethod = $this->_arObjParameterMethodConstraintHash[$i]    ["METHOD"];          $objThisConstraint = $this-    >_arObjParameterMethodConstraintHash[$i] ["CONSTRAINT"];          $varActualValue = "";          if ($intThisMethod == VERB_METHOD_COOKIE) {            $varActualValue = $this->_arCookieVars[$strThisParameter];          };          if ($intThisMethod == VERB_METHOD_GET) {            $varActualValue = $this->_arGetVars[$strThisParameter];          };          if ($intThisMethod == VERB_METHOD_POST) {            $varActualValue = $this->_arPostVars[$strThisParameter];          };          $intConstraintType = $objThisConstraint->GetConstraintType();          $strConstraintOperand = $objThisConstraint->GetConstraintOperand();          $thisFail = false;          $objFailureObject = new constraintfailure($strThisParameter,    $intThisMethod, $objThisConstraint);          switch ($intConstraintType) {            case CT_MINLENGTH:              if (strlen((string)$varActualValue) < (integer)    $strConstraintOperand) {                $thisFail = true;              };              break;            case CT_MAXLENGTH:              if (strlen((string)$varActualValue) > (integer)    $strConstraintOperand) {                $thisFail = true;              };              break;            case CT_PERMITTEDCHARACTERS:              for ($j=0; $j<=strlen($varActualValue)-1; $j++) {                  $thisChar = substr($varActualValue, $j, 1);                  if (strpos($strConstraintOperand, $thisChar) === false) {                    $thisFail = true;                  };                };              break;            case CT_NONPERMITTEDCHARACTERS:              for ($j=0; $j<=strlen($varActualValue)-1; $j++) {                  $thisChar = substr($varActualValue, $j, 1);                  if (!(strpos($strConstraintOperand, $thisChar) === false)) {                    $thisFail = true;                  };                };              break;            case CT_LESSTHAN:              if ($varActualValue >= $strConstraintOperand) {                $thisFail = true;              };              break;            case CT_MORETHAN:              if ($varActualValue <= $strConstraintOperand) {                $thisFail = true;              };              break;            case CT_EQUALTO:              if ($varActualValue != $strConstraintOperand) {                $thisFail = true;              };              break;            case CT_NOTEQUALTO:              if ($varActualValue == $strConstraintOperand) {                $thisFail = true;              };              break;            case CT_MUSTMATCHREGEXP:              if (!(preg_match($strConstraintOperand, $varActualValue))) {                $thisFail = true;              };              break;            case CT_MUSTNOTMATCHREGEXP:              if (preg_match($strConstraintOperand, $varActualValue)) {                $thisFail = true;              };              break;          };          if ($thisFail) {            $anyFail = true;            $this->_arObjConstraintFailure[] = $objFailureObject;          };        };        if ($anyFail) {          if ($this->_blRedirectOnConstraintFailure) {              $targetURL = $_ENV["HTTP_REFERER"];              if (!$targetURL) {                $targetURL = $this->_strConstraintFailureDefaultRedirectTargetURL;              };              if ($this->_strConstraintFailureRedirectTargetURL) {                $targetURL = $this->_strConstraintFailureRedirectTargetURL;              };              if ($targetURL) {                $objToSerialize = $this;                $strSerialization = serialize($objToSerialize);                $strResult = setcookie ("phprqcOriginalRequestObject",    $strSerialization, time() + 3600, "/");                header("Location: $targetURL");                exit(0);              };          };        };        return(!($anyFail));  // Returns TRUE if all tests passed, otherwise    returns FALSE      }      function GetConstraintFailures() {        if (!$this->_blHasRunConstraintTests) {          $this->TestConstraints();        };        return($this->_arObjConstraintFailure);      }    }    ?> 

Take a look at each of the member variables. These are all private, and accessor methods are provided to get to or set their useful values. We've continued to use Hungarian notation that is, we've included a hint at the data type each member variable should contain at the start of the variable's name.

Member Variable

Role

$_arGetVars

A copy of $_GET, an associative array of HTTP GET variables passed in this request.

$_arPostVars

A copy of $_POST, an associative array of HTTP POST variables passed in this request.

$_arCookieVars

A copy of $_COOKIE, an associative array of pre-existing cookies passed in this request.

$_arRequestVars

A copy of $_REQUEST, an associative array of GET and POST variables, as well as cookies, combined into one. Where GET/POST variables or cookies of the same names exist, precedence is determined by PHP.INI.

$_objOriginalRequest Object

In the event that the user has been redirected back to the original page by the request object as a result of passing parameters that failed constraint tests, this will contain a copy of the original request object supplied.

$_blIsRedirectFollowing ConstraintFailure

A Boolean variable determining whether this request has been created as a result of a redirect following a constraint test failure.

$_blRedirectOnConstraint Failure

In the event that a constraint test is failed, should the request object automatically redirect the browser back to the original page, or to some other URL?

$_strConstraintFailure RedirectTargetURL

In the event that a constraint test is failed, and _blRedirectOnConstraintFailure is set to true, should the request object divert to any URL in particular? If this is left blank, the referring URL shall be used.

$_strConstraintFailure DefaultRedirectTargetURL

In the event that _strConstraintFailure RedirectTargetURL is not set, and _blRedirectOnConstraintFailure is set to true, and no referring URL is available, the URL to which the browser should be redirected.

$_arObjParameterMethod ConstraintHash

An array of constraints, the indices of which contain three keyed components in a hash. First, it contains the parameter name on which the constraint applies. Second, it contains the method in which this parameter is expected to be passed (as a constant). Finally, it contains the constraint object expressing the test to be applied.

$_arObjConstraintFailure

An array of constraint failure objects, should any constraints have failed, assuming that the tests have been run.

$_blHasRunConstraintTests

A Boolean variable expressing whether the constraint tests have yet been run on this request object.

The following table shows the methods provided by the request object.

Method

Role

__construct

Instantiates the request object. Does not take any required parameters. Instead, it consults $_REQUEST, $_POST, $_GET and $_COOKIE and populates member variables (see earlier) as appropriate. In addition, the existence of a cookie called phprqcOriginalRequestObject is checked for. Should it exist, another request object is assumed to be passed back as a result of throwback following constraint test failures. The cookie is nullified at this stage to prevent an infinite loop when the original request object is created. Its contents are then unserialized (via the stripslashes PHP function) into a new request object, which is then made available for interrogation through the GetOriginalRequest ObjectFollowingConstraintFailure accessor method (detailed in this table).

IsRedirectFollowing ConstraintFailure

If the page being displayed has been automatically loaded following a 302 redirect issued as a result of a constraint test failure, this method will return true.

GetOriginalRequest ObjectFollowing ConstraintFailure

If the page being displayed has been automatically loaded following a 302 redirect issued as a result of a constraint test failure, this method will return the Request object that existed when that page was called, just before the constraint tests were failed.

SetRedirect OnConstraintFailure

Accepting true or false as a parameter, this method allows you to tell the Request object whether it should perform a 302 redirect upon any constraint tests failing. The target for that redirect is set using the SetConstraintFailureRedirectTargetURL and Set ConstraintFailureDefaultRedirectTargetURL methods.

SetConstraintFailure RedirectTargetURL

Sets the target for a 302 redirect following a constraint failure taking place. If one is not set, either the referring page (which is normally the desired target) is used or, if that is not available, a default URL will be used (which is set using the SetConstraintFailure DefaultRedirectTargetURL method).

SetConstraintFailure DefaultRedirect TargetURL

In the event that no target URL is set using the preceding accessor method, the referring page will be used should a referral become necessary as a result of a constraint test failing. If no referring page is available, for example as a result of a user visiting from a bookmark, the default URL specified using this method will be used instead.

GetParameterValue

Returns the value of the specified parameter. This value is pulled directly from $_REQUEST, so the precedence of search for GET variables, POST variables and COOKIE variables will be dependent on PHP's overall configuration.

GetParameters

Returns a hash of parameters essentially, a direct copy of $_REQUEST.

GetCookies

Returns a hash of cookies essentially, a direct copy of $_COOKIE.

GetPostVariables

Returns a hash of POST variables essentially, a direct copy of $_POST.

GetGetVariables

Returns a hash of GET variables essentially, a direct copy of $_GET.

AddConstraint

Adds a constraint to this request. A constraint is applied to a single parameter and specifies a condition that must be met in order for that constraint to be considered met. This method takes the name of the parameter, the delivery method (VERB_METHOD_GET, VERB_METHOD_POST, or VERB_METHOD_COOKIE as a constant) and a constraint object (discussed shortly) as its parameters. After a constraint is added, it cannot be removed. More than one constraint may be applied to a single parameter.

TestConstraints

Tests each constraint in turn. Makes a note of any that fail by populating the member variable $_arObjConstraintFailure, accessible using GetConstraintFailures. In the event that $_blRedirectOnConstraintFailure is set to true, redirects automatically to the appropriate URL, having first issued a temporary cookie that is a serialized representation of the current request object.

GetConstraintFailures

Returns an array of constraintfailure objects (discussed shortly) such that the conditions that were not met may be evaluated. If a redirect has taken place in TestConstraints as a result of a constraint failure, this method will need to be accessed on the original request object, accessed using GetOriginalRequest ObjectFollowingConstraintFailure (described earlier in this table).

Don't worry if the theory isn't quite clear from the preceding table. Later in this chapter, you'll find a practical example of how to employ this class and the rest of the toolkit in the real world.

Now take a look at the constraint class and its close cousin, the constraintfailure class.

The Constraint Class

The constraint class exists only as an object-oriented encapsulator. It does not concern itself with the code necessary to test whether the constraint is passed. As you have just seen, this is handled by the request object.

Examine the following code, in a file called constraint.phpm:

   <?php    require_once("constants.phpm");    class constraint {      private $_intConstraintType;      private $_strConstraintOperand;      function __construct($intConstraintType, $strConstraintOperand) {        $this->_intConstraintType = $intConstraintType;        $this->_strConstraintOperand = $strConstraintOperand;      }      function GetConstraintType() {        return($this->_intContraintType);      }      function GetConstraintOperand() {        return($this->_strConstraintOperand);      }    }    ?> 

Note how the constructor takes the constraint type (chosen from one of the constraint types that have been enumerated as constants) as its first parameter and the operand as its second. The meaning of the operand depends on the type of constraint. Its interpretation is up to the request object in its implementation of each type of constraint. For example, for a CT_MAXLENGTH constraint, the operand should contain the maximum length of the parameter. For a CT_PERMITTEDCHARS constraint, the operand should contain a string containing all permitted characters that may exist in the parameter.

To accommodate this flexibility, the operand is a string. However, it will be cast back to its most appropriate native form when the test takes place.

Note that you do not store the name of the parameter being tested in a constraint object. A constraint is simply a condition and can be applied to any (or even multiple) parameters using the methods in the request object. For that reason, to encapsulate neatly any given failure of a constraint, you also need to store the parameter and delivery method that have provoked the failure. This is done using an instance of the ConstraintFailure class.

The ConstraintFailure Class

A constraint failure object is instantiated whenever a particular constraint fails. Its constructor expects the name of the parameter, the delivery method (VERB_METHOD_GET, VERB_METHOD_POST,or VERB_ METHOD_COOKIE), and the original constraint object that caused the failure.

In normal use, the GetFailedConstraintObject method will be used to determine the nature of the constraint which failed through the use of the native Constraint class methods described previously.

Take a look at the following code, in a file called constraintfailure.phpm:

   <?php    require_once("constants.phpm");    require_once("constraint.phpm");    class constraintfailure {      private $_strParameterName;      private $_intVerbMethod;      private $_objFailedConstraintObject;      function __construct($strParameterName, $intVerbMethod,    $objFailedConstraintObject) {        $this->_strParameterName = $strParameterName;        $this->_intVerbMethod = $intVerbMethod;        $this->_objFailedConstraintObject = $objFailedConstraintObject;      }      function GetParameterName() {        return($this->_strParameterName);      }      function GetVerbMethod() {        return($this->_intVerbMethod);      }      function GetFailedConstraintObject() {        return($this->_objFailedConstraintObject);      }    } 

This class neatly encapsulates a failure that has occurred as a result of the user's input to the page not meeting your acceptance criteria.

As you can see in the preceding code, it contains the name of the parameter that has failed, the method (GET, POST, or Cookie) in which the parameter was passed, and the constraint (expressed as a constraint object) test that has not been passed.

Instances of this object can be retrieved from the Request class using the GetConstraintFailures method, provided that the Request class has been instantiated in the context of a 302 redirect following redirection caused by constraint failures.

Using the Toolkit

Now that you've learnt a little bit about the basic classes that constitute the toolkit, it's worth looking at how you'd go about implementing this in a real-world application.

You'll also see in this example a practical case of an MVC approach as defined earlier in the chapter namely, the use of native PHP templating. (The next section goes into further detail on how this works.)

Assume that you have a search engine consisting of two pages: a search page in which the terms are entered, and a search terms page in which the results are displayed.

search.php

The initial search page has the following jobs:

The code is reproduced here:

   require_once("constants.phpm");    require_once("request.phpm");    require_once("constraint.phpm");    require_once("constraintfailure.phpm");    $strTemplateFile = "search.phtml";    $displayHash = Array();    $objRequest = new request();    $blHadProblems = ($objRequest->IsRedirectFollowingConstraintFailure());    $displayHash["HADPROBLEMS"] = $blHadProblems;    if ($blHadProblems) {      $objFailingRequest = $objRequest-    >GetOriginalRequestObjectFollowingConstraintFailure();      $arConstraintFailures = $objFailingRequest->GetConstraintFailures();      $displayHash["PROBLEMS"] = Array();      for ($i=0; $i<=sizeof($arConstraintFailures)-1; $i++) {        $objThisConstraintFailure = &$arConstraintFailures[$i];        $objThisFailingConstraintObject = $objThisConstraintFailure-    >GetFailedConstraintObject();        $intTypeOfFailure = $objThisFailingConstraintObject->GetConstraintType();        switch ($intTypeOfFailure) {          case CT_MINLENGTH:            $displayHash["PROBLEMS"][] = "Your search term was too short.";            break;          case CT_MAXLENGTH:            $displayHash["PROBLEMS"][] = "Your search term was too long.";            break;          case CT_PERMITTEDCHARACTERS:            $displayHash["PROBLEMS"][] = "Your search term contained characters    I didn't understand.";            break;        };      };    };    require_once($strTemplateFile);    exit(0); 

Note the exit statement at the end. It's not strictly necessary but prevents you from accidentally writing code after the template has been handed control.

Now take a look at the template itself.

search.phtml

The template has the sole responsibility of intelligently rendering the contents of $displayHash with appropriate surrounding HTML. Note how you do not need to declare $displayHash as global. The act of requiring the template glues it to the end of the original script. The interpreter treats it as a single script.

In this particular template, in addition to rendering the straightforward HTML required to create the search form, you must render any error messages generated as a result of a failed search that has just taken place.

   <html>    <head>      <title>Ed's Search Page</title>    </head>    <body>    <H1>Ed's Search Page</H1>    <hr>    You can search for types of steak here.    <BR><BR>    <? if ($displayHash["HADPROBLEMS"]) ?>      <B>Sorry, there <?=((sizeof($displayHash["PROBLEMS"]) > 1) ? "were    problems" : "was a problem")?> with your search!</B>      <? for ($i=0; $i<=sizeof($displayHash["PROBLEMS"])-1; $i++) ?>        <?=$displayHash["PROBLEMS"][$i]?>      <?;?>    <?;?>    <FORM METHOD="GET" ACTION="searchresults.php">      <TABLE BORDER="0">        <TR>          <TD>Type of Steak</TD>          <TD><INPUT TYPE="TEXT" NAME="typeOfSteak"></TD>        </TR>      </TABLE><BR>      <INPUT TYPE="SUBMIT">    </FORM>    </body>    </html> 

Note how simple the PHP is including very basic statements such as if and for. As a rule, if you find yourself writing code of greater complexity in a template file, it is most likely in the wrong place and should be in either the control page (.php) or in one of the classes.

In the next part of the chapter, we describe an alternative for using these simple statements in templates, a method that doesn't use PHP at all true templating.

If you fire up search.php in your browser, you should see that it successfully renders a standard search form (see Figure 13-1).


Figure 13-1

Obviously, the search page needs a target. This is provided using two pages a control page and a template.

searchresults.php

The search results page will be the immediate target of the search page and has the job of determining whether its input is acceptable.

It does not have the job of informing the user if the input is not acceptable. Instead, the user will be diverted via a 302 HTTP redirect back to the original search page, which then explains the shortcomings.

The conditions that apply in the following example relate to the only search parameter passed to the script the search string provided by the user. It must contain between 4 and 11 characters, which must be letters, not numbers.

   <?    require_once("constants.phpm");    require_once("request.phpm");    require_once("constraint.phpm");    require_once("constraintfailure.phpm");    $strTemplateFile = "searchresults.phtml";    $displayHash = Array();    $objRequest = new request();    $objRequest->SetRedirectOnConstraintFailure(true);    $objConstraint = new constraint(CT_MINLENGTH, "3");    $objRequest->AddConstraint("typeOfSteak", VERB_METHOD_GET, $objConstraint);    $objConstraint = new constraint(CT_MAXLENGTH, "12");    $objRequest->AddConstraint("typeOfSteak", VERB_METHOD_GET, $objConstraint);    $objConstraint = new constraint(CT_PERMITTEDCHARACTERS,    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");    $objRequest->AddConstraint("typeOfSteak", VERB_METHOD_GET, $objConstraint);    $objRequest->SetConstraintFailureDefaultRedirectTargetURL("/search.php");    $objRequest->TestConstraints();    # If we've got this far, tests have been passed - perform the search.    $displayHash["RESULTS"] = Array();    $arSteaks = array("fillet", "rump", "sirloin", "burnt");    for ($i=0; $i<=sizeof($arSteaks)-1; $i++) {      if (!(strpos(trim(strtolower($arSteaks[$i])), strtolower(trim($objRequest-    >GetParameterValue("typeOfSteak")))) === false)) {        array_push($displayHash["RESULTS"], $arSteaks[$i]);      };    };    require_once($strTemplateFile);    exit(0);    ?> 

You will notice that the $displayHash template content variable is defined again just as the request object is instantiated again.

Each constraint on input is defined as a new instance of a constraint object. It is then added using AddConstraint, specifying the delivery method (GET, in this case) and parameter name as additional operands.

To negate the need for the referral page to be available, you specify a default URL (the original search page). If you are trying out this example, you will need to adjust the path to this chapter's scripts accordingly.

A single method is then called: TestConstraints(). This proceeds to perform the tests necessary to validate whether all the constraints you have expressed have been met. If one has not, it is added to the array of constraint failures contained in this request object.

At the end of these tests, if any one of them has resulted in failure, the request object is authorized to force the user back to the search page, as a result of the use of SetRedirectOnConstraintFailure(). First, it serializes a snapshot of the request object as it stands on this page and then pushes this as a cookie to the browser. When the user returns to the search page, this cookie will be parsed by the request object to work out what went wrong.

If a redirect takes place, the template is not displayed at all. As a result, the user is none the wiser that he or she has hit the second page at all. This improves the user experience; users do not feel as if they have "left'' the original search page, hence they instantly recognize that something is wrong.

Assuming that the tests passed, the search can go ahead. In the example, you will notice the search mechanism is pretty simple. In a real-world scenario external classes would undoubtedly be used to execute the search.

After a search results variable has been populated, the template can be displayed.

searchresults.phtml

The search results template is extremely simple. It displays the results of the search. There is no provision in the example for displaying a your search produced no results message. However, the implementation of such a message would be relatively straightforward.

   <html>    <head>     <title>Ed's Search Page</title>    </head>    <body>    <H1>Search Results</H1>    <hr>    Here are the results of your search.    <UL>    <? for ($i=0; $i<=sizeof($displayHash["RESULTS"])-1; $i++) ?>      <LI><?=$displayHash["RESULTS"][$i]?></LI>    <?;?>    </UL>    <A HREF="search.php">Go back and search for more steaks!</A>    </body>    </html> 

Try It!

Assuming that you've entered the code for the previous four files, you can open the search page again, as you did before, and enter some search terms.

First, try an example that should work just fine. Enter fil as your search term and click the submit button. This should bring back fillet as a search result (see Figure 13-2).


Figure 13-2

Assuming that all looks good, it's time to try out the real purpose of the toolkit: handling things when bad input is supplied.

Try offering fi. This is only two characters in length and hence should fail (see Figure 13-3).


Figure 13-3

As you can see, you are immediately redirected back to the search page and an error message is displayed.

The searchresults.php ran three constraint tests against your search parameter and failed on one. That one failure caused a bounce back to the search page, but not before a snapshot serialization of the request object you had generated had been taken.

That snapshot was issued to your browser as a cookie immediately before you were redirected back to the original page. The search page then detected the presence of that cookie and knew that you had been referred back to it following a failure as a result. It then proceeded to read the constraint failures that were embedded in that object and display them to you one at a time.

Try issuing a string that is too long ("myfavoritesteak'' or a term that contains invalid characters such as "11431'') and you will see similar failure messages. Issue something like "my 1st choice.'' which violates more than one constraint, and you should see that you are chastised twice.

The Toolkit in the Wild

By this section of the chapter, you have built up a mini toolkit to allow you to easily handle requests and constraints as part of your use of an MVC design pattern in your projects, as well as learned a simple method for using templates in native PHP.

The built-in constraints in the request object you have created here are relatively simple and, admittedly, could be quite easily be implemented in raw PHP within your control page. Constraints really come into their own when they get complex, such as when they need to query a data source to determine validity. On occasions like this, it may make sense to genericize your request object and then extend it to form multiple request objects, perhaps one per page, which handle a different range of constraints in each case.

In the next part of the chapter, you'll learn about Smarty an entirely different way of doing templating.



Professional PHP5 (Programmer to Programmer Series)
Professional PHP5 (Programmer to Programmer Series)
ISBN: N/A
EAN: N/A
Year: 2003
Pages: 182
BUY ON AMAZON

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net