Page 1 of 1

State pattern???

Posted: Wed Dec 15, 2010 10:20 am
by VladSun
I'm using a State-pattern-like pattern :) and I need your comments on it.

I'm developing a CLI code generator framework.

So ... I have a Plugin_Factory that creates an object. The object created class inherits the Plugin_Abstract class. So they have similar API. The main loop should execute plugin methods like this:

Code: Select all

$pluginFactory = new Plugin_Factory();

// +++LOOP
$this->plugin = $pluginFactory($this->optionProcessor->import);
$this->plugin->provideBootstrap($this->bootstrap);
$this->plugin->provideOptions($this->optionProcessor);
$this->plugin->init();
$this->plugin->run();
But some of the plugins should change their behavior depending of the CLI options (and related). Such an example is a plugin that reads an XML file (MySQLWorkbench file), reads the MySQLWorkbench version from it and provides a subplugin that can handle this version. So, I changed the above loop like this:

Code: Select all

$pluginFactory = new Plugin_Factory();

$this->plugin = $pluginFactory($this->optionProcessor->import);
$this->plugin = $this->plugin->getInstance();

$this->plugin->provideBootstrap($this->bootstrap);
$this->plugin = $this->plugin->getInstance();

$this->plugin->provideOptions($this->optionProcessor);
$this->plugin = $this->plugin->getInstance();

$this->plugin->init();
$this->plugin = $this->plugin->getInstance();

$this->plugin->run();
and the Plugin_Abstract code:

Code: Select all


	/**
	 * @var Plugin_Abstract
	 */
	protected $self;

	public function __construct()
	{
		$this->self = $this;
	}

	public function getInstance()
	{
		$this->self->optionProcessor = $this->optionProcessor;
		$this->self->bootstrap = $this->bootstrap;
		$this->self->self = $this->self;
		
		return $this->self;
	}
So, the MySsqlWorkbench Importer Plugin becomes:

Code: Select all

...

	public function provideOptions(Application_Options_Abstract $optionProcessor)
	{
		parent::provideOptions($optionProcessor);

		$optionProcessor->addOptions(array
		(
			'file=s'		=> 'MySQLWorkbench file [required]',
			'encoding=s'	=> 'File encoding',
		));

		if (empty($optionProcessor->file))
		{
			throw new \Exception('--file option is required.');
		}

		if (empty($optionProcessor->encoding))
		{
			$optionProcessor->encoding = 'utf-8';
		}

		$this->versionReader = new Importer_MySqlWorkbench_VersionReader
		(
			new Importer_MySqlWorkbench_Loader($optionProcessor->file), 
			$optionProcessor->encoding
		);

		$factory = new Importer_MySqlWorkbench_Factory();
		$this->self = $factory->create($this->versionReader);
	}
I'm planing to use such strategy for providing help messages (i.e. when the --help option is provided). Although I feel the AbstractFactory pattern would be a better solution...
I'm using the same strategy to read options from a config file instead of the CLI (except the --config option).

Re: State pattern???

Posted: Wed Dec 15, 2010 12:15 pm
by Darhazer
IMO it should be:

Code: Select all

$this->plugin->setState($this->bootstrap);
and the return part is unnecessary.
Remember, state is implemented much like strategy, but the purpose is different, and the class that implements state pattern instantiate the state implementation instead of being provided with an instance.

btw, what is the purpose of $this->self->self = $this->self; ?

Re: State pattern???

Posted: Wed Dec 15, 2010 12:21 pm
by VladSun
Darhazer wrote:IMO it should be:

Code: Select all

$this->plugin->setState($this->bootstrap);
and the return part is unnecessary.
Hm, I can achive this with magic methods while still have the self object instance changed.
Darhazer wrote:Remember, state is implemented much like strategy, but the purpose is different, and the class that implements state pattern instantiate the state implementation instead of being provided with an instance.
Well, I said it was State-like pattern :) IMO, it's a mixture of State, Strategy and Proxy (etc. :twisted: ) patterns :)
Darhazer wrote:btw, what is the purpose of $this->self->self = $this->self; ?
Probably useless :) I've just copied&initialized all properties from $this class to $self object:) But the $self/this->self property is initialized in the constructor ... so you're right - no purpose !

