Wednesday, November 24, 2010

Try Zend Framework vol.13 "Dispatch Like Mojavi"

Do you remember "Mojavi" framework? :)
Once upon a time, there was a fantastic framework which is called "Mojavi".
If you were a PHP programmer, once you have probably heard that name.
But, it's not seems to be developed no longer.

So, tonight we look back old days and revive "Mojavi" as Zend dispacher! :)

Before talking about Mojavi, Let's talk about Ruby on Rails and frameworks around it.
After Ruby on Rails (RoR) was shown in the world, it became very famous and common as rapid web development tool.
It gives you cool architecture and the speed until making a product up.
Then many frameworks imitate to RoR, and they became similar to RoR.
And I think Zend Framework is also the one of them. :)

RoR style dispach is very simple and easy to understand, then keeps class and file less. When I saw that architechture first time, I was so impressed of its simpleness.

RoR style is very nice, but it is too hard to throw away Mojavi style.
I think Mojavi style is also simple and easy to identify the area of influence.

To know the diffrences between two styles, let's see both architechtures.
Comparing two of them, let's see Zend Frameworks' default structure.

There are "Model", "Controller" and "Action".

In Zend Framework, the standard dispatcher interprets "Module", "Controller" and "Action" from the executed url.
Actually it depends on the router, but for now , let's think about "Standard" dispatcher. ;)

OK, see the url below.
http://zf.f-pig.net/foo/bar/buz

There are three paths foo, bar and buz, it would be interpreted by dispatcher like this.
1.foo is "Module" and it is "Direcotry".
2.bar is "Controller" and it is "Class file".
3.buz is "Action" and it is "Method" of bar (controller).

