From f03508bf3e2300bc36f0b9551d6e6b868f4de34d Mon Sep 17 00:00:00 2001 From: Daniel Carbone Date: Wed, 30 Sep 2015 15:12:35 -0500 Subject: [PATCH] First round of updates finished. - Needs better test cases. --- README.md | 73 +++++++++++++-- src/UglyQueue.php | 151 +++++++++++++++++++++--------- src/UglyQueueManager.php | 25 ++++- tests/UglyQueue/UglyQueueTest.php | 28 +++--- 4 files changed, 207 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 7861b22..b120180 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ugly-queue ========== -A simple file-based queue system for PHP 5.3.3+ +A simple file-based FIFO queue system for PHP 5.3.3+ Build statuses: - master: [![Build Status](https://travis-ci.org/dcarbone/ugly-queue.svg?branch=master)](https://travis-ci.org/dcarbone/ugly-queue) @@ -20,15 +20,74 @@ Once installed, you must first initialize an instance of [src/UglyQueueManager.p This is done as such: ```php -$config = array( - 'queue-base-dir' => 'path to where you would like queue files and directories to be stored' -); +$queueBaseDir = 'path to where you would like queue files and directories to be stored'; -$manager = new UglyQueueManager($config); +$manager = new UglyQueueManager($queueBaseDir); ``` Once initialized, you can start adding queues! ```php -$manager -``` \ No newline at end of file +$sandwichQueue = $manager->createQueue('sandwiches'); + +$sandwichQueue->lock(); + +$sandwichQueue->addItems(array( + 'bread', + 'meat', + 'cheese', + 'lettuce', + 'bread' +)); + +$sandwichQueue->unlock(); +``` + +Once you have items added to the queue, you can either pull items out ad-hoc or set up some sort of cron +or schedule task to process items regularly. + +If the base directory for all of your queues remains the same, each initialization +of `UglyQueueManager` will automatically find and initialize instances of pre-existing +UglyQueues. + +In a subsequent request, simply do the following: + +```php +$queueBaseDir = 'path to where you would like queue files and directories to be stored'; + +$manager = new UglyQueueManager($queueBaseDir); + +// 'tasty-sandwich' queue will exist now + +$tastySandwich = $manager->getQueue('sandwiches'); + +$tastySandwich->lock(); + +// Specify the number of items you wish to retrieve from the queue + +$items = $tastySandwich->retrieveItems(4); + +// $items is now an array... + +var_export($items); + +/* +array ( + 4 => 'bread', + 3 => 'lettuce', + 2 => 'cheese', + 1 => 'meat', +) +*/ + +``` + +The queue will then retain a single item, `0 => 'bread'` as the 5th item left in the queue. + +At any time you can determine how many items remain in a queue by executing `count($queueObj);` + +There are a few limitations currently: + +1. This lib is designed for small values without much in the way of formatting or line breaks +2. It is designed to be atomic, meaning that only one process can be adding / retrieving items from +a queue at a time. Reading actions (count, searching, etc) are NOT atomic, however. diff --git a/src/UglyQueue.php b/src/UglyQueue.php index d106982..12f0fb5 100644 --- a/src/UglyQueue.php +++ b/src/UglyQueue.php @@ -68,39 +68,7 @@ class UglyQueue implements \Serializable, \SplSubject, \Countable $this->queueTmpFile = sprintf('%s%squeue.tmp', $path, DIRECTORY_SEPARATOR); $this->serializeFile = sprintf('%s%sugly-queue.obj', $path, DIRECTORY_SEPARATOR); - if (is_readable($this->path) && is_writable($this->path)) - $this->mode = self::QUEUE_READWRITE; - else if (is_readable($this->path)) - $this->mode = self::QUEUE_READONLY; - - if (!file_exists($this->path.'/index.html')) - { - if ($this->mode === self::QUEUE_READONLY) - throw new \RuntimeException('Cannot initialize queue with name "'.$this->name.'", the user lacks permission to create files.'); - - $html = << - - 403 Forbidden - - -

Directory access is forbidden.

- - -HTML; - file_put_contents($this->path.'/index.html', $html); - } - - if (!file_exists($this->queueFile)) - { - if ($this->mode === self::QUEUE_READONLY) - throw new \RuntimeException('Cannot initialize queue with name "'.$this->name.'", the user lacks permission to create files.'); - - file_put_contents($this->queueFile, ''); - } - - $this->_notifyStatus = UglyQueueEnum::QUEUE_INITIALIZED; - $this->notify(); + $this->initialize(); } /** @@ -177,6 +145,14 @@ HTML; return $this->serializeFile; } + /** + * @return boolean + */ + public function isLocked() + { + return $this->locked; + } + /** * @param int $ttl Time to live in seconds * @throws \InvalidArgumentException @@ -190,10 +166,8 @@ HTML; if ($ttl < 1) throw new \InvalidArgumentException('Argument 1 expected to be greater than 0 "'.$ttl.'" seen'); - $alreadyLocked = $this->isLocked(); - // If there is currently no lock - if ($alreadyLocked === false) + if ($this->isAlreadyLocked() === false) return $this->createLockFile($ttl); // If we make it this far, there is already a lock in place. @@ -209,7 +183,7 @@ HTML; */ public function unlock() { - if ($this->locked === true) + if ($this->isLocked() === true) { unlink($this->lockFile); $this->locked = false; @@ -223,7 +197,7 @@ HTML; * @throws \RuntimeException * @return bool */ - public function isLocked() + public function isAlreadyLocked() { // First check for lock file if (is_file($this->lockFile)) @@ -300,7 +274,7 @@ HTML; if ($i++ >= $start_line) { list ($key, $value) = $line; - $data[$key] = $value; + $data = array($key => $value) + $data; } } @@ -349,7 +323,7 @@ HTML; throw new \RuntimeException('Cannot add item to queue "'.$this->name.'" as it is in read-only mode'); // If we don't have a lock, assume issue and move on. - if ($this->locked === false) + if ($this->isLocked() === false) throw new \RuntimeException('Cannot add item to queue "'.$this->name.'". Queue is already locked by another process'); if (!is_resource($this->tmpHandle)) @@ -368,6 +342,17 @@ HTML; ."\n"); } + /** + * @param array $items + */ + public function addItems(array $items) + { + foreach($items as $k=>$v) + { + $this->addItem($k, $v); + } + } + /** * If there is a tmp queue file, add it's contents to the beginning of a new queue file * @@ -441,7 +426,17 @@ HTML; */ public function serialize() { - return serialize(array($this->name, $this->path)); + return serialize( + array( + $this->baseDir, + $this->name, + $this->path, + $this->queueFile, + $this->queueTmpFile, + $this->lockFile, + $this->serializeFile, + ) + ); } /** @@ -454,10 +449,15 @@ HTML; */ public function unserialize($serialized) { - /** @var \DCarbone\UglyQueue $uglyQueue */ $data = unserialize($serialized); - $this->name = $data[0]; - $this->path = $data[1]; + $this->baseDir = $data[0]; + $this->name = $data[1]; + $this->path = $data[2]; + $this->queueFile = $data[3]; + $this->queueTmpFile = $data[4]; + $this->lockFile = $data[5]; + $this->serializeFile = $data[6]; + $this->initialize(); } /** @@ -504,6 +504,26 @@ HTML; } } + /** + * This method is mostly intended to check the "validity" of a re-initialized queue + * + * Could probably stand to be improved. + * + * @return bool + */ + public function _valid() + { + return ( + $this->baseDir !== null && + $this->name !== null && + $this->path !== null && + $this->queueFile !== null && + $this->queueTmpFile !== null && + $this->lockFile !== null && + $this->serializeFile !== null + ); + } + // -------- /** @@ -524,10 +544,51 @@ HTML; } $this->locked = true; - $this->_notifyStatus = UglyQueueEnum::QUEUE_LOCKED; $this->notify(); return true; } + + /** + * Post-construct initialization method. + * + * Also used post-un-serialization + */ + protected function initialize() + { + if (is_readable($this->path) && is_writable($this->path)) + $this->mode = self::QUEUE_READWRITE; + else if (is_readable($this->path)) + $this->mode = self::QUEUE_READONLY; + + if (!file_exists($this->path.'/index.html')) + { + if ($this->mode === self::QUEUE_READONLY) + throw new \RuntimeException('Cannot initialize queue with name "'.$this->name.'", the user lacks permission to create files.'); + + $html = << + + 403 Forbidden + + +

Directory access is forbidden.

+ + +HTML; + file_put_contents($this->path.'/index.html', $html); + } + + if (!file_exists($this->queueFile)) + { + if ($this->mode === self::QUEUE_READONLY) + throw new \RuntimeException('Cannot initialize queue with name "'.$this->name.'", the user lacks permission to create files.'); + + file_put_contents($this->queueFile, ''); + } + + $this->_notifyStatus = UglyQueueEnum::QUEUE_INITIALIZED; + $this->notify(); + } } \ No newline at end of file diff --git a/src/UglyQueueManager.php b/src/UglyQueueManager.php index df23d78..2296139 100644 --- a/src/UglyQueueManager.php +++ b/src/UglyQueueManager.php @@ -38,6 +38,22 @@ class UglyQueueManager implements \SplObserver, \Countable } } + /** + * @param string $name + * @return UglyQueue|UglyQueueManager + */ + public function getQueue($name) + { + if (isset($this->queues[$name])) + return $this->queues[$name]; + + $path = sprintf('%s/%s', $this->baseDir, $name); + if (file_exists($path)) + return $this->addQueueAtPath($path); + + return $this->createQueue($name); + } + /** * @param UglyQueue $uglyQueue * @return \DCarbone\UglyQueueManager @@ -61,7 +77,8 @@ class UglyQueueManager implements \SplObserver, \Countable */ public function createQueue($name) { - $this->addQueue(new UglyQueue($this->baseDir, $name, array($this))); + $queue = new UglyQueue($this->baseDir, $name, array($this)); + $this->addQueue($queue); return end($this->queues); } @@ -80,11 +97,15 @@ class UglyQueueManager implements \SplObserver, \Countable return null; $serializedFile = sprintf('%s/%s/ugly-queue.obj', $this->baseDir, $queueName); + /** @var \DCarbone\UglyQueue $uglyQueue */ if (file_exists($serializedFile)) $uglyQueue = unserialize(file_get_contents($serializedFile)); - else + + if (!isset($uglyQueue) || $uglyQueue->_valid() === false) $uglyQueue = new UglyQueue($this->baseDir, $queueName, array($this)); + $uglyQueue->attach($this); + return $this->addQueue($uglyQueue); } diff --git a/tests/UglyQueue/UglyQueueTest.php b/tests/UglyQueue/UglyQueueTest.php index 717a32b..e23bce6 100644 --- a/tests/UglyQueue/UglyQueueTest.php +++ b/tests/UglyQueue/UglyQueueTest.php @@ -209,14 +209,14 @@ class UglyQueueTest extends PHPUnit_Framework_TestCase } /** - * @covers \DCarbone\UglyQueue::isLocked + * @covers \DCarbone\isAlreadyLocked::isAlreadyLocked * @uses \DCarbone\UglyQueue * @depends testCanInitializeObjectWithValidParameters * @param \DCarbone\UglyQueue $uglyQueue */ public function testCanGetQueueLockedStatus(\DCarbone\UglyQueue $uglyQueue) { - $locked = $uglyQueue->isLocked(); + $locked = $uglyQueue->isAlreadyLocked(); $this->assertFalse($locked); } @@ -239,7 +239,6 @@ class UglyQueueTest extends PHPUnit_Framework_TestCase */ public function testCanInitializeExistingQueue() { - $uglyQueue = new \DCarbone\UglyQueue($this->baseDir, 'tasty-sandwich'); $this->assertInstanceOf('\\DCarbone\\UglyQueue', $uglyQueue); @@ -256,7 +255,6 @@ class UglyQueueTest extends PHPUnit_Framework_TestCase */ public function testExceptionThrownWhenPassingNonIntegerValueToLock(\DCarbone\UglyQueue $uglyQueue) { - $uglyQueue->lock('7 billion'); } @@ -269,13 +267,12 @@ class UglyQueueTest extends PHPUnit_Framework_TestCase */ public function testExceptionThrownWhenPassingNegativeIntegerValueToLock(\DCarbone\UglyQueue $uglyQueue) { - $uglyQueue->lock(-73); } /** * @covers \DCarbone\UglyQueue::lock - * @covers \DCarbone\UglyQueue::isLocked + * @covers \DCarbone\isAlreadyLocked::isAlreadyLocked * @covers \DCarbone\UglyQueue::createLockFile * @uses \DCarbone\UglyQueue * @depends testCanInitializeObjectWithValidParameters @@ -284,7 +281,6 @@ class UglyQueueTest extends PHPUnit_Framework_TestCase */ public function testCanLockUglyQueueWithDefaultTTL(\DCarbone\UglyQueue $uglyQueue) { - $locked = $uglyQueue->lock(); $this->assertTrue($locked); @@ -303,7 +299,7 @@ class UglyQueueTest extends PHPUnit_Framework_TestCase /** * @covers \DCarbone\UglyQueue::lock - * @covers \DCarbone\UglyQueue::isLocked + * @covers \DCarbone\isAlreadyLocked::isAlreadyLocked * @uses \DCarbone\UglyQueue * @depends testCanInitializeExistingQueue * @param \DCarbone\UglyQueue $uglyQueue @@ -317,7 +313,7 @@ class UglyQueueTest extends PHPUnit_Framework_TestCase } /** - * @covers \DCarbone\UglyQueue::isLocked + * @covers \DCarbone\isAlreadyLocked::isAlreadyLocked * @uses \DCarbone\UglyQueue * @depends testCanLockUglyQueueWithDefaultTTL * @param \DCarbone\UglyQueue $uglyQueue @@ -325,7 +321,7 @@ class UglyQueueTest extends PHPUnit_Framework_TestCase public function testIsLockedReturnsTrueAfterLocking(\DCarbone\UglyQueue $uglyQueue) { - $isLocked = $uglyQueue->isLocked(); + $isLocked = $uglyQueue->isAlreadyLocked(); $this->assertTrue($isLocked); } @@ -348,7 +344,7 @@ class UglyQueueTest extends PHPUnit_Framework_TestCase } /** - * @covers \DCarbone\UglyQueue::isLocked + * @covers \DCarbone\isAlreadyLocked::isAlreadyLocked * @uses \DCarbone\UglyQueue * @depends testCanUnlockLockedQueue * @param \DCarbone\UglyQueue $uglyQueue @@ -356,14 +352,14 @@ class UglyQueueTest extends PHPUnit_Framework_TestCase public function testIsLockedReturnsFalseAfterUnlockingQueue(\DCarbone\UglyQueue $uglyQueue) { - $isLocked = $uglyQueue->isLocked(); + $isLocked = $uglyQueue->isAlreadyLocked(); $this->assertFalse($isLocked); } /** * @covers \DCarbone\UglyQueue::lock - * @covers \DCarbone\UglyQueue::isLocked + * @covers \DCarbone\isAlreadyLocked::isAlreadyLocked * @uses \DCarbone\UglyQueue * @uses \DCarbone\Helpers\FileHelper * @depends testCanUnlockLockedQueue @@ -373,18 +369,18 @@ class UglyQueueTest extends PHPUnit_Framework_TestCase { $uglyQueue->lock(2); - $isLocked = $uglyQueue->isLocked(); + $isLocked = $uglyQueue->isAlreadyLocked(); $this->assertTrue($isLocked); sleep(3); - $isLocked = $uglyQueue->isLocked(); + $isLocked = $uglyQueue->isAlreadyLocked(); $this->assertFalse($isLocked); } /** * @covers \DCarbone\UglyQueue::lock - * @covers \DCarbone\UglyQueue::isLocked + * @covers \DCarbone\isAlreadyLocked::isAlreadyLocked * @uses \DCarbone\UglyQueue * @depends testCanUnlockLockedQueue * @param \DCarbone\UglyQueue $uglyQueue