Thanx :)

Re: State pattern???

Posted: Wed Dec 15, 2010 12:32 pm
by VladSun
I guess the Main Application object illustrates it in detail (the provideOptions method):

Code: Select all

<?php
namespace CryoGen\Core;
/**
 * Description of Main
 *
 * @author vladsun
 */
require_once 'Bootstrap.php';

class Application_Main // de facto implements Plugin_Abstract
{
	/**
	 * @var Application_Bootstrap
	 */
	public $bootstraper;

	/**
	 * @var Application_Options_CLI
	 */
	public $optionProcessor;

	protected $self;

	public function __construct()
	{
		$this->bootstraper = new Application_Bootstrap();
		$this->optionProcessor = new Application_Options_CLI();
		$this->self = $this;
	}

	public function getInstance()
	{
		$this->self->optionProcessor = $this->optionProcessor;
		$this->self->bootstrap = $this->bootstrap;
		$this->self->self = $this->self;

		return $this->self;
	}

	public function provideBootstrap(Application_Bootstrap $bootstrap)
	{
		$bootstrap->addClassPath(realpath(APPLICATION_PATH));
	}

	public function provideOpions(Application_Options_Abstract $optionProcessor)
	{
		$optionProcessor->addOptions(array
		(
			'command=s'		=> 'Command to be executed [required]',
			'config-s'		=> 'Config file to be parsed',
			'help'			=> 'Help -- usage message',
		));

		if (empty($optionProcessor->command))
			throw new \Exception ('--command can not be empty');

		if (!empty($optionProcessor->config))
		{
			$this->optionProcessor = new Application_Options_Config
			(
				$optionProcessor->config, 
				$optionProcessor->command
			);
			$this->optionProcessor->beginProcessing();
		}

		if ($this->optionProcessor->help)
		{
			$this->self = new Application_Help_Main();
		}
	}

	public function init()
	{
		$this->provideBootstrap($this->bootstraper);
		$this->optionProcessor->beginProcessing();
		$this->provideOpions($this->optionProcessor);
	}

	public function run()
	{
		$commandFactory = new Application_Command_Factory();
		$command = $commandFactory($this->optionProcessor->command);

		$command->provideBootstrap($this->bootstraper);
		$command->provideOptions($this->optionProcessor);

		$command->init();

		$this->optionProcessor->endProcessing();

		$command->run();
	}
}
The executable:

Code: Select all

#!/usr/bin/php
<?php
namespace CryoGen;

defined('APPLICATION_PATH') || define('APPLICATION_PATH', realpath(dirname(__FILE__)));
require_once 'CryoGen/Core/Application/Main.php';

$application = new \CryoGen\Core\Application_Main();

$application->getInstance()->init();
$application->getInstance()->run();

exit(0);
Bootstrap and Option_Prosessor use the according Zend classes.
begin_processing()/stop_processing() are extension methods of Zend_Console_Getopt - whether an "unrecognized option" Exception should be thrown or option parsing continues.

I didn't extend the Plugin_abstract because I dont have a class autoloder yet (it's in the Bootstrap which knows how to autoload Zend classes only)

Re: State pattern???

Posted: Wed Dec 15, 2010 12:59 pm
by VladSun
Ha, ha
I'm going to name this pattern the "Chameleon pattern" :D :D :D

Should look like this:

Code: Select all

/**
 * Description of Chameleon
 *
 * @author vladsun
 */
class Chameleon
{
	protected $___self = null;
	protected $___env  = null;

	public function __construct()
	{
		$this->___self = $this;
	}

	public function setEnviroment($enviroment)
	{
		$this->___env  = $enviroment;
		$this->___self = Chameleon_Factory::create($this, $enviroment);
	}

	public function __call($method, $args)
	{
		return call_user_func_array(array($this->___self, $method), $args);
	}

	public function __get($property)
	{
		return $this->___self->{$property};
	}

	public function __set($property, $value)
	{
		$this->___self->{$property} = $value;
	}
}
WOW, a lot of dashes here :)

Re: State pattern???

Posted: Wed Dec 15, 2010 1:29 pm
by Darhazer
Vlad, stop drinking, come back tomorrow and we'll discuss your solution :D

just joking, as right now I'm too tired to understand what you are talking about