Showing posts with label projects. Show all posts
Showing posts with label projects. Show all posts

Sunday, April 5, 2009

Zend Framework Dynamic Site

::UPDATE - 04.09.2009::
I finally was able to create an example of the below described site.  If you have any questions/comments please let me know.

When creating a website, especially in the corporate/enterprise world, it assumed that you have to use some sort of content management system.  The range, and subsequent debate, could be everything from free (Joomla, Wordpress, Drupal) to the very expensive (Vignette).  While I am in a firm believer in the use of a CMS it seems lately I have been asked to develop sites whose content doesn't change that often, but the responsibility still needs to fall into the hands of a content editor not a developer.  In addition, the designer that I work with wanted the freedom of creating as many templates as he wanted and tweak the design without having to consult me, the developer, nor have to work with a content editor to update any of the content pages.

I went back and forth how to best solve this solution.  While Joomla and Wordpress would have certainly handled all of these requirements there is the overhead that comes with any CMS and with the requirements that I had to work with I kept asking myself if using a CMS was lighting a grill with an atom bomb.

The end result was a hybrid between using static "html" and a mini-CMS....all using the Zend Framework.  I came up with a way that separated the content, layouts, and the developer maintained backedend of the site.

Basic requirements:
  1. SEF's
  2. Breadcrumb subheadings
  3. Flexible layouts - some pages might need a "one column content well", "one column content well with a side bar", etc
  4. Content pages should be kept in one location
  5. Templates should be independent of backend code and content
Directory Structure:
  • content
  • css
  • images
  • index.php
  • js
  • layouts
  • lib
  • modules
  • settings
How It Works
  • Creating/Editing Content


    1. Create the static phtml file in the content directory
    2. Edit the content.xml file in the content directory and add the following attributes and/or nodes: url - 1) what the request url will be. 2) file - the name of the phtml file (minus the extension). 3) pageTitle - self explainatory 4) layout - what template do you want to use for the page
  • Page Rendering


    1. Zend Layout
    2. Zend MVC
    3. Custom regex route
    4. Custom Site Page Render Plugin

Plugin and Go

The majority of the site dynamics happens in the custom plugin - routeShutdown method.
  1. Based upon the $_SERVER['REQUEST_URI'] the appropriate "page" node is located: "//pages/page[@url='{$_SERVER['REQUEST_URI']}']".
  2. The page title is set
  3. The name of the view (phtml) file is set
  4. The breadcrumbs array is populated. *
  5. All the attributes are then set as an array of params and set in the request.
  6. The IndexController/indexAction grabs the params, sets them to view properties to be displayed in the layout

*::NOTE::
Since there is technically not a hierarchy of files on the web server... the way that the url is decided upon is by the content editors.  Because of this there is a business rule in place that the "toplevel" navigation is the first set of characters in the url and child navigation thus follows.  In the method the breadcrumbs are created by parsing this pattern.

Conclusion
While there is definite room for improvement to how the site management is done we have seen great efficiency not only how the site is maintained and content is created, but also the amount of code that is used to generate the site.
  • 1 module, 1 controller, 1 action, 1 custom plugin
  • developers, content editors, and designers are able to work on their respective portions of the site without having to rework or update any other aspect of the site in 99% of use cases.

I finally was able to create an example of the below described site.  If you have any questions/comments please let me know.

Saturday, January 10, 2009

PayPal Zend Framework Validator

I was presented with a project where the client wanted a form that would accept credit card payments via paypal.  I could either write it in PHP or Java. While I am a big fan of both languages I felt in this instance that PHP was the most time efficient route.   However, PayPal doesn't offer an API for PHP. Only Java and .NET.  As to not be discouraged I decided to spend a little time trying to come with a custom validator that would handle the validation of the credit card validation.

::Warning::
My use case was very specific.  I only needed to authorize credit card sales.  Basically their simpliest form of transations.

