Category: "Zend Framework"

Zend Framework 1.12 - Form Decorator settings

I really like the dl/dt/dd output of Zend Framework for forms, but there came a day when I needed a lot more control over the presentation.

In order to remove the dl/dt/dd I used these settings in the form.

In form/model/edit.php

        $this->setDecorators(array(
            array('ViewScript', array('viewScript' => '/model/form/edit.phtml'))));
        $this->setElementDecorators(array(
            'ViewHelper',
            'Errors',
            'Label',
            array('HtmlTag', array('tag' => 'div')),
            'Description'));

In /model/form/edit.phtml



<?php echo $this->element->contact_first_name ?>

Resulting HTML


<div><label for="name" class="required">Name</label>
<input type="text" name="name" id="name" value="" autocomplete="off"></div>

References

http://framework.zend.com/manual/1.12/en/zend.form.standardDecorators.html
http://devzone.zend.com/1240/decorators-with-zend_form/

Google OAuth and Contacts API - PHP Curl Examples

This code uses Zend Framework’s Zend_Json::decode method, however you can use json_decode if it is available on your version of PHP.

Create the authorization link
When this link is clicked, the site visitor is presented with a page from Google asking if they would like to allow your application access to their data. If they accept, Google will give them a code, which can then be used on the next request. This is run as an installed application, because there are times when data will be requested without user interaction.

$sAuthURL=AUTH_URL;
$aParms=array('response_type'=>RESPONSE_TYPE,
        'client_id'=>CLIENT_ID,
        'redirect_uri'=>REDIRECT_URI,
        'scope'=>SCOPE);
$sLink=$sAuthURL.'?'.http_build_query($aParms);

