View Source

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{zone-template-instance:ZFPROP:Proposal Zone Template}
{composition-setup}

{zone-data:component-name}
Zend_Commons
{zone-data}

{zone-data:proposer-list}
[Yegor Bugayenko|mailto:yegor@fazend.com]
{zone-data}

{zone-data:liaison}
TBD
{zone-data}

{zone-data:revision}
0.1 - 28 February 2011: Initial Draft.
{zone-data}

{zone-data:overview}
Zend_Commons is a collection of object-oriented strategies wrapping most common algorithms and operations. The biggest problem with existing PHP functions like {{fopen()}}, {{file_get_contents()}}, {{shell_exec()}}, etc. is that they are not object-oriented and are a huge legacy from PHP4. Zend_Commons resolves this problem by means of an extendable collection of classes designed in a Strategy pattern.

Moreover, this approach is very helpful during unit testing, when sensitive I/O, system, HTTP, etc. operations shall be properly isolated and mocked. When every operation is presented as an object retrieved from a factory - we can control their behavior to the very last detail.

I can't imagine right now all possible algorithms we may wrap in Zend_Commons. The idea is to create a factory of reusable components, which can be extended later by other contributors.
{zone-data}

{zone-data:references}
* [apache commons|http://commons.apache.org/]
{zone-data}

{zone-data:requirements}
* This component *will* implement algorithms and operations.
* This component *will* include a unified entry point.
* This component *will* allow customization.
{zone-data}

{zone-data:dependencies}
* Zend_Exception
* Zend_Log
* Zend_Loader_PluginLoader
{zone-data}

{zone-data:operation}
One of the strategies is instantiated via factory method, configured, and executed. Every strategy is a plugin, loaded with {{Zend_Loader_Plugin}}.
{zone-data}

{zone-data:milestones}
* Milestone 1: Design notes will be published
* Milestone 2: Working prototype checked into the incubator
* Milestone 3: Unit tests exist, work, and are checked into SVN.
* Milestone 4: Initial documentation exists.
{zone-data}

{zone-data:class-list}
* Zend_Commons
** Zend_Commons_Exception
** Zend_Commons_Abstract
** Zend_Commons_Listener
** Zend_Commons_Event
*** Zend_Commons_Io_Read
*** Zend_Commons_Io_Write
*** Zend_Commons_Io_Delete
*** Zend_Commons_Io_Mkdir
*** Zend_Commons_Io_Chmod
** Zend_Commons_Exec
** Zend_Commons_Email
** Zend_Commons_Http
* more later...
{zone-data}

{zone-data:use-cases}
{deck:id=Use Cases}
{card:label=UC-01: Zend_Commons}
{code}
// We want to make a directory and all necessary parent directories
// on top of it. We would like this operation to be properly
// logged. If the directory is actually created we would like to
// get a notification. In case of any problems we would like
// an exception to be thrown. Here it is:
Zend_Commons::factory('io_Mkdir')
->setDirName($directory)
->setCreateMissedParents(true)
->setVerbose(true)
->addLog($logger)
->addListener($listener)
->execute();

// The majority of configuration options can be configured
// in the factory class:
Zend_Commons::addLog($logger);
Zend_Commons::setVerbose(true);
Zend_Commons::addListener($listener);

// In unit tests we can easily replace every particular operation
// with our own implementation, or just disable it. For example:
Zend_Commons::factory('io_Write')
->setId('writing-config-file') // it's unique in application
->setFileName('/etc/passwd') // we don't want to touch it in tests
->setContent($content)
->execute();
// Later in unit test we mock the operation and override its
// parameters:
Zend_Commons::mock('io_Write', 'writing-config-file')
->setFileName('/tmp/passwd.txt');
{code}
{card}
{card:label=UC-02: Zend_Commons_Io}
{code}
// Instead of using file_get_contents() and validate its response
// for possible NULL value we may use this object approach. First, we
// instantiate an object of class Zend_Commons_Io_Read Then we
// configure the object via fluent interface and start the
// operation by means of execute() method. If something
// happens inside, the strategy throughs an exception.
$content = Zend_Commons::factory('io_Read')
->setFileName('some-file.txt')
->execute(); // exception here if any

// Moreover, we can add specific configuration parameter
// to the strategy, which will tailor its behavior to our
// particular needs, for example:
$content = Zend_Commons::factory('io_Read')
->setFileName('some-file.txt')
->setBufferLimit(50 * 1024) // limit memory usage up to 50K
->setVerbose(true) // protocol the operation to the log
->addListener(new Model_Io_Listener()) // send event to listener
->execute();

// Recursive directory deletion is a common problem, which
// is typically solved by custom functions. With Zend_Commons
// strategy object we can do it as simple as this:
Zend_Commons::factory('io_Delete')
->setName('/home/user/some/directory')
->setIgnoreIfAbsent(true) // don't panic if it's absent
->setVerbose(false) // don't protocol, act silently
->execute();
{code}
{card}
{card:label=UC-03: Zend_Commons_Exec}
{code}
// Execution of shell command can be wrapped in a strategy
// pattern class, in order to enable its easier configuration. In
// this example we instantiate Zend_Commons_Exec and configure it to
// work with '{ps} | {grep}' pattern. Then we add arguments to every
// segment in the pattern. Arguments are escaped by default, but we
// can disable this feature. Then we instruct the strategy to save
// stdout of the command to the file provided, and stderr to
// a variable. Then we ask the strategy to return a duration of
// execution, and fail with exception if response code is not
// equal to zero.
$duration = Zend_Commons::factory('exec')
->setPattern('{ps} | {grep}')
->useCommand('ps')
->addArg('ax', false) // don't quote this argument
->useCommand('grep')
->addArg('some text') // this arg will be escaped/quoted
->setStdoutFile($fileName)
->setStderrVariable($stderr)
->setReturnSubject(Zend_Commons_Exec::RETURN_DURATION) // in sec
->setFailOnNonZero(true) // fail if return code is non-zero
->execute();
{code}
{card}
{card:label=UC-03: Zend_Commons_Email}
{code}
// Sending of an email created by a template, where every
// email template looks similar to a PHTML view:
Zend_Commons::factory('email')
->setTemplate('welcome.email')
->inMail('setSubject', $name . ', your account is ready!')
->setParam('title', 'Mr.')
->setParam('name', 'John Doe')
->inMail('addAttachment', new Zend_Mime_Part(...))
->execute(); // send email

// Preliminary we can configure this strategy factory by
// means of static methods of Zend_Commons_Email, for example:
Zend_Commons_Email::setDefaultView(new Zend_View());
Zend_Commons_Email::setDefaultTransport(new Zend_Mail_Transport_Smtp(...));
Zend_Commons_Email::setDefaultFolder(APPLICATION_PATH . '/emails');
Zend_Commons_Email::setDefaultLocale(new Zend_Locale('fr'));

// All these static parameters can be re-configured in
// every particular strategy object, for example:
Zend_Commons::factory('email')
->setTemplate('welcome.email')
->setParam('name', 'John Doe')
->setLocale(new Zend_Locale('de'))
->setFolder(APPLICATION_PATH . '/new-emails')
->setTransport(new Zend_Mail_Transport_Sendmail(...))
->execute();
{code}
{card}
{card:label=UC-04: Custom plugins}
{code}
// We create a new class with a custom strategy, which is
// going to be used in many places inside our application:
class Model_Commons_Special extends Zend_Commons_Abstract {
public function execute() {
// do something special
}
}
// Somewhere inside bootstrap:
Zend_Commons::getPluginLoader()->addPrefixPath(
'Model_Commons',
APPLICATION_PATH . '/Model/Commons'
);
// Somewhere later in the code of the application:
Zend_Commons::factory('special')->execute();
{code}
{card}
{deck}
{zone-data}

{zone-data:skeletons}
{deck:id=Class Skeletons}
{card:label=Zend_Commons}
{code}
class Zend_Commons
{
/**
* Instantiates a new strategy.
* @param string The name of the strategy, like "io_ReadFile"
* @return Zend_Commons_Abstract
*/
public static function factory($name);

/**
* Shall our operations be verbose?
* @param boolean Shall they?
* @return void
*/
public static function setVerbose($verbose);

/**
* Add logger to be used in all operations.
* @param Zend_Log The logger to use
* @return void
*/
public static function addLog(Zend_Log $log);

/**
* Remove logger previously added.
* @param Zend_Log The logger to remove
* @return void
*/
public static function removeLog(Zend_Log $log);

/**
* Add new listener.
* @param Zend_Commons_Listener The listener
* @return void
*/
public static function addListener(Zend_Commons_Listener $lstnr);

/**
* Remove listener added before.
* @param Zend_Commons_Listener The listener to remove
* @return void
*/
public static function removeLog(Zend_Commons_Listener $lstnr);

/**
* We can mock any other operation.
* @param string Class name
* @param string Unique ID of the operation
* @return Zend_Commons_Abstract
*/
public static function mock($cls, $id);

/**
* Return an instance of plugin loader used here.
* @return Zend_Loader_PluginLoader
*/
public static function getPluginLoader();
}

abstract class Zend_Commons_Abstract
{
/**
* Execute the operation.
* @return mixed
*/
public abstract function execute();

/**
* Shall the operation be verbose?
* @param boolean Shall it?
* @return Zend_Commons_Abstract
*/
public function setVerbose($verbose);

/**
* Add logger to be used in all operations.
* @param Zend_Log The logger to use
* @return Zend_Commons_Abstract
*/
public function addLog(Zend_Log $log);

/**
* Remove logger previously added.
* @param Zend_Log The logger to remove
* @return Zend_Commons_Abstract
*/
public function removeLog(Zend_Log $log);

/**
* Add new listener.
* @param Zend_Commons_Listener The listener
* @return Zend_Commons_Abstract
*/
public function addListener(Zend_Commons_Listener $lstnr);

/**
* Remove listener added before.
* @param Zend_Commons_Listener The listener to remove
* @return Zend_Commons_Abstract
*/
public function removeLog(Zend_Commons_Listener $lstnr);
}
interface Zend_Commons_Listener
{
/**
* Listen to the event passed from operation.
* @return void
*/
function listen(Zend_Commons_Event $event);
}
class Zend_Commons_Event
{
}
{code}
{card}
{card:label=Zend_Commons_Io}
{code}
/**
* Read file content to a PHP variable.
*/
class Zend_Commons_Io_Read extends Zend_Commons_Abstract
{
/**
* Set the name of file to read.
* @param string The name
* @return $this
*/
public function setFileName($name);

/**
* Read the file and return its content.
* @return string
*/
public function execute();
}

/**
* Write content to a file.
*/
class Zend_Commons_Io_Write extends Zend_Commons_Abstract
{
/**
* Set the name of file to write.
* @param string The name
* @return $this
*/
public function setFileName($name);

/**
* Set content to write.
* @param string The content
* @return $this
*/
public function setContent($content);

/**
* Write the file.
* @return string
*/
public function execute();
}

/**
* Delete file or directory.
*/
class Zend_Commons_Io_Delete extends Zend_Commons_Io_Abstract
{
/**
* Set the name of file/directory to delete.
* @param string The name
* @return $this
*/
public function setName($name);

/**
* Execute the operation, delete the file or directory.
* @return void
*/
public function execute();
}

/**
* Make directory.
*/
class Zend_Commons_Io_Mkdir extends Zend_Commons_Abstract
{
/**
* Set the name of directory to create.
* @param string The name of directory to create
* @return $this
*/
public function setDirectory($dir);

/**
* Execute the operation, create the directory.
* @return void
*/
public function execute();
}
{code}
{card}
{card:label=Zend_Commons_Exec}
{code}
/**
* Execute shell command.
*/
class Zend_Commons_Exec extends Zend_Commons_Abstract
{
/**
* Set shell command pattern.
* @param string The name
* @return $this
*/
public function setPattern($pattern);

/**
* Use this particular command for further instructions.
* @param string The name of the command
* @return $this
*/
public function useCommand($command);

/**
* Add argument to the current active command.
* @param string The argument
* @param boolean Shall we quote it?
* @return $this
*/
public function addArg($arg, $quote = true);

/**
* Save stdout to the file provided.
* @param string The name of the file
* @return $this
*/
public function setStdoutFile($file);

/**
* Save stdout to the variable provided.
* @param mixed The variable
* @return $this
*/
public function setStdoutVariable(&$var);

/**
* Save stderr to the file provided.
* @param string The name of the file
* @return $this
*/
public function setStderrFile($file);

/**
* Save stderr to the variable provided.
* @param mixed The variable
* @return $this
*/
public function setStderrVariable(&$var);

/**
* What to return as a result of execute()?
* @param integer What shall we return
* @return $this
*/
public function setReturnSubject($subj);

/**
* Shall we fail if return code is NON zero?
* @param boolean Shall we?
* @return $this
*/
public function setFailOnNonZero($fail);

/**
* Execute the command.
* @return mixed
*/
public function execute();
}
{code}
{card}
{card:label=Zend_Commons_Email}
{code}
/**
* Send email.
*/
class Zend_Commons_Email extends Zend_Commons_Abstract
{
/**
* Set default locale.
* @param Zend_Locale The locale
*/
public static function setDefaultLocale(Zend_Locale $locale);

/**
* Set default location of email templates.
* @param string The directory
*/
public static function setDefaultFolder($folder);

/**
* Set default mail transport.
* @param Zend_Mail_Transport_Abstract The transport to use
*/
public static function setDefaultTransport(Zend_Mail_Transport_Abstract $transport);

/**
* Set default view to use for rendering.
* @param Zend_View The view
*/
public static function setDefaultView(Zend_View $view);

/**
* Set instance-specific locale.
* @param Zend_Locale The locale
* @return $this
*/
public function setLocale(Zend_Locale $locale);

/**
* Set instance-specific location of email templates.
* @param string The directory
* @return $this
*/
public function setFolder($folder);

/**
* Set instance-specific mail transport.
* @param Zend_Mail_Transport_Abstract The transport to use
* @return $this
*/
public function setTransport(Zend_Mail_Transport_Abstract $transport);

/**
* Set instance-specific view to use for rendering.
* @param Zend_View The view
* @return $this
*/
public function setView(Zend_View $view);

/**
* Set certain parameter available in view/template.
* @param string The name of the parameter
* @param mixed The value
* @return $this
*/
public function setParam($name, $value);

/**
* Call method of the underlying Zend_Mail object.
* @param string The method
* @return $this
*/
public function inMail($method /* arguments */);

/**
* Execute the command, send the email.
* @return mixed
*/
public function execute();
}
{code}
{card}
{deck}
{zone-data}

{zone-template-instance}]]></ac:plain-text-body></ac:macro>