UltraGlobals :)

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

UltraGlobals :)

Post by VladSun »

Code: Select all

<?php
namespace GlobalVariables;

require_once 'SharedMemoryStore.php';

/**
 * Description of GlobalVariablesBoard
 *
 * @author vladsun
 */

class Board implements \IteratorAggregate
{
	const	DEFAULT_KEY	= 0x1024;
	
	private $_memoryStore;
	private $_persistanceStore;
	private $_key;
	
	public function __construct($persistanceStore, $key = null)
	{
		$this->_persistanceStore = $persistanceStore;
		$this->_key = $key ? $key : self::DEFAULT_KEY;

		$this->_memoryStore = $this->createMemoryStore();

		if (!$this->_memoryStore->isKeyValid($this->_key))
		{
//			$data = $this->_persistanceStore->load($this->_key);
//
//			if (!$data)
//			{
//				$data = $this->_persistanceStore->create($this->_key);
//			}
//
//			$this->_memoryStore->create($this->_key, $data);
			$this->_memoryStore->create($this->_key, array());
		}
		else
		{
			$this->_memoryStore->load($this->_key);
//			$this->_persistanceStore->store($this->_key, $this->_memoryStore->getData());
		}
	}

	public function getIterator()
	{
		return new \ArrayIterator($this->_memoryStore->getData());
	}

	protected function createMemoryStore()
	{
		return new \GlobalVariables\SharedMemoryStore();
	}

	public function __get($variable)
	{
		return $this->_memoryStore->getVariable($variable);
	}

	public function __set($variable, $value)
	{
		$this->_memoryStore->setVariable($variable, $value);
	}

	public function __unset($variable)
	{
		$this->_memoryStore->unsetVariable($variable);
	}

	public function __isset($variable)
	{
		return $this->_memoryStore->isSetVariable($variable);
	}

}

Code: Select all

<?php
namespace GlobalVariables;

abstract class AbstractMemoryStore
{
	abstract public function create($key, $data);
	abstract public function store($key, $data);
	abstract public function load($key);
	abstract public function isKeyValid($key);

	abstract public function getData();
	abstract public function getVariable($variable);
	abstract public function setVariable($variable, $value);
	abstract public function isSetVariable($variable);
	abstract public function unsetVariable($variable);
}

Code: Select all

<?php
namespace GlobalVariables;

require_once 'AbstractMemoryStore.php';

class SharedMemoryStore extends AbstractMemoryStore
{
	protected $initialMemorySize;
	protected $incrementMemorySize;
	protected $permitions;
	protected $key;
	protected $data;

	protected $shmID = null;

	public function __construct($initialMemorySize = 10240, $incrementMemorySize = 10240, $permitions = 0x666)
	{
		$this->initialMemorySize = $initialMemorySize;
		$this->incrementMemorySize = $incrementMemorySize;
		$this->permitions = $permitions;
	}

	public function getData()
	{
		$this->attach();
		$header = $this->getHeader();
		return $header['data'];
	}


	public function getVariable($variable)
	{
		$this->attach();
		$header = $this->getHeader();

		if (isset($header['data'][$variable]))
		{
			return $header['data'][$variable];
		}
		else
		{
			trigger_error('Variable '.$variable.' is undefined.');
			return null;
		}
	}

	public function setVariable($variable, $value)
	{
		$this->attach();
		$header = $this->getHeader();
		$header['data'][$variable] = $value;
		$this->writeHeader($header);
	}

	public function isSetVariable($variable)
	{
		$this->attach();
		$header = $this->getHeader();
		return isset($header['data'][$variable]);
	}

	public function unsetVariable($variable)
	{
		$this->attach();
		$header = $this->getHeader();
		unset($header['data'][$variable]);
		$this->writeHeader($header);
	}

	public function isKeyValid($key)
	{
		$this->attach($key);
		if (shm_has_var($this->shmID, 1))
		{
			$this->detach();
			return true;
		}
		$this->detach();
		return false;
	}

	public function create($key, $data)
	{
		$this->data = $data;
		$this->key = $key;

		$this->attach();

		$header = array
		(
			'initialMemorySize'	=> $this->initialMemorySize,
			'incrementMemorySize'	=> $this->incrementMemorySize,
			'permitions'		=> $this->permitions,
			'data'			=> $this->data,
		);

		$this->writeHeader($header);
	}

	public function store($key, $data)
	{
		$this->attach();

		$header = $this->getHeader();
		unset($header->data[$variable]);
		$this->writeHeader($header);
	}

	public function load($key)
	{
		$this->attach($key);
	}

	protected function getHeader()
	{
		$this->attach();
		
		if (shm_has_var($this->shmID, 1))
			return shm_get_var($this->shmID, 1);

		throw new HeaderNotFound();
	}