And it looks like this.
application/
|~modules/
| |~foo/(module)
| | |~controllers/
| | | `~BarController.php (has "buz" method as action method.)

In BarController.php
class Foo_BarController extends Zend_Controller_Action
{
public function buzAction()
{
// this is buz action.
}
}

OK, that is Zend Framework style.

Then now, let's look back old days!!

From here I'm going to talk about Mojavi Framework style.
Mojavi interprets that url like this.
http://zf.f-pig.net/foo/bar/buz

1. foo is "Moduel" and it is "Directory".
2. bar is "Controller" it is also "Direcotry".
3. buz is "Action" and it is "Class file". It has a method to execute it self.

And it looks like this.
application/
|~modules/
| |~foo/(module)
| | |~controllers/
| | | `~Bar/(controller)
| | |   `-BuzAction.php(action having "execute" method.)

In BuzAction.php
class Foo_Bar_BuzAction extends Zend_Controller_Action
{
// this is action.

public function execute()
{
// this is an executive method of this action "class".
}
}


You may feel Mojavi style is too detailed style, and makes many class files and feel it messy.

But, this Mojavi style sometimes helps you.
There are some situations that Mojavi style can solve.

Case 1.
Let's think, you want to write some method which is for buzAction.
You probably write that method on BarController.
On the one hand, it's pretty normal, because there is no place to write that method,
on the other hand, you need to name that method to specify the aim that the method is for buzAction.

Case 2.
Zend_Controller_Action gives some useful methods like init, preDispatch and postDispatch.
But, these methods are on Controller, so every action method was called it would be called automatically.
Even if you don't want to call preDispatch when another action methods called, it would be called automatically.
Or you may need to write a "if statement" in these methods.

Case 3.
Imagine you are working with many coworkers whose skills are not same level as you.
They may touch a same class at the same project.
And you need to share the class file with them.
A coworker may use generic name even it only work for buzAction.
Once that code is released, it's pretty hard to refactor and release, isn't it?

Oops, I'm talking too much. :)
It's time to write Mojavi dispacher!

To make Mojavi style architecture, we need to make a new dispatcher!
Let's call it "Hoge_Controller_Dispatcher_MojaviLike".
Hoge_Controller_Dispatcher_MojaviLike
<?php
/**
* Hoge_Controller_Dispatcher_MojaviLike
* Dispatch like mojavi.
*/
class Hoge_Controller_Dispatcher_MojaviLike extends Zend_Controller_Dispatcher_Standard
{
/**
* Execute method name.
* @var string
*/
protected $_executeMethod = 'execute';

/**
* Set executable method name.
* @return void
*/
public function setExecuteMethod($name)
{
$this->_executeMethod = $name;
}

/**
* Get executable method name.
* @return executetable method name.
*/
public function getExecuteMethod()
{
return $this->_executeMethod;
}

/**
* Formats a string into a controller name.
* @param string $unformatted
* @return string
*/
public function formatControllerName($unformatted)
{
return ucfirst($this->_formatName($unformatted));
}

/**
* Formats a string into an action name.
* @param string $unformatted
* @return string
*/
public function formatActionName($unformatted)
{
return ucfirst(parent::formatActionName($unformatted));
}

/**
* Get action class name
*
* @param Zend_Controller_Request_Abstract $request
* @return string|false Returns class name on success
*/
public function getActionClass(Zend_Controller_Request_Abstract $request)
{
// controller info.
$controllerName = $request->getControllerName();
if (empty($controllerName)) {
if (!$this->getParam('useDefaultControllerAlways')) {
return false;
}
$controllerName = $this->getDefaultControllerName();
$request->setControllerName($controllerName);
}
$controllerName = $this->formatControllerName($controllerName);


// action info.
$actionName = $request->getActionName();
if (empty($actionName)) {
if (!$this->getParam('useDefaultActionAlways')) {
return false;
}
$actionName = $this->getDefaultAction();
$request->setActionName($actionName);
}
$actionName = $this->formatActionName($actionName);

// build class name.
$className = $controllerName . $this->getPathDelimiter() . $actionName;

$controllerDirs = $this->getControllerDirectory();
$module = $request->getModuleName();
if ($this->isValidModule($module)) {
$this->_curModule    = $module;
$this->_curDirectory = $controllerDirs[$module];
} elseif ($this->isValidModule($this->_defaultModule)) {
$request->setModuleName($this->_defaultModule);
$this->_curModule    = $this->_defaultModule;
$this->_curDirectory = $controllerDirs[$this->_defaultModule];
} else {
require_once 'Zend/Controller/Exception.php';
throw new Zend_Controller_Exception('No default module defined for this application');
}

return $className;
}

/**
* Returns TRUE if the Zend_Controller_Request_Abstract object can be
* @param Zend_Controller_Request_Abstract $action
* @return boolean
*/
public function isDispatchable(Zend_Controller_Request_Abstract $request)
{
$className = $this->getActionClass($request);
if (!$className) {
return false;
}
$finalClass  = $className;
if (($this->_defaultModule != $this->_curModule)
|| $this->getParam('prefixDefaultModule'))
{
$finalClass = $this->formatClassName($this->_curModule, $className);
}
if (class_exists($finalClass, false)) {
return true;
}

$fileSpec    = $this->classToFilename($className);
$dispatchDir = $this->getDispatchDirectory();
$test        = $dispatchDir . DIRECTORY_SEPARATOR . $fileSpec;
return Zend_Loader::isReadable($test);
}

/**
* Dispatch to a controller/action
* @param Zend_Controller_Request_Abstract $request
* @param Zend_Controller_Response_Abstract $response
* @return void
* @throws Zend_Controller_Dispatcher_Exception
*/
public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response)
{
$this->setResponse($response);

/**
* Get action class
*/
if (!$this->isDispatchable($request)) {
$action = $request->getControllerName();
if (!$this->getParam('useDefaultActionAlways') && !empty($action)) {
require_once 'Zend/Controller/Dispatcher/Exception.php';
throw new Zend_Controller_Dispatcher_Exception(
'Invalid controller/action specified (' .
$request->getControllerName() . '/' . $request->getActionName() . ')');
}
$className = $this->getDefaultActionClass($request);
} else {
$className = $this->getActionClass($request);
if (!$className) {
$className = $this->getDefaultActionClass($request);
}
}

/**
* Load the action class file
*/
$className = $this->loadClass($className);

$action = new $className($request, $this->getResponse(), $this->getParams());
if (!($action instanceof Zend_Controller_Action_Interface) &&
!($action instanceof Zend_Controller_Action)) {
require_once 'Zend/Controller/Dispatcher/Exception.php';
throw new Zend_Controller_Dispatcher_Exception(
'Action "' . $className . '" is not an instance of
Zend_Controller_Action_Interface'
);
}

/**
* Retrieve the execution name
*/
$execute = $this->getExecuteMethod();

$request->setDispatched(true);

// by default, buffer output
$disableOb = $this->getParam('disableOutputBuffering');
$obLevel   = ob_get_level();
if (empty($disableOb)) {
ob_start();
}

try {
$action->dispatch($execute);
} catch (Exception $e) {
// Clean output buffer on error
$curObLevel = ob_get_level();
if ($curObLevel > $obLevel) {
do {
ob_get_clean();
$curObLevel = ob_get_level();
} while ($curObLevel > $obLevel);
}
throw $e;
}

if (empty($disableOb)) {
$content = ob_get_clean();
$response->appendBody($content);
}

// Destroy the page controller instance and reflection objects
$action = null;
}

/**
* Retrieve default action class
*
* @param Zend_Controller_Request_Abstract $request
* @return string
*/
public function getDefaultActionClass(Zend_Controller_Request_Abstract $request)
{
$controller = $this->getDefaultControllerName();
$defaultController = $this->formatControllerName($controller);

$action = $this->getDefaultAction();
$defaultAction = $this->formatControllerName($action);

$default = $defaultController . $this->getPathDelimiter() . $defaultAction;

$request->setControllerName($controller)
->setActionName($action);

$module              = $request->getModuleName();
$controllerDirs      = $this->getControllerDirectory();
$this->_curModule    = $this->_defaultModule;
$this->_curDirectory = $controllerDirs[$this->_defaultModule];

if ($this->isValidModule($module)) {
$found = false;
if (class_exists($default, false)) {
$found = true;
} else {
$moduleDir = $controllerDirs[$module];
$fileSpec  = $moduleDir . DIRECTORY_SEPARATOR . $this->classToFilename($default);
if (Zend_Loader::isReadable($fileSpec)) {
$found = true;
$this->_curDirectory = $moduleDir;
}
}
if ($found) {
$request->setModuleName($module);
$this->_curModule = $this->formatModuleName($module);
}
} else {
$request->setModuleName($this->_defaultModule);
}

return $default;
}


To switch dispatcher.
$frontController = Zend_Controller_Front::getInstance();
$frontController->setDispatcher(new Hoge_Controller_Dispatcher_MojaviLike())


With this Mojavi dispacher, Controller class will be Action class.
It looks like this.

application/modules/foo/controllers/Bar/BuzAction.php
<?php
class Foo_Bar_BuzAction extends Zend_Controller_Action
{
/** 
* init
*/
public function init()
{   
$this->view->class = '"' . __CLASS__ . '" class';
$this->view->init = '"' . __FUNCTION__ . '" is called';
}   

/** 
* preDispatch
*/
public function preDispatch()
{   
$this->view->preDispatch = '"' . __FUNCTION__ . '" is called';
}   

/** 
* postDispatch
*/
public function postDispatch()
{   
$this->view->postDispatch = '"' . __FUNCTION__ . '" is called';
}   

/** 
* execute this class. 
*/
public function execute()
{   
$this->view->execute = '"' . __FUNCTION__ . '" is called';
}   
}


How about templates?
There is no need to change anything!

To show registered view values above template looks like this.

application/modules/foo/views/scripts/bar/buz.phtml
<b>Mojavi Like</b>
<? foreach ($this as $key =>  $prop): ?>
<?= $prop ?><br />
<? endforeach ?>
But, I also think Zend style is pretty nice and enough. :)
The Controller and Action method should be as small as they can.
The logic should be written in Model class?! Actually I'm not sure if it should be. :p

But, please don't forget, in the real world,
it's pretty hard that all coworkers write source code beautifully and same style. : <

In these case, "Mojavi" style might help you.

At last, thank Mojavi to appeared in this world. And good bye!! :)

(weight 89.6kg BMI 32%)

Labels:

Thursday, February 12, 2009

Try Zend Framework vol.12 "Service My Mini City"

Recentry I got a new work making a web site with php.
Then I luckly to get a chance to use Zend Framework again :)

So, I restarted to try Zend Framework!
But, before trying hard one, I wanted to start trying easy one like Zend_Service.
While thinking about it I recall a web service called "My Mini City" which is very fun and exciting.

I wanted a blog tool of it, but could not find it. So I decide to make blog tool of it looks like this.

myminicity

At first, make library which scrapes your MyMiniCity page.

librarly/Hoge/Service/MyMiniCity.php
<?php
/**
* @see Zend_Service_Abstract
*/
require_once 'Zend/Service/Abstract.php';

/**
* Hoge_Service_MyMiniCity is a concrete implementation of the myminicity web service
*/
class Hoge_Service_MyMiniCity extends Zend_Service_Abstract
{
/**
* Basic URI
*/
const API_URI = 'http://%s.myminicity.com';

/**
* XML path
*/
const PATH_XML = '/xml';

/**
* RSS path
*/
const PATH_RSS = '/rss';

/**
* Xml string
*
* @var xml string
*/
protected $_xml;

/**
* Account
*
* @var string city name
*/
protected $_city;

/**
* Constructs a new myminicity Web Service Client
* The myminicity.com requires "Referer" and "User-Agent"
* otherwise you can not get the page.
*
* @param string $city city name
* @return void
*/
public function __construct($city)
{
$this->_city = $city;
self::getHttpClient()->setHeaders('Referer', sprintf(self::API_URI, $city));
self::getHttpClient()->setHeaders('User-Agent', $_SERVER['HTTP_USER_AGENT']);
}

/**
* Get response body.
* @reutrn string response body.
*/
protected function _getResponse($path = '')
{
$uri = sprintf(self::API_URI . $path, $this->_city);
return self::getHttpClient()->setUri($uri)->request()->getBody();
}

/**
* Get Xml data
*
* @return string xml string
*/
protected function _getXml()
{
if ($this->_xml) return $this->_xml;
$this->_xml = $this->_getResponse(self::PATH_XML);
return $this->_xml;
}

/**
* Get Simple Xmle Element
*
* @return object $obj SimpleXmlElement
*/
protected function _getSimpleXmlElement()
{
return simplexml_load_string($this->_getXml());
}

/**
* Convert xml to array
*
* @return array converted array data from xml string.
*/
protected function _xmlToArray()
{
$items = array();
foreach ($this->_getSimpleXmlElement() as $key => $val) {
if ($key == 'region') {
foreach($val->attributes() as $key2 => $val2) {
$items[$key][$key2] = (string) $val2;
}
$items[$key][0] = (string) $val;
}
else if ($key === 'bases') {
foreach($val->attributes() as $key2 => $val2) {
$items[$key][$key2] = (string) $val2;
}
}
else {
$items[$key] = (string) $val;
}
}
return $items;
}

/**
* Flash vars
* Perse string look like below in source
* The purpose of scraping source is to get k parameter.
* so.addParam("FlashVars","name=kawadu&pop=129&ind=36&tra=8&sec=0&env=0&com=0&k=949e4");
*
* @return string flash parameters.
* @throws Exception In case it failed to get flash parameters.
*/
public function getFlashVars()
{
$pattern = '/so.addParam\("FlashVars","(name=.*?&k=.*?)"\)/';
if (preg_match($pattern, $this->_getResponse(), $matches)) {
return $matches[1];
}
throw new Zend_Service_Exception('Could not perse "k" parameter.');
}

/**
* Get embed tag
*
* @param int $height height
* @param int $width width
* @return string embed tag
*/
public function getEmbedTag($height = '100%', $width = '100%', $showdetail = false)
{
$str ='(function(){
city = arguments[0];

html = "<embed "
html += "quality=\"high\" "
html += "allowscriptaccess=\"always\" "
html += "flashvars=\"" + city.flashvars + "\" "
html += "type=\"application/x-shockwave-flash\" "
html += "src=\"http://data.myminicity.com/swf/client.swf?v=5\" "
html += "bgcolor=\"#deecfe\" "
html += "id=\"client\" "
html += "height=\"" + city.height + "\" "
html += "width=\"" + city.width + "\" "
html += "scale=\"scale\" "
html += "name=\"client\" "
html += "></embed>"
document.write(html);

if (city.showdetail) {
html = "<ul>";
html += "<li>name : " + city.name + "</li>";
html += "<li>region : " + city.region + "</li>";
html += "<li>ranking : " + city.ranking + "</li>";
html += "<li>population : " + city.population + "</li>";
html += "<li>incomes : " + city.incomes + "</li>";
html += "<li>unemployment : " + city.unemployment + "</li>";
html += "<li>transport : " + city.transport + "</li>";
html += "<li>criminality : " + city.criminality + "</li>";
html += "<li>pollution : " + city.population + "</li>";
html += "<li>nextnuke : " + city.nextnuke + "</li>";
html += "<li>signatures : " + city.signatures + "</li>";
html += "</ul>";
document.write(html);
}

html = "<ul>";
html += "<li><a href=\"http://" + city.host + "\" target=\"_blank\">Increase population</a></li>";
html += "<li><a href=\"http://" + city.host + "/ind\" target=\"_blank\">Increase industry</a></li>";
html += "<li><a href=\"http://" + city.host + "/tra\" target=\"_blank\">Improve the transport network</a></li>";
html += "<li><a href=\"http://" + city.host + "/sec\" target=\"_blank\">Increase security</a></li>";
html += "<li><a href=\"http://" + city.host + "/env\" target=\"_blank\">Increase environment</a></li>";
html += "<li><a href=\"http://" + city.host + "/com\" target=\"_blank\">Increase business</a></li>";
html += "</ul>";
document.write(html);

})({
height: "' . $height . '",
width: "' . $width .'",
host : "' . $this->host . '",
name: "' . $this->name . '",
region: "' . $this->region . '",
ranking: "' . $this->ranking . '",
population: "' . $this->population . '",
incomes: "' . $this->incomes . '",
unemployment: "' . $this->unemployment . '",
transport: "' . $this->transport . '",
criminality: "' . $this->criminality . '",
pollution: "' . $this->population . '",
nextnuke:"' . $this->nextnuke . '",
signatures:"' . $this->signatures . '",
flashvars:"' . $this->flashvars . '",
showdetail:"' . $showdetail . '"
});
';
return $str;
}

/**
* Get Xml
* @return string xml string.
*/
public function getXml()
{
return $this->_getXml();
}

/**
* Get Rss
* @return string Rss string
*/
public function getRss()
{
return $this->_getResponse(self::PATH_RSS);
}

/**
* Get Json
* get json string from xml data
* @return string json string
*/
public function getJson()
{
require_once 'Zend/Json.php';
return Zend_Json::fromXml($this->_getXml(), false);
}

/**
* Get Array
* Get array values from xml data
*
* @return array data array converted from xml data
*/
public function getArray()
{
return $this->_xmlToArray($this->_getSimpleXmlElement());
}

/**
* Get specified data from xml data
*
* @return string|array specified data.
*/
public function __get($name)
{
return $this->__call($name);
}

/**
* Get parameter (magic method call)
* Getting value from Xml data.
*
* @param $name key to get data from xml
* @param $argv no needs
* @return string|array specified key's value.
*/
public function __call($name, $argv = null)
{
$data = $this->getArray();
switch ($name) {
case 'region':
return $data['region'][0];
case 'code':
return $data['region']['code'];
case 'com':
case 'env':
case 'ind':
case 'sec':
case 'tra':
return $data['bases'][$name];
case 'rss':
return $this->getRss();
case 'xml':
return $this->getXml();
case 'embed':
return $this->getEmbedTag();
case 'json':
return $this->getJson();
case 'array':
return $this->getArray();
case 'flashvars':
return $this->getFlashVars();
default:
return (isset($data[$name])) ? $data[$name] : null;
}
}
}


Ok. Let's make API application.
For here, I want to make the url looks like this.

http://yoursite/?city=your_city_name

application/controllers/IndexController.php
<?php
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$city = $this->_getParam('city');
$height = $this->_getParam('height', null);
$width = $this->_getParam('width', null);
$showdetail = $this->_getParam('showdetail', false);
$s = new Hoge_Service_MyMiniCity($city);
echo $s->getEmbedTag($height, $width, $showdetail);
}
}


To get data in other way. You can get it as property.
echo $s->xml;
echo $s->json;
echo $s->array;
echo $s->embed; // but you can not set size or show detail setting.
echo $s->rss;

To show blog tool in your site, use javascript.
<script src="http://yoursite/path/to/application/?city=your_city_name" type="text/javascript"></script>


Don't have a time? Or feel bothering to make it?
Here is a sample.
http://api.f-pig.net/myminicity/?city=your_city_name
Try your city name insted of "your_city_name" :)

Any way, Zend Framework is nice and fun. I will try more.

Oops, I have to win the game with my co-workers what if I can lose my weight about 6kg until end of March, I will win!!
(weight 94.7kg BMI 34%)

Labels:

Tuesday, November 18, 2008

Zend Framework 1.7 is released!!

Yahoooooooooooooooooooooooooooooooooooooo!!

Zend Framework 1.7 is released !

Zend_AMF will become a rainbow bridge to the Actionscripters!
ZendX_JQuery will become a lighthouse for the Javascripters!

Any way, I restarted to use Zend Framework for my new project :)
And so happy to forget how fatty I am!!
(weight I don't kg BMI care%)

Labels:

Wednesday, September 03, 2008

Zend Framework 1.6 is released!!

Yahoooooooooooooooooooooooooooooooooooooo!!

Zend Framework 1.6 is released !
With dojo ?? Oh OKay.
It has been enhanced a lot while I haven't checked. :)

Any way, it's been a long time since I worte the last entry :P
And so happy to forget how fatty I am!!
(weight I don't kg BMI care%)

Labels:

Monday, March 17, 2008

Zend Framework 1.5 is released!!

Yahoooooooooooooooooooooooooooooooooooooo!!

Zend Framework 1.5 is released today! :p

And so happy to forget how fatty I am!!
(weight I don't kg BMI care%)

Labels:

Tuesday, February 26, 2008

Zend Framework 1.0.4 is released!!

Yahoooooooooooooooooooooooooooooooooooooo!!

Zend Framework 1.0.4 is released today! :p

And so happy to forget how fatty I am!!
(weight I don't kg BMI care%)

Labels:

Sunday, January 27, 2008

Try Zend Framework vol.11 "Save session data into DB"

How do you handle session in your site? Usually you may not need to change it from default.
Let's think your site is on the several web servers and need to use unique session. You probably want to store session into database.
Zend Framework provides interface of session save handler. so you just need to implement the interface when you create your own session save handler.
Then I tried to write my own session save handler here.
But before writing it, I draw upon Zend Developer Zone tips below.

Trick-Out Your Session Handler
To know how to write session save handler.

Zend_Session_SaveHandler_DbTable
To know the concept of the session save handler for Zend Framework.
But I did not implement all of them but some of them.

According to "Trick-Out Your Session Handler", I should write opening database connection in "open()" method, and closing database connection in "close()" method. But in my class I just made it return "true". :P

First, we are going to store session into this table(in this case I used PostgreSQL, you may change timestamp to datetime with MySQL)
CREATE TABLE sessions (
id char(32) default NULL,
lifetime timestamp default NULL,
data text
);

OK, here this is a session save handler of database.
library/Hoge/Session/SaveHandler/Db.php
<?php
require_once 'Zend/Db/Table/Abstract.php';

/**
 * Hoge_Session_SaveHandler_Db
 */
class Hoge_Session_SaveHandler_Db extends Zend_Db_Table_Abstract implements Zend_Session_SaveHandler_Interface
{
    /**
     * constatns
     */
    // session table name
    const TABLE_NAME         = 'sessions';
    // primary key column name
    const COLUMN_PRIMARY_KEY = 'id';
    // lifetime column name
    const COLUMN_LIFETIME    = 'lifetime';
    // data column name
    const COLUMN_DATA        = 'data';

    /**
     * primary key
     * @var string
     */
    protected $_primary = self::COLUMN_PRIMARY_KEY;

    /**
     * table name
     * @var string
     */
    protected $_name = self::TABLE_NAME;

    /**
     * table columns.
     * @var array
     */
    protected $_columnMap = array(
        self::COLUMN_PRIMARY_KEY => self::COLUMN_PRIMARY_KEY,
        self::COLUMN_LIFETIME    => self::COLUMN_LIFETIME,
        self::COLUMN_DATA        => self::COLUMN_DATA
    );

    /**
     * session maxlifetime
     * @var null|intger
     */
    protected $_lifetime = null;

    /**
     * constructor
     * @param string $table      Session table name
     * @param array  $columnMap  Session table column names
     */
    public function __construct($table = null, $columnMap = null)
    {
        // set table name
        if ($table) {
            $this->_name = $table;
        }

        if ($columnMap) {
            // set primary key name
            if (isset($columnMap[self::COLUMN_PRIMARY_KEY])) {
                $this->_columnMap[self::COLUMN_PRIMARY_KEY] = $columnMap[self::COLUMN_PRIMARY_KEY];
                $this->_primary = $columnMap[self::COLUMN_PRIMARY_KEY];
            }
            // set lifetime column name
            if (isset($columnMap[self::COLUMN_LIFETIME])) {
                $this->_columnMap[self::COLUMN_LIFETIME] = $columnMap[self::COLUMN_LIFETIME];
            }
            // set session data column name
            if (isset($columnMap[self::COLUMN_DATA])) {
                $this->_columnMap[self::COLUMN_DATA] = $columnMap[self::COLUMN_DATA];
            }
        }

        parent::__construct();
    }

    /**
     * Set session max lifetime.
     * @param $lifetime
     */
    public function setLifetime($lifetime)
    {
        $this->_lifetime = $lifetime;
    }

    /**
     * Open Session - retrieve resources
     *
     * @param string $save_path
     * @param string $name
     */
    public function open($save_path, $name)
    {
        return true;
    }

    /**
     * Close Session - free resources
     *
     */
    public function close()
    {
        return true;
    }

    /**
     * Read session data
     *
     * @param string $id
     * @return string
     */
    public function read($id)
    {
        $return = '';
        $where = $this->getAdapter()->quoteInto($this->_columnMap[self::COLUMN_PRIMARY_KEY] . "=?", $id);
        if ($row = $this->fetchRow($where)) {
            $return = $row->{$this->_columnMap[self::COLUMN_DATA]};
        }
        return $return;
    }

    /**
     * Write Session - commit data to resource
     *
     * @param string $id
     * @param mixed $data
     * @return bool
     */
    public function write($id, $data)
    {
        $return = false;
        $dataSet = array(
            $this->_columnMap[self::COLUMN_PRIMARY_KEY] => $id,
            $this->_columnMap[self::COLUMN_LIFETIME]    => date("Y-m-d H:i:s", mktime()),
            $this->_columnMap[self::COLUMN_DATA]        => $data
        );
        $where = $this->getAdapter()->quoteInto($this->_columnMap[self::COLUMN_PRIMARY_KEY] . "=?", $id);

        if ($this->fetchRow($where)) {
            $return = ($this->update($dataSet, $where)) ? true : false;
        } else {
            $return = ($this->insert($dataSet)) ? true: false;
        }

        return $return;
    }

    /**
     * Destroy Session - remove data from resource for
     * given session id
     *
     * @param string $id
     * @return bool
     */
    public function destroy($id)
    {
        $where = $this->getAdapter()->quoteInto($this->_columnMap[self::COLUMN_PRIMARY_KEY] . "=?", $id);
        return ($this->delete($where)) ? true : false;
    }

    /**
     * Garbage Collection - remove old session data older
     * than $maxlifetime (in seconds)
     *
     * @param int $maxlifetime
     * @return bool
     */
    public function gc($maxlifetime)
    {
        $lifetime = ($this->_lifetime) ? $this->_lifetime : $maxlifetime;
        $expiry = date("Y-m-d H:i:s", mktime() - $lifetime);
        $where = $this->getAdapter()->quoteInto($this->_columnMap[self::COLUMN_LIFETIME] . "<=?", $expiry);
        return ($this->delete($where)) ? true : false;
    }
}

Well, Let's use that session save handler.
Basically all you need to do is initialize that save handler and set it to Zend_Session_SaveHandler.

www/index.php
require_once 'Hoge/Session/SaveHandler/Db.php';
$saveHandler = new Hoge_Session_SaveHandler_Db();
Zend_Session::setSaveHandler($saveHandler);

If you want to change table name and column name, set it on constructor.
$table = 'hoge';
$columns = array('id'=>'foo', 'lifetime'=>'bar', 'data'=> 'buz');
$saveHandler = new Hoge_Session_SaveHandler_Db($table, $columns);

If you want to set session max life time, use lifetime setter.
$saveHandler = new Hoge_Session_SaveHandler_Db();
$saveHandler->setLifetime(60*60*12);

That's it. But Zend Framework release version 1.5 soon or later.
It may includes "Zend_Session_SaveHandler_DbTable".
May be it much flexible and useful :)
But I didn't want to wait it. Just wanted to write it like somebody talked to me "Don't wait somebody write code, just write as you need!" :)
(weight 88.6kg BMI 31%)

Labels:

Saturday, January 12, 2008

Try Zend Framework vol.10 "Mailing an error log"

Zend Framework has great logging system like "Zend_Log_Writer_Stream", "Zend_Log_Writer_Db".
But if some trouble happens, you might want to know it ASAP or don't want to know it forever, don't you :P

Let's make a log writer for mail first.

library/Hoge/Log/Writer/Mail.php
<?php
/** Zend_Log_Writer_Abstract */
require_once 'Zend/Log/Writer/Abstract.php';

/** Zend_Log_Formatter_Simple */
require_once 'Zend/Log/Formatter/Simple.php';

class Hoge_Log_Writer_Mail extends Zend_Log_Writer_Abstract
{
/**
* Mailer
* @var Zend_Mail
*/
private $_mail;

/**
* Class constructor
*
* @param Zend_Mail $mail Mail instance
*/
public function __construct($mail)
{
$this->_mail = $mail;
$this->_formatter = new Zend_Log_Formatter_Simple();
}

/**
* Write a message to the log.
*
* @param array $event event data
* @return void
*/
protected function _write($event)
{
$line = $this->_formatter->format($event);
$this->_mail->setBodyText($line);
$this->_mail->send();
}

}


Settings for Mail Logger.
www/index.php
<?php
require_once 'Zend/Registry.php';
require_once 'Zend/Mail.php';
require_once 'Zend/Log.php';
require_once 'Hoge/Log/Writer/Mail.php';

// Create mail object.
$mail = new Zend_Mail();
$mail->addTo('foo@example.com', 'System members');
$mail->setSubject('Error Occured!');
$mail->setFrom('foo@example.com');

// Set writer
$writer = new Hoge_Log_Writer_Mail($mail);

// Set logger and writer
$logger = new Zend_Log();
$logger->addWriter($writer);

// Set in registry
Zend_Registry::set('logger', $logger);


To use it. Get logger from registry.
application/controllers/HogeController.php
<?php
class HogeController extends Zend_Controller_Action
{
function loggerAction()
{
$logger = Zend_Registry::get('logger');
$logger->log('Information log', Zend_Log::INFO);
$logger->emerg('Emerg log');
exit;
}
}


Ok, that's it. From now, you cannot escape from error mail!
If you don't want to get error mail just create system without bugs!! :)
(weight 88.7kg BMI 30%)

Labels:

Tuesday, January 01, 2008

Try Zend Framework vol.9 "Adding Json method on Rowset class"

Recently I started to study javascript and it's pretty interesting to use ajax.
When you deal with ajax, you probably want to get data as JSON. Don't you?

Zend Framework has data set handling class which is called
"Zend_Db_Table_Row" and "Zend_Db_Table_Rowset". And they have data conversion method "toArray()"

If they have JSON conversion method like "toJson()", it's helpful. isn't it? :)

Ok, Let's say you have data like below. We are going to convert this data to JSON.
mysql> select * from data;
+----+---------+
| id | data |
+----+---------+
| 1 | data 1 |
| 2 | data 2 |
| 3 | data 3 |
| 4 | data 4 |
| 5 | data 5 |
+----+---------+


Add a new method toJson() on Row class.
library/Hoge/Db/Table/Row.php
<?php
/**
* @see Zend_Db_Table_Row
*/
require_once 'Zend/Db/Table/Row.php';

class Hoge_Db_Table_Row extends Zend_Db_Table_Row
{
/**
* Returns the column/value data as json.
*
* @return string
*/
public function toJson()
{
require_once 'Zend/Json.php';
return Zend_Json::encode($this->_data);
}
}


For Rowset class.
library/Hoge/Db/Table/Rowset.php
<?php
/**
* @see Zend_Db_Table_Rowset
*/
require_once 'Zend/Db/Table/Rowset.php';

class Hoge_Db_Table_Rowset extends Zend_Db_Table_Rowset
{
/**
* Returns all data as a json.
*
* @return string
*/
public function toJson()
{
require_once 'Zend/Json.php';
return Zend_Json::encode($this->_data);
}
}


Ok, Let's use these extended classes.
There are several ways to specify these new classes.

1.Set names of these classes via setter methods.
application/controllers/HogeController.php

function jsonAction() {
require_once 'Hoge/Db/Table/Data.php';
$data = new Data();
$data->setRowClass('Hoge_Db_Table_Row');
$data->setRowsetClass('Hoge_Db_Table_Rowset');
$res = $data->fetchAll()->toJson();
print_r($res);
exit;
}


2.Set names of these classes in the Table_Abstract class.
library/Hoge/Db/Table/Abstract.php
<?php
require_once 'Zend/Db/Table/Abstract.php';

abstract class Hoge_Db_Table_Abstract extends Zend_Db_Table_Abstract
{

public function __construct()
{
$config = array(
'rowClass' => 'Hoge_Db_Table_Row',
'rowsetClass' => 'Hoge_Db_Table_Rowset'
);
parent::__construct($config);
}
}


To use toJson() method. The usage is same as toArray() method.
application/controllers/HogeController.php

function jsonAction()
{
require_once 'Hoge/Db/Table/Data.php';
$data = new Data();
$res = $data->fetchAll()->toJson();
print_r($res);
exit;
}


It returns JSON data like below.
[{"id":"1","data":"data 1"},{"id":"2","data":"data 2"},{"id":"3","data":"data 3"},{"id":"4","data":"data 4"},{"id":"5","data":"data 5"}]

(weight 89.0kg BMI 31%)

Labels:

Saturday, December 15, 2007

Try Zend Framework vol.8 "Using Layout with ViewRenderer"

**************************************** Notice ****************************************
"Zend_Layout" is out after version 1.5 !!
It's much more flexible and easy to use . So you should read the documentation and use it. :-)
**************************************** Notice ****************************************

When you use Zend Framework, you probably write full HTML for each action in your controllers.
It's kind of wasting time to write HTML for every template. isn't is?
And if you need to put additional code like css or javascript, you need to write same code for every template.
I think the concept of "Layout" is very powerful and useful, so I wrote ViewRenderer which can handle layout.

First you need to create a new ViewRenderer which extends Zend_Controller_Action_Helper_ViewRenderer. And just change a little on postDispatch() and add some methods.

library/Hoge/Controller/Action/Helper/ViewRenderer.php
<?php
require_once 'Zend/Controller/Action/Helper/ViewRenderer.php';

/**
* View script integration
*/
class Hoge_Controller_Action_Helper_ViewRenderer extends Zend_Controller_Action_Helper_ViewRenderer
{
/**
* postDispatch - auto render a view
* @return void
*/
public function postDispatch()
{
if ($this->getFrontController()->getParam('noViewRenderer')) {
return;
}

if (!$this->_neverRender
&& !$this->_noRender
&& (null !== $this->_actionController)
&& $this->getRequest()->isDispatched()
&& !$this->getResponse()->isRedirect())
{
if($this->hasViewLayout()) {
$this->render();
$this->view->contens = $this->getResponse()->getBody();
$this->getResponse()->clearBody();
$this->render($this->getViewLayout(), null, true);
} else {
$this->render();
}
}
}

/**
* if layout is sat.
* @return bool
*/
function hasViewLayout()
{
if ($this->getActionController()->layout) {
return true;
}
return false;
}

/**
* Get layout template
* @return string
*/
function getViewLayout()
{
return $this->getActionController()->layout;
}
}


Next, you need to add Views' Script Path.
www/index.php
$view = new Zend_View();
$view->setScriptPath(dirname(dirname(__FILE__)) . '/application/views/scripts/');
$view->addScriptPath(dirname(dirname(__FILE__)) . '/application/views/layouts/');

so now, it will look for templates not only scripts but also layouts directory.
*Don't forget to set setScriptPath, too. Otherwise it only see layouts directory.
Then, use new ViewRenderer which you just made.
www/index.php
$viewRenderer = new Hoge_Controller_Action_Helper_ViewRenderer($view);
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);

To use it you nedd to set layout. Set $layout variable and put the name of layout template.

appplication/controllers/HogeController.php
class HogeController extends Zend_Controller_Action
{
var $layout = 'default';


And make layout template
application/views/layouts/default.phtml
<html>
<head>
</head>
<body>
<?= $this->contens ?>
</body>
</html>


That's it. :-)

Be careful, under these case like bellow, ViewRenderer will not work. :P

Call setNoRender() in the Controller.
application/controllers/HogeController.php
$this->_helper->viewRenderer->setNoRender();


Call render() on purpose.
application/controllers/HogeController.php
function indexAction()
{
$this->render();
}


Set noViewRenderer.
www/index.php
$front = Zend_Controller_Front::getInstance();
$front->setParam('noViewRenderer', true);


(weight 87.0kg BMI 29%)

Labels:

Friday, November 30, 2007

Zend Framework 1.0.3 is released!!

Yahoooooooooooooooooooooooooooooooooooooo!!

Zend Framework 1.0.3 is released today! :p

And so happy to forget how fatty I am!!
(weight I don't kg BMI care%)

Labels:

Friday, November 23, 2007

Try Zend Framework vol.7 "Using Simplate as Template Enginge"

As you guys know "Smarty" is pretty famous template engine.
But, "Simplate" is likely faster than "Smarty".
And the way of writing template is almost same as smarty.
So I tried using it as zend view template.

First, you need to install simplate as php extension, but don't worry it's pretty easy.
All you need to do is bellow.
* In my case installed php at /usr/local/php525, and make simbolick link like below.
php5 -> /usr/local/php525/bin/php
phpize5 -> /usr/local/php525/bin/phpize.

Ok, install simplate extension.
# cd /usr/local/src
# wget http://simplate.aimy.jp/archive/simplate-0.3.7.tar.gz
# tar zxvf simplate-0.3.7.tar.gz
# cd simplate-0.3.7
# phpize5
# ./configure \
--enable-simplate \
--with-php-config=/usr/local/php525/bin/php-config \
--prefix=/usr/local/php525 \
# make
# make install


If you see the message like below, you would success to install simplate extension.
Installing shared extensions:     /usr/local/php525/lib/php/extensions/no-debug-non-zts-20060613/


Next, you need to modify php.ini.
# vim /usr/local/php525/lib/php.ini

Add this line.
extension=simplate.so

Then reload apache. Don't forget to check if the extension is installed like this.
php5 -i | grep simplate


You should see message like this.
simplatesimplate support => enabled


Ok, we are ready to make simplate view!

library/Hoge/View/Simplate.php
<?php
require_once 'Zend/View/Interface.php';

class Hoge_View_Simplate implements Zend_View_Interface
{
/**
* Simplate object
* @var Simplate
*/
protected $_simplate;

/**
* constructor
*
* @param string $tmplPath
* @param array $extraParams
* @return void
*/
public function __construct($tmplPath = null, $extraParams = array())
{
$this->_simplate = new Simplate;
if (null !== $tmplPath) {
$this->setScriptPath($tmplPath);
}
foreach ($extraParams as $key => $value) {
$this->_simplate->$key = $value;
}
}

/**
* Return template enginge object.
*
* @return Simplate
*/
public function getEngine()
{
return $this->_simplate;
}

/**
* Set template's path
*
* @param strimg $path Directory path
* @return void
public function setScriptPath($path)
{
if (is_readable($path)) {
$this->_simplate->template_dir = $path;
return;
}
throw new Exception("You specified unrecognized path '{$path}'");
}

/**
* Get current template path.
* @return string
*/
public function getScriptPaths()
{
return $this->_simplate->template_dir;
}

/**
* Alias of setScript path.
*
* @param string $path
* @param string $prefix Unused
* @return void
*/
public function setBasePath($path, $prefix = 'Zend_View')
{
return $this->setScriptPath($path);
}

/**
* Alias of setScript path.
*
* @param string $path
* @param string $prefix unused
*
* @return void
*/
public function addBasePath($path, $prefix = 'Zend_View')
{
return $this->setScriptPath($path);
}

/**
* Set valiables to template.
*
* @param string $key valiable name
* @param mixed $val valiable value
* @return void
*/
public function __set($key, $value)
{
$this->_simplate->assign($key, $value);
}

/**
* Get value.
*
* @param string $key valiable name
* @return mixed valiable value
*/
public function __get($key)
{
return $this->_simplate->_tpl_vars[$key];
}

/**
* Enable test for empty() isset().
*
* @param string $key
* @return boolen
*/
public function __isset($key)
{
return isset($this->_simplate->_tpl_vars[$key]);
}

/**
* Enable unset to object valiable.
* @return void
*/
public function __unset($key)
{
unset($this->_simplate->_tpl_vars[$key]);
}

/**
* Set valiable to template.
* set value to specific key.
* set at once by key => value array.
*
* @see __set()
* @param string|array $spec type of settin value (key or key => value array)
* @param mixed $value (optionale) specify the name of key. set here.
* @return void
*/
public function assign($spec, $value = null)
{
if (is_array($spec)) {
foreach($spec as $key => $value) {
$this->_simplate->assign($key, $value);
}
return;
}
$this->_simplate->assign($spec, $value);
}

/**
* Clear all valiables.
* Clear all valiables sat to Zend_View by {@link assign()} or {@link __get()}/{@link __set()}.
*
* @return void
*/
public function clearVars()
{
$this->_simplate->_tpl_vars = null;
}

/**
* Dispatch template and return result.
*
* @param string $name template name
* @return string result
*/
public function render($name)
{
return $this->_simplate->fetch($name);
}
}


Make method for displaying simplate template with assigning 20000 variables.
* Don't forget writing $this->_helper->viewRenderer->setNoRender();
otherwise zf automatically expect the template which has same name as method and has phtml suffix then render it, even if you specify another desired template.

application/controllers/HogeController.php
function simplateAction()
{
$this->_helper->viewRenderer->setNoRender();
require_once 'Hoge/View/Simplate.php';
$tplPath = dirname(dirname(__FILE__)) . '/views/scripts/hoge/';
$params = array(
'compile_dir' => dirname(dirname(__FILE__)) . '/views/templates_c',
'left_delimiter' => '{',
'right_delimiter' => '}',
);
$view = new Hoge_View_Simplate($tplPath, $params);
foreach(range(0, 20000) as $i) {
$vars[$i] = "Hello World $i";
}
$view->vars = $vars;
echo $view->render('simplate.tpl');
}


This is template.
<html>
<head></head>
<body>
{foreach key=key item=item from=$vars}
{$key} : {$item} <br />
{/foreach}
</body>
</html>


That's it. You will see 20000 Hello Worlds!

But, it's not enough, because I didn't check if simplate is faster than smaty template.
So, I compare "Smaty" with "Simplate".

Get time before execution.

application/controllers/HogeController.php
function preDispatch()
{
$this->startTime = microtime(true);
}

function postDispatch()
{
echo (microtime(true) - $this->startTime);
}


For Smaty template view you should see here

Controllers' method is this. Almost same as simplate.
application/controllers/HogeController.php
function smartyAction()
{
$this->_helper->viewRenderer->setNoRender();
require_once 'Hoge/View/Smarty.php';
$tplPath = dirname(dirname(__FILE__)) . '/views/scripts/hoge/';
$params = array(
'compile_dir' => dirname(dirname(__FILE__)) . '/views/templates_c',
);
$view = new Hoge_View_Smarty($tplPath, $params);
foreach(range(0, 20000) as $i) {
$vars[$i] = "Hello World $i";
}
$view->vars = $vars;
echo $view->render('smarty.tpl');
}


Template is same as simplate.

Here the result of rendering time.
Simplate: 0.137821912766
Smarty : 0.153032064438


Faster than smaty. :-)
(weight 89.4kg BMI 33%)

Labels:

Sunday, October 21, 2007

Try Zend Framework vol.6 "Simple Pager"

When I look back in my old days, the pagination was my first obstacle to understand how it works like my friend introduced here.
PEAR::Pager was so impressive for me to make pagination. :-)
So, I wrote a simple pager for Zend Framework view helper.

Let's say you have data like below. The total record is 1000.
mysql> select * from data limit 10;
+----+---------+
| id | data |
+----+---------+
| 1 | data 1 |
| 2 | data 2 |
| 3 | data 3 |
| 4 | data 4 |
| 5 | data 5 |
| 6 | data 6 |
| 7 | data 7 |
| 8 | data 8 |
| 9 | data 9 |
| 10 | data 10 |
+----+---------+


Here the pager helper.
It basically requires total number of record for pagination.
library/Hoge/View/Helper/Pager.php
<?php
require_once 'Zend/View/Helper/Url.php';

/**
* Pager Helper
*/

class Hoge_View_Helper_Pager
{
/**
* Current page
* @var integer
*/
protected $currentPage = 1;

/**
* Per page
* How many items to display per pages.
* @var integer
*/
protected $perPage = 20;

/**
* Display links
* How many page to display in the pagination links.
* @var integer
*/
protected $pageRange = 10;

/**
* Total number of items
* @var integer
*/
protected $total = 0;

/**
* Start page
* @var integer
*/
protected $startPage = 1;

/**
* End page
* @var integer
*/
protected $endPage = 1;

/**
* Pager format.
*/
const FORMAT_FIRST = '<a href="%s" class="pager_first">&#171;</a>';
const FORMAT_PREVIOUS = '<a href="%s" class="pager_previous">&#139;</a>';
const FORMAT_PREVIOUS_RANGE = '<a href="%s" class="pager_previous_range">...</a>';
const FORMAT_NAVIGATION_LINK = '<a href="%s" class="pager_navigation_link">%d</a>';
const FORMAT_NAVIGATION_CURRENT = '<b class="pager_navigation_current">%d</b>';
const FORMAT_NEXT_RANGE = '<a href="%s" class="pager_next_range">...</a>';
const FORMAT_NEXT = '<a href="%s" class="pager_next">&#155;</a>';
const FORMAT_LAST = '<a href="%s" class="pager_last">&#187;</a>';

/**
* Base link formats
* @var array
*/
protected $baseFormats =
array(
'first' => self::FORMAT_FIRST,
'previous' => self::FORMAT_PREVIOUS,
'previousRange' => self::FORMAT_PREVIOUS_RANGE,
'navigationLink' => self::FORMAT_NAVIGATION_LINK,
'navigationCurrent' => self::FORMAT_NAVIGATION_CURRENT,
'nextRange' => self::FORMAT_NEXT_RANGE,
'next' => self::FORMAT_NEXT,
'last' => self::FORMAT_LAST
);

/**
* Pager instance
* @var object
*/
static protected $pager;

/**
* Pager
*
* @access public
*
* @param integer $total Total records
* @param integer $perPage How many to display per page
*
* @return object pager
*/
public function pager($total = null, $perPage = 20)
{
if(isset(self::$pager)) return self::$pager;

$page = Zend_Controller_Front::getInstance()->getRequest()->getParam('page');

if ($page > 0) {
$this->currentPage = $page;
}

// Number of total data.
$this->total = $total;

// How many rows to display per page.
$this->perPage = $perPage;

// Number of total page
$this->totalPage = ceil($this->total / $this->perPage);

// If total page is smaller than called page.
if ($this->totalPage < $this->currentPage) $this->currentPage = 1;

// Page numnber of core pages.
$corePage = ceil($this->currentPage / $this->pageRange);

// Max page number.
$this->endPage = ($this->pageRange * $corePage);

// Start page number.
$this->startPage = ($this->endPage - $this->pageRange) + 1;

// Set to static instance
self::$pager = $this;

return $this;
}


/**
* Display a simple pagination ex:[<< < ... 1 2 3 ... > >>]
*
* @param array $formats An array of links to use sprintf()
* @return string A link to the desired page page of the pagination
*/
public function paginate(array $formats = array())
{
$formats = array_merge($this->baseFormats, $formats);

$this->pager($this->total, $this->perPage);
$pagination = array();
$pagination[] = $this->pager()->first($formats['first']);
$pagination[] = $this->pager()->previous($formats['previous']);
$pagination[] = $this->pager()->previousRange($formats['previousRange']);
$pagination[] = $this->pager()->navigation($formats['navigationLink'], $formats['navigationCurrent']);
$pagination[] = $this->pager()->nextRange($formats['nextRange']);
$pagination[] = $this->pager()->next($formats['next']);
$pagination[] = $this->pager()->last($formats['last']);

return join('', $pagination);
}

/**
* Display link of the navigation links ex:[1 2 3]
*
* @param string $linkFormat Format string to display page number links to use sprintf()
* @param string $currentFormat Format string to display current page to use sprintf()
* @return string A link to the desired page page of the pagination
*/
public function navigation($linkFormat = self::FORMAT_NAVIGATION_LINK, $currentFormat = self::FORMAT_NAVIGATION_CURRENT)
{
$return = null;
// Display [1,2,3__]
for ($p = $this->startPage; $p <= $this->endPage; $p++) {
// Unlink if the page is selected
if ($p <= $this->totalPage) {
if ($this->currentPage != $p) {
$url = $this->url(array('page' => $p));
$return[] = sprintf($linkFormat, $url, $p);
} else {
$return[] = sprintf($currentFormat, $p);
}
}
}
return join('', $return);
}

/**
* Display link of the next range ex:[...]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the previous range of the pagination
*/
public function previousRange($format = self::FORMAT_PREVIOUS_RANGE)
{
$return = null;
if ($this->pageRange < $this->currentPage) {
$url = $this->url(array('page' => ($this->startPage - 1)));
$return = sprintf($format, $url);
}
return $return;
}

/**
* Display link of the next range ex:[...]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the next range of the pagination
*/
public function nextRange($format = self::FORMAT_NEXT_RANGE)
{
$return = null;
if ($this->endPage < $this->totalPage) {
$url = $this->url(array('page' => ($this->endPage + 1)));
$return = sprintf($format, $url);
}
return $return;
}

/**
* Display link of the previous page ex:[<<]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the first page of the pagination
*/
public function first($format = self::FORMAT_FIRST)
{
$return = null;
if ($this->currentPage != 1) {
$url = $this->url(array('page' => 1));
$return = sprintf($format, $url);
}
return $return;
}

/**
* Display link of the previous page ex:[<]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the previous page of the pagination
*/
public function previous($format = self::FORMAT_PREVIOUS)
{
$return = null;
if ($this->currentPage != 1) {
$url = $this->url(array('page' => ($this->currentPage - 1)));
$return = sprintf($format, $url);
}
return $return;
}

/**
* Display link of the next page ex:[>]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the next page of the pagination
*/
public function next($format = self::FORMAT_NEXT)
{
$return = null;
if($this->currentPage != $this->totalPage) {
$url = $this->url(array('page' => ($this->currentPage + 1)));
$return = sprintf($format, $url);
}
return $return;
}

/**
* Display link of the last page ex:[>>]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the last page of the pagination
*/
public function last($format = self::FORMAT_LAST)
{
$return = null;
if($this->currentPage != $this->totalPage) {
$url = $this->url(array('page' => $this->totalPage));
$return = sprintf($format, $url);
}
return $return;
}


/**
* Generates an url given the name of a route.
*
* @access public
*
* @see Zend_View_Helper::url()
* @param array $urlOptions Options passed to the assemble method of the Route object.
* @param mixed $name The name of a Route to use. If null it will use the current Route
* @param bool $reset Whether or not to reset the route defaults with those provided
* @return string Url for the link href attribute.
*/
public function url(array $urlOptions = array(), $name = null, $reset = false)
{
$obj = new Zend_View_Helper_Url();
$url = $obj->url($urlOptions, $name, $reset);
return $url;
}

}


Pager needs to know how many total record the object paged has.
In this case, I want to count DB record.
So I added a method to count simple record of Zend_Db_Table object.
library/Hoge/Db/Table/Abstract.php
public function numRows($where = null)
{
$select = $this->_db->select();
$select->from($this->_name, "COUNT(*)", $this->_schema);
// the WHERE clause
$where = (array) $where;
foreach ($where as $key => $val) {
// is $key an int?
if (is_int($key)) {
// $val is the full condition
$select->where($val);
} else {
// $key is the condition with placeholder,
// and $val is quoted into the condition
$select->where($key, $val);
}
}
// return the results
$stmt = $this->_db->query($select);
$data = $stmt->fetchColumn(0);
return $data;
}


To use it.
It is not related to Db model. So you need to calculate the offset and limit.
But it not so difficult, isn't it? :p
application/controllers/HogeController.php
function pagerAction()
{
$this->view->setHelperPath(dirname(dirname(dirname(__FILE__))) . '/library/Hoge/View/Helper', 'Hoge_View_Helper');
$this->view->addHelperPath(dirname(dirname(__FILE__)) . '/View/Helper', 'Hoge_View_Helper');

require_once 'Hoge/Db/Table/Data.php';
$data = new Data();
$perPage = 20; // Should be defined.
$offset = ($this->_getParam('page') > 0) ? $perPage * ($this->_getParam('page') - 1) : 0;
$this->view->data = $data->fetchAll(null, null, $perPage, $offset)->toArray();
$this->view->total = $data->numRows();
}


In the view script. Write like this.
In case you want to handle each parts of pager.
<html>
<head></head>
<body>
<? $this->pager($this->total) ?>
<?= $this->pager()->first() ?>
<?= $this->pager()->previous() ?>
<?= $this->pager()->previousRange() ?>
<?= $this->pager()->navigation() ?>
<?= $this->pager()->nextRange() ?>
<?= $this->pager()->next() ?>
<?= $this->pager()->last() ?>

<ul>
<? foreach($this->data as $k => $v): ?>
<li><?= "{$v['id']} : {$v['data']}" ?></li>
<? endforeach ?>
</ul>
</body>
</html>


In case you want to display simple pager.
<html>
<head></head>
<body>
<?= $this->pager($this->total)->paginate() ?>
<ul>
<? foreach($this->data as $k => $v): ?>
<li><?= "{$v['id']} : {$v['data']}" ?></li>
<? endforeach ?>
</ul>
</body>
</html>

Both of them look like this.
«‹...11121314151617181920...›»

The helper class only have responsibility creating pagination, NOT dataset, so you need to set offset and limit.
If you want to get more sophisticated "Pagination helper" see here :^)
The author is introducing a pagination helper which related to dataset.

(weight 88.6kg BMI 31%)

Labels:

Wednesday, October 03, 2007

Try Zend Framework vol.5 "Always absolute url"

Zend Framework has some cute View Helpers.
One of them is Zend_View_Helper_Url which generates url easily.
But, it gives part of url. Sometimes I want to get absolute path. :<
Don't you want? :>

library/Hoge/View/Helper/Url.php
<?php
require_once 'Zend/View/Helper/Url.php';

class Hoge_View_Helper_Url extends Zend_View_Helper_Url
{

public function url(array $urlOptions = array(), $name = null, $reset = false)
{
$server = Zend_Controller_Front::getInstance()->getRequest()->getServer();
$url = parent::url($urlOptions, $name, $reset);

return strtolower(trim(array_shift(split('/', $server['SERVER_PROTOCOL'])))) . '://' . $server['HTTP_HOST'] . $url;
}

}


To use it. Nothing changes from default usage.

application/views/scripts/hoge/url.phtml

<?= $this->url() ?>


I strongly inspired by this entry. So I could write the code above. :p
(weight 86.8kg BMI 29%)

Labels:

Tuesday, September 25, 2007

Zend Framework 1.0.2 is released!!

Yahoooooooooooooooooooooooooooooooooooooo!!

Zend Framework 1.0.2 is released today! :p

And so happy to forget how fatty I am!!
(weight I don't kg BMI care%)

Labels:

Try Zend Framework vol.4 "Validate Date"

As you know, Zend Framework has a lot of fancy validators like Zend_Validte_Ccnum.
But, it comes to "Zend_Validate_Date", I have to be quiet, because it only allows "YYYY-mm-dd" format.
Date sometimes look like "YYYY/mm/dd" but the validator returns "false". :<

It's Okay. But it should be more flexible, isn't it?

Zend Framework has Date class which is pretty powerful to create date object, but it's also powerful as Date validator.
So I wrote a Date validator.

library/Hoge/Validate/Date.php
<php
require_once 'Zend/Validate/Abstract.php';
require_once 'Zend/Date.php';

class Hoge_Validate_Date extends Zend_Validate_Abstract
{

/**
* Invalid date error message.
*/
const INVALID = 'dateInvalid';

/**
* Validation failure message template.
*
* @var array
*/
protected $_messageTemplates = array(
self::INVALID => "'%value%' does not appear to be a valid date"
);

/**
* Defined by Zend_Validate_Interface
*
* Return if the date is correct.
*
* @param string $value
* @return boolean
*/
public function isValid($value)
{

$valueString = (string) $value;

$this->_setValue($valueString);

if (!Zend_Date::isDate($value)) {
$this->_error(self::INVALID);
return false;
}

return true;
}

}


To use it
application/controller/HogeController.php
<php
function dateValidateAction()
{
$date = '2004/02/29';

require 'Hoge/Validate/Date.php';
$validator = new Hoge_Validate_Date();
echo $validator->isValid($date);
exit;
}

Return "true".
It distinguish "/" and validate the Date correctly!! :p

(weight 86.4kg BMI 29%)

Labels:

Saturday, September 22, 2007

Try Zend Framework vol.3 "Extends Zend_View_Helper_Select"

Zend Framwrok has some useful helpers.
One of them is "Zend_View_Helper_Select".
Using it, you may set arguments like this.

controller/views/scripts/hoge/index.phtml
<?= $this->formSelect('hoge', null, null, array(''=>'', 'foo'=>'foo', 'bar'=>'bar')) ?>

You may not want to set blank as the default of options.

So, I extends Zend_View_Helper_FormSelect and add an argument to set default options.
library/Hoge/View/Helper/FormSelect.php
<php
require_once 'Zend/View/Helper/FormSelect.php';

class Hoge_View_Helper_FormSelect extends Zend_View_Helper_FormSelect
{
public function formSelect($name, $value = null, $attribs = null,
$options = null, $default_options = null, $listsep = "<br />\n")
{
$options = (is_array($default_options)) ? array_merge($default_options, $options) : $options;
return parent::formSelect($name, $value, $attribs, $options, $listsep);
}
}

It's just added argument for setting default options.
But, be careful it changes the number of arguments and changed order. :>

To use it.
<?= $this->formSelect('hoge', null, null, array('foo'=>'foo', 'bar'=>'bar'), array('' => '')) ?>

Simple right? :p
(weight 86.5kg BMI 30%)

Labels:

Tuesday, September 18, 2007

Try Zend Framework vol.2 "Debug Utils"

It's sometimes annoying to type "print_r" "get_class_methods".
"print_r" has 7 characters.
"get_class_methods" has 17 characters.
It just unbelievable!!
You might get hurt on your fingers.
And they just show the results as messy. It's not easy to read.

Zend Framework gives us a great class "Zend_Debug" for debugging.
It formats the result for you to read easily!

But, when you use the class you still need to type "Zend_Debug::dump()".
muu...18 characters. :<>
<php
require_once 'Zend/Debug.php';

function d($val)
{
Zend_Debug::dump($val);
}

function m($val)
{
Zend_Debug::dump(get_class_methods($val));
}


It's just simple functions but gives you time to make coffee instead of typing a lot of long letter functions. Isn't it? :p

(weight 85.4kg BMI 30%)

Labels:

Wednesday, July 25, 2007

Try Zend Framework vol.1 "Extends Zend_Db_Table"

I tryed Zend Framework!
It's so exciteing trying on a new staff. Isn't it?

I wrote a small class which enhance "Zend_Db_Table".
I need to write down before forgetting. :p
library/Hoge/Db/Table/Abstract.php
<?php
require_once 'Zend/Db/Table/Abstract.php';

abstract class Hoge_Db_Table_Abstract extends Zend_Db_Table_Abstract
{
protected function __call($name, $args)
{
$db = $this->getAdapter();

$parts = split('By', $name);
$how = $parts[0];
$conds = split('And', $parts[1]);

$where = array();
foreach($conds as $i => $cond) {
$cond = strtolower($cond);
$where[] = $db->quoteInto("{$cond}=?", $args[$i]);
}

switch ($how) {
case 'fetch' :
return $this->fetchAll($where);
break;

case 'find' :
return $this->fetchRow($where);
break;

default:
throw new Exception("Undefined method {$name} called. 'fetchByXXX' or 'findByXXX' is allowed to call.");
}
}
}


Extends the class from Hoge class like this.
library/Hoge/Db/Table/Hoge.php
<php
require_once 'Hoge/Db/Table/Abstract.php'

class Hoge extends Hoge_Db_Table_Abstract
{
protected $_name = 'hoges';
protected $_primary = 'id';

}


Use the extended class like this.
application/controllers/HogeController.php
<php
require_once 'Zend/Controller/Action.php';
require_once 'Hoge/Db/Table/Hoge.php';

class HogeController extends Zend_Controller_Action
{
function indexAction()
{
$code = '01';
$name = 'foo';

$hoge = new Hoge();
$res = $hoge->findByCodeAndName($code, $name);
}
}


Any way I gained a lot!
(weight 85.0kg BMI 29%)

Labels:

Sunday, July 01, 2007

Zend Framework 1.0 is released!!

Yahoooooooooooooooooooooooooooooooooooooo!!

Zend Framework 1.0 is released today! :p

And so happy to forget how fatty I am!!
(weight I don't kg BMI care%)

Labels: ,