Request an access and refresh token

        require_once 'Zend/Json.php';
        $sTokenURL=TOKEN_URL;
        $aParms=array(
                'code'=>$_POST['code'],
                'client_id'=>CLIENT_ID,
                'client_secret'=>CLIENT_SECRET,
                'redirect_uri'=>REDIRECT_URI,
                'grant_type'=>AUTHORIZATION_CODE);
        $cURL=curl_init();
        curl_setopt($cURL,CURLOPT_URL,$sTokenURL);
        curl_setopt($cURL,CURLOPT_SSL_VERIFYPEER,TRUE);
        curl_setopt($cURL,CURLOPT_POST, TRUE);
        curl_setopt($cURL,CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($cURL,CURLOPT_POSTFIELDS,$aParms);
        $cResponse=Zend_Json::decode(trim(curl_exec($cURL)));
        curl_close($cURL);                
        $sAccessToken=$cResponse['access_token'];
        $sRefreshToken=$cResponse['refresh_token'];

Use the refresh_token to request a new access_token

                                $sTokenURL=TOKEN_URL;
                                $aParms=array(
                                        'refresh_token'=>$sRefreshToken,
                                        'client_id'=>CLIENT_ID,
                                        'client_secret'=>CLIENT_SECRET,
                                        'grant_type'=>REFRESH_TOKEN);
                                $cURL=curl_init();
                                curl_setopt($cURL,CURLOPT_URL,$sTokenURL);
                                curl_setopt($cURL,CURLOPT_SSL_VERIFYPEER,TRUE);
                                curl_setopt($cURL,CURLOPT_POST, TRUE);
                                curl_setopt($cURL,CURLOPT_RETURNTRANSFER, TRUE);
                                curl_setopt($cURL,CURLOPT_POSTFIELDS,$aParms);
                                require_once 'Zend/Json.php';
                                $cResponse=Zend_Json::decode(trim(curl_exec($cURL)));
                                $sError=curl_error($cURL);
                                curl_close($cURL);
                                $sAccessToken=$cResponse['access_token'];



Request the contact data


                $sContactsURL=CONTACTS_URL.'?access_token='.$sAccessToken;
                $cURL=curl_init();
                curl_setopt($cURL,CURLOPT_URL,$sContactsURL);
                curl_setopt($cURL,CURLOPT_RETURNTRANSFER, TRUE);
                $cResponse=trim(curl_exec($cURL));
                $sError=curl_error($cURL);
                curl_close($cURL);

Parse the data from Google
This code also Zend_Gdata to handle the parsing.

$phone_only=false;

include 'Zend/Gdata.php';

$gdata=new Zend_Gdata();
$feed=$gdata->importString($cResponse);

foreach ($feed as $entry) {
        // Thanks to: http://www.ibm.com/developerworks/opensource/library/x-phpgooglecontact/index.html
        $xml = simplexml_load_string($entry->getXML());
        $obj = new stdClass;
        $obj->id = $entry->id->text;
        if (false===($iSlash=strpos($entry->title->text,'/')))
                $obj->name=$entry->title->text;
        else
                $obj->name = substr($entry->title->text,0,$iSlash);
        if (empty($obj->name)) continue;
        $name = explode(' ',$obj->name,3);
        $obj->first_name = $obj->middle_name = $obj->last_name = '';
        switch (count($name))
        {
                case 1: 
                        $obj->first_name = $obj->name;
                        break;
                case 2: 
                        $obj->first_name = $name[0];
                        $obj->last_name = $name[1];
                        break;
                case 3:
                        $obj->first_name = $name[0];
                        $obj->middle_name = $name[1];
                        $obj->last_name = $name[2];
                        break;
        }
        $obj->phoneNumber = array();
        $obj->phone = array();
        foreach ($xml->phoneNumber as $p) 
        {
                $type = preg_replace('/^[^#]+#(.*)$/','\1',(string) $p['rel']);
                $obj->phoneNumber[] = (string) $p.' ('.$type.')';
                $obj->phone[$type] = array ('phone' => preg_replace('/\D/','',(string) $p), 'type' => strtolower($type) );
        }
        if (empty($obj->phone) && $phone_only) continue;
        $results[$obj->last_name] = $obj;  
}
ksort($results);



This post courtesy of Lyrix, Inc. http://mobiso.com

dojo 1.7.1 AMD with Zend Framework 1.11

I have been working on a new web application using Zend Framework 1.11 and Dojo. In order to use the AMD loader of dojo 1.7.1, I modified the Zend_Dojo_View_Helper_Dojo to include a flag indicating whether to use the AMD loader or not, and updated Zend_Dojo_View_Helper_Dojo_Container to render the code for the AMD loader, if it has been selected.

Code

Zend_Dojo_View_Helper_Dojo: http://web-notes.wirehopper.com/AMD_Dojo.txt
Zend_Dojo_View_Helper_Dijit: http://web-notes.wirehopper.com/AMD_Dijit.txt
Zend_Dojo_View_Helper_ComboBox: http://web-notes.wirehopper.com/AMD_ComboBox.txt
Zend_Dojo_View_Helper_Dojo_Container: http://web-notes.wirehopper.com/AMD_Container.txt

Default application layout: http://web-notes.wirehopper.com/AMD_default.txt

This is very new code, and it has not been tested with a built version of the javascript. Be sure to make a backup of Zend before applying these changes.

The code is updated frequently, you may want to check back in a few days to see if new code has been posted. Be sure to reload if you’ve visited the pages, since .txt files tend to be cached.

The updates are running well with a non-built version of code. I’m deferring build development.

Zend_Rest - Example

I built a REST API and calling code using Zend Framework. This code also uses Doctrine (http://www.doctrine-project.org/projects/orm).

Prior to coding, I reviewed several resources on the ‘net in an attempt to follow best practices. I visited the following, as well as others:

routes.ini

These settings ensure the request is properly organized when received by the REST controller. Be sure the ACL is configured to allow access as appropriate as well.

# REST routes
resources.router.routes.api.type = "Zend_Rest_Route"
resources.router.routes.api.route = "/api/:id"
resources.router.routes.api.defaults.module = "api"
resources.router.routes.api.defaults.controller = "index"
resources.router.routes.api.api = "admin, vendor, client"

Calling Code

The calling code is a subset, only the relevant code is included.

Authentication is handled by JSON encoding, then encrypting the username and password in the Authorization header.

The process has two phases, a GET to test whether the element exists and a POST, PUT or DELETE depending on existence, error, or requested action. Since the GET is a test, no data is returned. POST and PUT pass the data in the body text, JSON encoded.

	
            $client = new Zend_Http_Client();
            $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
            $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
            $authorization = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $v['url'],
                Zend_Json::encode(array('username'=>$this->username,'password'=>$this->password)),
                MCRYPT_MODE_ECB, $iv));
            $client->setHeaders(array('keepalive' => true,'Authorization' => $authorization));
            $client->setUri($api['protocol'].'://'.$url.'/'.$api['path'].'/admin/'.$admin->uuid);
			$client->setMethod(Zend_Http_Client::GET);
			$response = $client->request();
			$status = $response->getStatus();
			$body = $response->getBody();
			switch ($status)
			{
				case 200:	/* Record exists */
					$next_method = ($type == 'allow') ? Zend_Http_Client::PUT : Zend_Http_Client::DELETE;
					break;
				case 404:	/* Record does not exist */
					if ($type == 'allow')
					{
						$client->setUri($api['protocol'].'://'.$url.'/'.$api['path'].'/admin');
						$next_method = Zend_Http_Client::POST;
						break;
					}
				default:	/* Error */
					return false;
			}
	
			if (($next_method == Zend_Http_Client::POST) ||
				($next_method == Zend_Http_Client::PUT))
				{
					$values = array('uuid','password','email','first_name','middle_name','last_name','status','last_modified_by');
					$admin_data = $admin->getData();
					foreach ($values as $k => $v)
						$data[$v] = $admin_data[$v];
					$jsonData = Zend_Json::encode($data);
					$client->setRawData($jsonData,'text/json');
				}
		
			$client->setMethod($next_method);
			$response = $client->request();
			$status = $response->getStatus();
			$client->getAdapter()->close();
			$return = in_array($status,array(200,201,204));