	protected function writeHeader($header)
	{
		$this->attach();

		shm_put_var($this->shmID, 1, $header);
	}


	protected function detach()
	{
		if ($this->shmID)
		{
			shm_detach($this->shmID);
			$this->shmID = null;
		}
	}

	protected function attach($key = null)
	{
		if (!$key)
		{
			$key = $this->key;
		}
		else
		{
			$this->key = $key;
		}

		if (!$this->shmID)
		{
			$this->shmID = shm_attach($this->key, $this->initialMemorySize, $this->permitions);
			if (!$this->shmID)
			{
				throw new SharedMemoryNotFound();
			}
		}
	}

}


class HeaderNotFound extends \Exception {}
class SharedMemoryNotFound extends \Exception {}
index1.php

Code: Select all

<?php

require_once 'Board.php';

define('APP_STORE_KEY', 0x4096);

$global = new \GlobalVariables\Board(null, APP_STORE_KEY);

$global->v1 = 10;
$global->v2 = 20;

sleep(10);

$global->v3 = array(30);
index2.php

Code: Select all

<?php

require_once 'Board.php';

define('APP_STORE_KEY', 0x4096);

$global = new \GlobalVariables\Board(null, APP_STORE_KEY);

foreach ($global as $key => $value)
	echo "$key = ".  var_export($value, true)." <br/>";

unset($global->v3);
echo $global->v3;

if (isset($global->v2))
	echo 'Variable "v2" is SET';
All of this should implement memory based data store. It doesn't depend on sessions. Changes are written imidiately. It can be used as a some kind of memory cache (that's why I write it :) ).

There are obvious TODOs :)
There are 10 types of people in this world, those who understand binary and those who don't
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: UltraGlobals :)

Post by josh »

I'm going to be hard on ya ;-)

Does 'AbstractMemoryStore' ship with Cake Framework? Why is that class not shown?

Commented out code it a 'smell'. (SVN or GIT will get the code back if you delete and want it back, I promise).

protected methods are also a smell. I used to in favor of them, but now I am not. If they are not part of the API or at a different level of abstraction, I try to move them to a third class. Making them protected prevents your system from being used in new contexts. If I want to call that protected method I ought to be able to do so, even if it wasn't designed that way. Putting the methods that are currently protected onto a base class reduces the chances I will use the main class wrongly, while allowing me to use those 'behind the scenes' methods as I become more familiar with your code, for example.

Your code does work in the constructor, which is an attribute of hard to test code. Simply instantiating your class requires that A) I have a working shared memory store, B) I can write to it, etc.. This creates unnecessary stubbing, and setup/teardown logic to test a component. http://googletesting.blogspot.com/2008/ ... -code.html

persistance is spelled persistence. Spelling things wrong is a honest mistake but introduces bugs if someone else doesn't know it was spelled wrong.

Just what exactly is the 'persistence store', how does it differ from a 'memory store' and why is one not included? This is one area where tests would have answered this question for me. With unit tests I'd see all your supported use cases. YOu have only shown 2 use cases, both of which just pass null for the persistence store. If a persistence store is just left over, 'dead' code - it should be removed. Use of code coverage tools would help in locating dead code, which is never executed under test. Often times this comes in the form of extra parameters that do nothing, lines of code that are not hit, return values that are not used for anything, etc.. The amount of time we spend reading code far exceeds the amount of time writing code. By having code that provides no business value but consumes time is analogous to a retail store paying rent for empty shelves, and trying to stay in business.
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: UltraGlobals :)

Post by VladSun »

josh wrote:I'm going to be hard on ya ;-)

Does 'AbstractMemoryStore' ship with Cake Framework? Why is that class not shown?
It's in my post.
josh wrote:Commented out code it a 'smell'. (SVN or GIT will get the code back if you delete and want it back, I promise).
I am still working on the persistence store, but I thought it would be nice to have some critique before I move on.
josh wrote:Your code does work in the constructor, which is an attribute of hard to test code. Simply instantiating your class requires that A) I have a working shared memory store, B) I can write to it, etc.. This creates unnecessary stubbing, and setup/teardown logic to test a component. http://googletesting.blogspot.com/2008/ ... -code.html
While the persistence store is likely to be changed (SQL, flat file, etc.), the memory store is not so likely to be changed - though I have created the createMemoryStore() method in order to have the ability to change it. I can see your point.
josh wrote:persistance is spelled persistence. Spelling things wrong is a honest mistake but introduces bugs if someone else doesn't know it was spelled wrong.
Thanks.
josh wrote:Just what exactly is the 'persistence store', how does it differ from a 'memory store' and why is one not included? This is one area where tests would have answered this question for me. With unit tests I'd see all your supported use cases. YOu have only shown 2 use cases, both of which just pass null for the persistence store. If a persistence store is just left over, 'dead' code - it should be removed. Use of code coverage tools would help in locating dead code, which is never executed under test. Often times this comes in the form of extra parameters that do nothing, lines of code that are not hit, return values that are not used for anything, etc.. The amount of time we spend reading code far exceeds the amount of time writing code. By having code that provides no business value but consumes time is analogous to a retail store paying rent for empty shelves, and trying to stay in business.
The main idea is to have a "fast" but "unreliable" store and a "slow" and "reliable" store. Data from the "slow" store is loaded into the "fast" store. Also this data should be readable/writable by all instances of a particular application PHP scripts at the same time.

