Showing posts with label zend framework. Show all posts
Showing posts with label zend framework. 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.

Sunday, February 15, 2009

Internalization and Zend Form

One of the many growing requirements that I am experiencing at work is for internalization of our forms.  In this particular use case the languages available are limited to English and Spanish so I am not able to use Zend_Locale::BROWSER exclusively.  To make sure that correct form displays, in my form model class I created an array of allowed locales.  Before the form instantiated in the controller the language param is set the registry, and then that locale is checked against allowed locales.

References:
Translations - located in my models directory: Models/Languages/En.php and Es.php

// En.php

return array(
              'name'    => 'First Name',

              'address' => 'Address'
       );


// Es.php

return array (

         'name'     => 'nombre',         'address' => 'dirección'

       );



// Controller snippet
 /**
  * Add language to the registry
  */
  $locale = new Zend_Locale($this->_getParam('lang'));
  Zend_Registry::set('locale', $locale);

// Form snippet
  /**
   * Instance of Zend_Locale
   *
   * @var Zend_Locale
   */
  protected $_locale         = null;

  /**
   * Allowed locale regions
   *
   * @var array
   */
  protected $_allowedLocales = array('en', 'es');

  protected $_translations   = null;

    $this->addElement('note', 'quanityInstructions', array(
      'decorators'  => $this->_standardNoteDecorator,
      'description' => "{$this->_translations->_('quanityInstructions')}"
    ));

  /**
   * Prepare the form
   *
   * @return boolean
   */
  private function _prepare() {

    /**
     * Grab the browsers locale
     */
    $this->_locale = Zend_Registry::get('locale');

    /**
     * If the browser is locale isn't English or Spanish then default to
     * English
     */
    if (!$this->_checkForValidLocaleRegion($this->_locale)) {
      $this->_locale = new Zend_Locale('en_US');
    }

    $transFile = ucfirst($this->_locale->getLanguage()).'.php';

    $this->_translations = new Zend_Translate('array', realpath(dirname(__FILE__).'/../../Models/HemOnc/Languages/'.$transFile), $this->_locale);

    return true;
  }

  /**
   * Check to make sure that browser's locale is english or spanish.  The region
   * doesn't matter.
   *
   * @param Zend_Locale $zl
   * @return boolean
   */
  private function _checkForValidLocaleRegion(Zend_Locale $zl) {
    return in_array($zl->getLanguage(), $this->_allowedLocales) ? true : false;
  }


If locale is allowed then the form label translation is set.

For further info please contact me.

Thursday, January 22, 2009

Site Improvements

Last night I finished migrating www.corywiles.com to a complete Zend Framework MVC app.  Now that is done there should be significant site improvements mostly due to enhanced caching.

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;
}
}

Using Zend View for Email Message Body in Your Model

Lately in the PHP/Zend Framework blog world there has been much discussion concerning what constitutes a model in an MVC app.  In the current implementation of the MVC apps at work our ZF Form implementations are processed in a corresponding model class as well as a 'notify' method which handles emailing a response after successful submission.  I was able to abstract all aspects of the email properties except for the message body.

Most of the time the requirement is to have, basically, the same form view but with the values populate.  That usually means creating some long and kludgy looking heredoc or worse a huge string of crazy html intermingled with escapes.  Neither solution was very appealing to me.  I kept thinking more and more that the email body was really a view.  So I treated as such.  So my solution was to create a global views directory and a view script that was the email body template and passed it's render method to Zend_Mail instance.

This allows for further separation of models and views.  I know that technically the email body should be apart of the model, but far too many times I have to change how the body of the mail looks, not the data, so it lends itself to more of a view.

Please feel free to comment.

::NOTE::
Forms as model architecture taken from http://weierophinney.net/matthew/archives/200-Using-Zend_Form-in-Your-Models.html

//Example Code
private function _notifyGroup() {
 
  $data = array();
  $data = $this->getForm()->getValues();
 
  /**
   *  Setup view instance to pass to notifier
   */
  $view = new Zend_View();

  /**
   * Assign all form data to view property
   */
  $view->assign("formvalues", $data);

 
 /**
   * Location of view scripts
   */
  $view->addScriptPath(dirname(__FILE__).'/../../../../views');

  $body = $view->render('email-templates/mir.phtml');

  /**
   *  Custom notify class that abstracts different 'notify' methodologies.  For example you can notfiy
   *  by writing to a log or email.  Email can be Zend_Mail, PEAR or plain ol' php mail() 
   */
  $notifier = CW_Notify::factory('mail_zend', $body, $options['email']);

  $notifier->notify();

}

Monday, August 4, 2008

Access XML Nodes From Namespaces With Zend Framework

One of the requirements for a project that I am working on is that a "weather" module display on on of the main pages.  At first glance this should be pretty easy.  While working on it I noticed that I wanted to access more of the information that the feed had available.  I found plenty of tutorials on how to access the node values using SimpleXML, but I couldn't find what I was looking for utilizing Zend Framework.

After a quick response from the mailing list and references to the PHP online manual I came up with the solution.

Thanks to Pádraic Brady for the assistance.


Zend_Feed::registerNamespace('yweather','http://xml.weather.yahoo.com/ns/rss/1.0');
Zend_Feed::registerNamespace('geo','http://www.w3.org/2003/01/geo/wgs84_pos#');

$feed = Zend_Feed::import("http://weather.yahooapis.com/forecastrss?p=38105");

// Conditional Codes: http://developer.yahoo.com/weather/#codes
$condition = $feed->current()->{'yweather:condition'};
$text      = $condition->getDOM()->getAttribute('text');
print "Text {$text}
";

$astronomy = $feed->{'yweather:astronomy'};
$text      = $astronomy->getDOM()->getAttribute('sunrise');
print "Sunrise Attribute {$text}
";
          
$lat  = $feed->current()->{'geo:lat'};
$text = $lat->getDOM()->nodeValue;
print "Latitude {$text}
";

Friday, July 25, 2008

Come Out, Come Out Where Ever You Are

I made an update to the helper classes and comitted them to the changes in subversion and download version.  If a photo has been tagged with a location then the GeoWhere node is returned as a property.

Monday, June 30, 2008

Mi Picasa Framework, es su Picasa Framework

One of the best features that the Zend Framework offers is it's interface utilities to many of Google's webservices. Namely Picasa and YouTube. However, in my ever continuing effort to simplify my development tasks I started writing some helper classes that would make it more intuitive to retrieve some basic information. For example, give me all the Picasa galleries for the user kwylez or give me the first 15 YouTube videos in the entertainment category.

While this task is fairly easy with the ZF APIs the method names aren't always intuitive. This is where my little project began, as most do. Having a task that one would like to accomplish easily.
I have committed the first version of the helper class framework to subversion as well as a file download.

When prompted for a username/password when checking out of subversion the username is anonymous and password is empty.

The zip file/repository includes:
  1. Custom helper classes
  2. Usage examples

Class features:

  1. Zend Framework style directory structure for easy autoloading
  2. Picasa interface that retrieves meta information on author, galleries, and images
  3. YouTube interface that retrieves information based upon a user or category

Dependencies

  1. Zend Framework 1.5