modules/api/controllers/AdminController.php

The REST controller uses a preDispatch override to ensure the submitted id, username and password are valid. Invalid requests are discarded with an HTTP/400. Unauthorized requests receive an HTTP/403.

GET requests use the submitted UUID to test for the existence of the data. Using a UUID ensures the data will be identified the same across different systems. The data has local identifiers as well, and some translation is applied, although it has been removed from the posted code.

The JSON data is read from the request body with a file_get_contents on php://input

<?php
class Api_AdminController extends Zend_Rest_Controller
{
	private $model = null;
	private $uuid = null;
	private $data = null;
	private $response = null;

	public function init()
	{
		$this->apiBaseUrl = $_SERVER['SERVER_NAME'].'/api/admin';
		$this->_helper->layout->disableLayout();
		$this->getHelper('viewRenderer')->setNoRender(true);
	}

	public function preDispatch()
	{
		// This ensures the required data is present and valid prior to processing any requests
		$hostname = new Zend_Validate_Hostname(array('allow'=>Zend_Validate_Hostname::ALLOW_DNS));
		$validators = array(
			'uuid'=>array(array('Regex','pattern' => '/^[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}$/','allowEmpty'=>false,'presence'=>'required')),
			'username'=>array(array('EmailAddress','allowEmpty'=>false,'presence'=>'required','allow' => $hostname,'mx' => true)),
			'password'=>array(array('StringLength','min'=>8,'max'=>32)));
		$input = new Zend_Filter_Input(array(),$validators);
		$request = $this->getRequest();

        $this->data = Zend_Json::decode(file_get_contents('php://input'));
        if ($request->isPost())
            $this->uuid = $this->data['uuid'];
        else
        {
            $this->uuid = $request->getParam('id');
            $this->data['uuid'] = $this->uuid;
        }

        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
        $authorization = Zend_Json::decode(trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $_SERVER['SERVER_NAME'],
            base64_decode($request->getHeader('Authorization')),
            MCRYPT_MODE_ECB, $iv)));
        $input->setData(array(
            'uuid'=>$this->uuid,
            'username'=>$authorization['username'],
            'password'=>$authorization['password']));

		if (!$input->isValid())
			$this->error(400);
		else
		{
			// Data is valid, authenticate based on username and password
		        $session = new Zend_Session_Namespace('global');
        		$adapter = new Application_Auth_Adapter_Doctrine($input->username,$input->password);
			$auth = Zend_Auth::getInstance();
			$result = $auth->authenticate($adapter);
	        	if (!$result->isValid()) 
				$this->error(403);
			else
			{
				// Prepare the response object
				$api = Zend_Registry::get('bootstrap')->getOption('rest');
				$this->response = array(
					'link'=>$api['protocol'].'://'.$this->apiBaseUrl.'/'.$input->id,
					'title'=>'Admin',
					'charset'=>'utf-8');
			}
		}

		$session=new Zend_Session_Namespace('global');
		switch ($session->type)
		{
			case 'client': 
				$this->model = 'Application_Model_Admin'; 
				break;
			case 'vendor': 
				$this->model = 'Application_Model_VendorAdmin'; 
				break;
			case 'admin': 
				$this->model = 'Application_Model_Admin'; 
				break;
			default: 
				$this->error(400);
				break;
		}
	}
	
	public function indexAction()
	{
		// Handles GET requests
	}
	
	public function getAction()
	{
		// Handles GET requests
		$q = Doctrine_Query::create()
			->from ($this->model)
			->where('uuid = ?',$this->uuid);
		$result = $q->fetchArray();
		$q->free();
		switch (count($result))
		{
			case 1:
				// The data is NOT sent back to the client.  The response serves only to acknowledge the existence of the record
				// In this application, the GET request is really querying for presence, not requesting data
				$this->response['updated'] =$result[0]['updated_at'];
				$this->_helper->json($this->response);
				break;
			case 0:
				$this->error(404);
				break;
			default:
				// Database should only have one instance of UUID
				$this->error(409,'Multiple matches - contact support');
				break;
		}
	}

	private function process()
	{
		$form = new Application_Access_Admin_View_Form();
		$form->populate($this->data);
		$values = array('uuid','password','email','first_name','middle_name','last_name','status','last_modified_by');

		$data = array();
		foreach ($elements as $k => $v)
		{
			$name = $v->getName();
			if (!in_array($name,$values))
				$form->removeElement($name);
			else
				$data[$name] = $v->getValue();
		}

		if ($form->isValidPartial($data))
		{
			$request = $this->getRequest();
			if (!$request->isPost())
			{
				$q = Doctrine_Query::create()
					->from ($this->model)
					->where('uuid = ?',$this->uuid);
				$admin = $q->fetchOne();
				$q->free();
			}
			else
			{
				$admin = new $this->model;
				$admin->assignDefaultValues();
			}
			$admin->fromArray($form->getValues());
			$response = $this->getResponse();
			try
			{
				$admin->save();
				if ($request->isPost())
					$response->setHttpResponseCode(201);
				else
					$response->setHttpResponseCode(204);
			}
			catch(Doctrine_Exception $e)
			{
   				$this->error(400,var_export($admin->getErrorMessages(),true));
			}
			catch (Exception $e)
			{
   				$this->error(400,var_export($e->getMessage(),true));
			}
		}
		else
   			$this->error(400,var_export($form->getMessages(),true));
	}

	public function postAction()
	{
		$this->process();
	}

	public function putAction()
	{
		$this->process();
	}
	
	public function deleteAction()
	{
		$q = Doctrine_Query::create()
			->from ($this->model)
			->where('uuid = ?',$this->uuid);
		$result = $q->fetchOne();
		$q->free();
		$result->delete();
		$response = $this->getResponse();
		$response->setHttpResponseCode(204);
	}

	private function error($code,$message=null)
	{
		// If a message is returned, it is text in the body
		$response = $this->getResponse();
		$response->setHttpResponseCode($code);
		if ($message != null)
			$response->setBody($message.PHP_EOL.PHP_EOL);
		$response->sendResponse();
		exit;
	}
}