A typical and simple example would be a "page request counter".
There are 10 types of people in this world, those who understand binary and those who don't
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: UltraGlobals :)

Post by josh »

VladSun wrote:It's in my post.
Reminds me of an interface. That should probably be an interface.
josh wrote:the memory store is not so likely to be changed
Sure it is, I might want to disable for test or production Redirect it to tmpfs if system load gets too high, etc..
The main idea is to have a "fast" but "unreliable" store and a "slow" and "reliable" store.
Take a look at Zend_Cache_Backend_TwoLevels
This (extend) backend is an hybrid one. It stores cache records in two other backends : a fast one (but limited) like Apc, Memcache... and a "slow" one like File, Sqlite...

This backend will use the priority parameter (given at the frontend level when storing a record) and the remaining space in the fast backend to optimize the usage of these two backends.

Available options include:
TwoLevels Backend Options Option Data Type Default Value Description
auto_refresh_fast_cache Boolean TRUE if TRUE, auto refresh the fast cache when a cache record is hit
stats_update_factor Integer 10 disable / tune the computation of the fast backend filling percentage (when saving a record into cache, computation of the fast backend filling percentage randomly 1 times on x cache writes)
http://framework.zend.com/manual/en/zen ... kends.html

I like their architecture better. In your class, even at the code level memory & persistence are different concepts. In Zend_Cache you just pass in an array of "backends" which can be memory OR persistence. I like the array better, then you're not passing null everywhere, leaving me wondering what those parameters on the constructor are for ;-)
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: UltraGlobals :)

Post by VladSun »

I think I should explain my self about the purpose of this.

Imagine it's just like a $_SESSION - i.e it has board_start (=== session_start (in fact it's the creation of an instance of Board) ), $_BOARD (=== $_SESSION), etc. But... it's not per user - its global. The reason I needed persistence store was just to initialized $_BOARD at "board_start()" in case I had loosen its data (i.e. Apache restart or something like that). Indeed I don't need write-back to persistence store in my case.

As I was building up my classes I thought it would be good to have much more generalized architecture, but as far as I can see it has loosen its target while trying to do this :(
There are 10 types of people in this world, those who understand binary and those who don't
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: UltraGlobals :)

Post by josh »

There is no such thing as 'true global'. What if you wanted two boards to run? One feature Zend_Cache provides is 'cache tags'. You can filter your potential cache hits to ones that match a given tag, and you can garbage collect all records matching a given tag (I don't know if this is supported by memcache?). It would be a cool feature though. if you wan't to run two boards, you might not want state from one acting upon state from the other. Or if you find out two applications are using your script, and both use $a for a variable name, you don't want them interfering.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: UltraGlobals :)

Post by Weirdan »

josh wrote:You can filter your potential cache hits to ones that match a given tag, and you can garbage collect all records matching a given tag (I don't know if this is supported by memcache?).
It's not supported by memcache and not emulated by Zend's memcache wrappers.
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: UltraGlobals :)

Post by VladSun »

josh wrote:There is no such thing as 'true global'. What if you wanted two boards to run? ... if you wan't to run two boards, you might not want state from one acting upon state from the other. Or if you find out two applications are using your script, and both use $a for a variable name, you don't want them interfering.
That's what the purpose of APP_STORE_KEY

Code: Select all

$global = new \GlobalVariables\Board(null, APP_STORE_KEY);
Two applications would use two different "global" spaces by assigning two different APP_STORE_KEYs (usually generated by using the ftok() function).
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Re: UltraGlobals :)

Post by Jenk »

Surely this feature is easily provided by a settings file.
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: UltraGlobals :)

Post by VladSun »

Jenk wrote:Surely this feature is easily provided by a settings file.
My implementation permits changing "setting" values and accessing these new values at "true" runtime.
Also it's much faster because memory is used.
There are 10 types of people in this world, those who understand binary and those who don't
Post Reply