Since all the applications that I write now are us the Zend Framework I was able to reduce "inline-code" by creating a custom validator that I assign to my preValidation method inside of the model.  :: I worked on this concept with Jeremy Kendall ::

There is definitely room for improvement, but this is might be helpful to others.  For example, the paypal options should really not be declared as Zend_Config instance, but checked for an instance of Zend_config and then set as an array with the toArray method.


// Validator

/**
* @see Zend_Validate_Abstract
*/
require_once 'Zend/Validate/Abstract.php';

/**
* @see Zend_Http_Client
*/
require_once 'Zend/Http/Client.php';

class SJCRH_Validate_PayFlow extends Zend_Validate_Regex {

const CHARGE_FAILED = "ppChargeFailed";

const CHARGE_EXCEPTED_CODE = "0";

/**
* Error message to display to the user if the credit card transaction fails
*
* @access protected
* @var array
*/
protected $_messageTemplates = array(
self::CHARGE_FAILED => "There was an error processing your card. Error code: '%code%' Error message: '%msg%'"
);

/**
* @var array
*/
protected $_messageVariables = array(
'code' => '_code',
'msg' => '_msg'
);

/**
* Error Code value
*
* @var mixed
*/
protected $_code;

/**
* Error Message
*
* @param Zend_Config $options
*/
protected $_msg;


/**
* CC validation using PayFlow
*
* @param Zend_Config $options
* @param string $ccnum
* @param string $exp
* @param array $billing
* @param string $amount
* @param string $country
*/
public function __construct(Zend_Config $paypaloptions, $ccnum, $amount, $exp, $billing = array(), $country = 'US') {

if (!$paypaloptions instanceof Zend_Config) {
throw new Exception("Options must be an instance of Zend Config");
} else {
$options = $paypaloptions->toArray();
}

/**
* Random string used to error checking with payflow - DUPLICATION TRANSACTIONS
*/
$requestId = md5(date('YmdGis'));

$zfClient = new Zend_Http_Client($options['url']);

$zfClient->setHeaders(array('X-VPS-Request-ID' => $requestId));

/**
* Recommended from their documentation to change the timeout from 30 seconds (default)
* to 45
*/
$zfClient->setConfig(array('timeout' => 45));

$zfClient->setMethod(Zend_Http_Client::POST);

$zfClient->setParameterPost(array(
'USER' => $options['username'],
'VENDOR' => $options['vendor'],
'PARTNER' => $options['partner'],
'PWD' => $options['password'],
'FIRSTNAME' => $billing['firstname'],
'LASTNAME' => $billing['lastname'],
'STREET' => $billing['street'],
'ZIP' => $billing['zip'],
'TENDER' => 'C',
'TRXTYPE' => 'S',
'ACCT' => $ccnum,
'EXPDATE' => $exp,
'AMT' => $amount,
'CURRENCY' => 'USD',
'COUNTRY' => $country,
'CLIENTIP' => $_SERVER['REMOTE_ADDR'],
'VERBOSITY' => 'MEDIUM'
));

/**
* The response key/value pairs are seperated by ampersands.
*
* I extract the RESULT and RESPMSG
*
* If anything but a RESULT=0 is found then the code and error message are
* set to the validator's variables to display to the form user.
*/

$payFlowResponse = explode("&", $zfClient->request()->getBody());
$regex = "/(\w*)=(.*)/i";

@preg_match($regex, $payFlowResponse[0], $matches);

$this->setCode($matches[2]);

if ($this->getCode() !== self::CHARGE_EXCEPTED_CODE) {
@preg_match($regex, $payFlowResponse[2], $matches);
$this->setMsg($matches[2]);
}
}

public function isValid($value) {

$this->_setValue($value);

if ($this->getCode() !== self::CHARGE_EXCEPTED_CODE) {
$this->_error();
return false;
}

return true;
}

public function setCode($code) {
$this->_code = $code;
}

public function setMsg($msg) {
$this->_msg = $msg;
}

public function getCode() {
return $this->_code;
}

public function getMsg() {
return $this->_msg;
}
}