Use curl to test the interface, or PHP.

dojo - dijit Form - Create dijits programmatically

This code populates an empty form with a radio button input that has three options. It can be modified to work with check boxes and other dijits.

This is intended to be used with Zend Framework’s Zend_Dojo_Form component, the tags are created using the id naming conventions of Zend, so styling can be applied more easily.

Test data is included, this code can be copied and used as is. It does assume dojo is in the dojo directory. It is coded for dojo 1.6.1 and should work for later versions as well. DOCTYPE is HTML5, although no HTML5 tags are used.

<!DOCTYPE html><head>  
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<META http-equiv="Content-Style-Type" content="text/css">
<title>dojo Test Code</title>
<style type="text/css"> 
<!--
    @import "dojo/dijit/themes/tundra/tundra.css";
-->
dl#dl-web_admin_select
{
width:625px;
}
dl#dl-web_admin_select dt
{
font-weight:bolder;
width:550px;
float:right;
}
dd
{
}
</style>
<script type="text/javascript"> 
//<!--
    var djConfig = {"parseOnLoad":true,"isDebug":true,"locale":"en_US"};
//-->
</script>
<script type="text/javascript" src="dojo/dojo/dojo.js"></script>
<script type="text/javascript" src="dojo/dijit/dijit.js"></script>
<script type="text/javascript" src="dojo/dijit/dijit-all.js"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("dijit.form.RadioButton");

// Data to simulate Zend_Dojo_Data response
var data={"identifier":"id","items":[{"id":"1","url":"one.com","account_name":"AccountOne","name":"AccountOne - one.com"},{"id":"3","url":"three.com","account_name":"AccountOne","name":"AccountOne - three.com"},{"id":"2","url":"two.com","account_name":"AccountTwo","name":"AccountTwo - two.com"}]};

// Used to store the array of created inputs, so they can be destroyed if replaced
var dijit_input = new Array();

dojo.addOnLoad(function(){
		var i=0,input_type;
		// Destroy the HTML
		dojo.destroy('fieldset-web_admin_select');
		// Destroy the dijit widgets
		dijit_input.forEach(function(item){item.destroy()});

		fieldset = dojo.create('fieldset',{'id':'fieldset-web_admin_select'},'frmTest');
		dl = dojo.create('dl',{'id':'dl-web_admin_select'},'fieldset-web_admin_select');
		i=0;
		data.items.forEach(function(item){
			label = dojo.create('dt',{'id':'web_admin_select-label_'+i,'innerHTML':item.name},'dl-web_admin_select');
			input = dojo.create('dd',{'id':'dd_web_admin_select_'+i},'web_admin_select-label_'+i,'after');
			selector = dojo.create('input',{'id':'web_admin_select_'+i,'type':'radio','name':'web_admin_select'},'dd_web_admin_select_'+i);
			dijit_input.push(new dijit.form.RadioButton({
				'value': item.id,
				'id': "rb["+i+"]"},
				'web_admin_select_'+i));
			i++;
		});
});

</script>
</head>
<body class="tundra">
<h1>dojo Test</h1>
<form data-dojo-type="dijit.form.Form" data-dojo-props="'id':'frmTest','name':'frmTest','method':'post'">
</form>
</body>
</html>

1 3