merged branch pulzarraider/memcache_profiler_storage (PR #2766)

Commits
-------

7474293 memcache profiler storage support added

Discussion
----------

[HttpKernel] [FrameworkBundle] Memcache(d) Profiler Storage added

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
Fixes the following tickets: -
Todo: -

There are 2 memcache PHP extensions: Memcache and MemcacheD (with "D" at the end) - both are supported.

How to use Memcache Profiler Storage (Memcache php extension is used):
change (or add if there isn't) "dsn" in framework/profiler section in config_dev.yml

```
...
framework:
    ...
    profiler:
        ...
        dsn: memcache://127.0.0.1/11211
...
```

How to use Memcached Profiler Storage (MemcacheD php extension is used):
change "dsn" in framework/profiler section in config_dev.yml

```
...
framework:
    ...
    profiler:
        ...
        dsn: memcached://127.0.0.1/11211
...
```

Last changes:
- memcached support addedd
- optimized performance (serialization done in extension, index is created with ```append``` function)
- updated to last version of Profiler (find by method, avoid duplications)
- done squash on commits

---------------------------------------------------------------------------

by stloyd at 2011-12-01T23:36:02Z

You need to add check for index name size, AFAIK memcache will fail if key is longer than 250 characters.

Also please do an `squash` for all those commits.

---------------------------------------------------------------------------

by pulzarraider at 2011-12-02T00:15:28Z

@stloyd Thanks. I will add the check for key length.

I am just starting with git. Could you please add some tutorial about squash to a documentation page: http://symfony.com/doc/2.0/contributing/code/patches.html ? It will help me (and maybe some others) to do it correct way.

---------------------------------------------------------------------------

by stof at 2011-12-02T00:19:01Z

http://help.github.com/rebase/

---------------------------------------------------------------------------

by pulzarraider at 2011-12-03T18:56:11Z

Thanks @stof, rebase done.

---------------------------------------------------------------------------

by dlsniper at 2011-12-11T14:00:17Z

Hi,

Would it be possible to either use Memcached instead of Memcache or make it configurable to use either Memcache or Memcached?
I've did a little digging on the benefits of using Memcached over Memcache (like for example: http://stackoverflow.com/questions/1442411/using-memcache-vs-memcached-with-php http://devzone.zend.com/1869/zendcon-sessions-episode-040-memcached-the-better-memcache-interface/ ) and maybe this will also help in not having two extensions installed for people who are using Memcached already.

Regards.

---------------------------------------------------------------------------

by pulzarraider at 2011-12-11T16:15:58Z

@dlsniper  thanks for great comment. I will add memcached support.

---------------------------------------------------------------------------

by stof at 2011-12-12T20:49:00Z

@pulzarraider what is the status of this PR ? Is it still a WIP ?

---------------------------------------------------------------------------

by pulzarraider at 2011-12-12T22:58:48Z

@stof Yes, it's still WIP. I'm working on a memcached (with D at the end) support. It will be finished in the next few days.

---------------------------------------------------------------------------

by dlsniper at 2011-12-15T12:51:52Z

@pulzarraider if I can help you with the PR let me know.

---------------------------------------------------------------------------

by pulzarraider at 2012-01-08T20:22:24Z

@dlsniper @stof I've finally added memcached support and done some optimizations. Memcache(d) profiler storage is now ready.

---------------------------------------------------------------------------

by dlsniper at 2012-01-08T22:12:29Z

I'm glad you finished this @pulzarraider
Thanks! for your hard work!

+1 for this PR

@stof, @fabpot is it good to go on master?

---------------------------------------------------------------------------

by pulzarraider at 2012-01-28T19:45:56Z

@stof, @fabpot ping
This commit is contained in:
Fabien Potencier 2012-02-12 13:26:06 +01:00
commit e986b9b7e5
6 changed files with 849 additions and 4 deletions

View File

@ -202,10 +202,12 @@ class FrameworkExtension extends Extension
// Choose storage class based on the DSN
$supported = array(
'sqlite' => 'Symfony\Component\HttpKernel\Profiler\SqliteProfilerStorage',
'mysql' => 'Symfony\Component\HttpKernel\Profiler\MysqlProfilerStorage',
'file' => 'Symfony\Component\HttpKernel\Profiler\FileProfilerStorage',
'mongodb' => 'Symfony\Component\HttpKernel\Profiler\MongoDbProfilerStorage',
'sqlite' => 'Symfony\Component\HttpKernel\Profiler\SqliteProfilerStorage',
'mysql' => 'Symfony\Component\HttpKernel\Profiler\MysqlProfilerStorage',
'file' => 'Symfony\Component\HttpKernel\Profiler\FileProfilerStorage',
'mongodb' => 'Symfony\Component\HttpKernel\Profiler\MongoDbProfilerStorage',
'memcache' => 'Symfony\Component\HttpKernel\Profiler\MemcacheProfilerStorage',
'memcached' => 'Symfony\Component\HttpKernel\Profiler\MemcachedProfilerStorage',
);
list($class, ) = explode(':', $config['dsn'], 2);
if (!isset($supported[$class])) {

View File

@ -0,0 +1,260 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Profiler;
/**
* Base Memcache storage for profiling information in a Memcache.
*
* @author Andrej Hudec <pulzarraider@gmail.com>
*/
abstract class BaseMemcacheProfilerStorage implements ProfilerStorageInterface
{
const TOKEN_PREFIX = 'sf_profiler_';
protected $dsn;
protected $lifetime;
/**
* Constructor.
*
* @param string $dsn A data source name
* @param string $username
* @param string $password
* @param int $lifetime The lifetime to use for the purge
*/
public function __construct($dsn, $username = '', $password = '', $lifetime = 86400)
{
$this->dsn = $dsn;
$this->lifetime = (int) $lifetime;
}
/**
* {@inheritdoc}
*/
public function find($ip, $url, $limit, $method)
{
$indexName = $this->getIndexName();
$indexContent = $this->getValue($indexName);
if (!$indexContent) {
return array();
}
$profileList = explode("\n", $indexContent);
$result = array();
foreach ($profileList as $item) {
if ($limit === 0) {
break;
}
if ($item=='') {
continue;
}
list($itemToken, $itemIp, $itemMethod, $itemUrl, $itemTime, $itemParent) = explode("\t", $item, 6);
if ($ip && false === strpos($itemIp, $ip) || $url && false === strpos($itemUrl, $url) || $method && false === strpos($itemMethod, $method)) {
continue;
}
$result[$itemToken] = array(
'token' => $itemToken,
'ip' => $itemIp,
'method' => $itemMethod,
'url' => $itemUrl,
'time' => $itemTime,
'parent' => $itemParent,
);
--$limit;
}
return array_values($result);
}
/**
* {@inheritdoc}
*/
public function purge()
{
$this->flush();
}
/**
* {@inheritdoc}
*/
public function read($token)
{
if (empty($token)) {
return false;
}
$profile = $this->getValue($this->getItemName($token));
if (false !== $profile) {
$profile = $this->createProfileFromData($token, $profile);
}
return $profile;
}
/**
* {@inheritdoc}
*/
public function write(Profile $profile)
{
$data = array(
'token' => $profile->getToken(),
'parent' => $profile->getParentToken(),
'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()),
'data' => $profile->getCollectors(),
'ip' => $profile->getIp(),
'method' => $profile->getMethod(),
'url' => $profile->getUrl(),
'time' => $profile->getTime(),
);
if ($this->setValue($this->getItemName($profile->getToken()), $data, $this->lifetime)) {
// Add to index
$indexName = $this->getIndexName();
$indexRow = implode("\t", array(
$profile->getToken(),
$profile->getIp(),
$profile->getMethod(),
$profile->getUrl(),
$profile->getTime(),
$profile->getParentToken(),
))."\n";
return $this->appendValue($indexName, $indexRow, $this->lifetime);
}
return false;
}
/**
* Retrieve item from the memcache server
*
* @param string $key
*
* @return mixed
*/
abstract protected function getValue($key);
/**
* Store an item on the memcache server under the specified key
*
* @param string $key
* @param mixed $value
* @param int $expiration
*
* @return boolean
*/
abstract protected function setValue($key, $value, $expiration = 0);
/**
* Flush all existing items at the memcache server
*
* @return boolean
*/
abstract protected function flush();
/**
* Append data to an existing item on the memcache server
* @param string $key
* @param string $value
* @param int $expiration
*
* @return boolean
*/
abstract protected function appendValue($key, $value, $expiration = 0);
private function createProfileFromData($token, $data, $parent = null)
{
$profile = new Profile($token);
$profile->setIp($data['ip']);
$profile->setMethod($data['method']);
$profile->setUrl($data['url']);
$profile->setTime($data['time']);
$profile->setCollectors($data['data']);
if (!$parent && $data['parent']) {
$parent = $this->read($data['parent']);
}
if ($parent) {
$profile->setParent($parent);
}
foreach ($data['children'] as $token) {
if (!$token) {
continue;
}
if (!$childProfileData = $this->getValue($this->getItemName($token))) {
continue;
}
$profile->addChild($this->createProfileFromData($token, $childProfileData, $profile));
}
return $profile;
}
/**
* Get item name
*
* @param string $token
*
* @return string
*/
private function getItemName($token)
{
$name = self::TOKEN_PREFIX . $token;
if ($this->isItemNameValid($name)) {
return $name;
}
return false;
}
/**
* Get name of index
*
* @return string
*/
private function getIndexName()
{
$name = self::TOKEN_PREFIX . 'index';
if ($this->isItemNameValid($name)) {
return $name;
}
return false;
}
private function isItemNameValid($name)
{
$length = strlen($name);
if ($length > 250) {
throw new \RuntimeException(sprintf('The memcache item key "%s" is too long (%s bytes). Allowed maximum size is 250 bytes.', $name, $length));
}
return true;
}
}

View File

@ -0,0 +1,100 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Profiler;
use Memcache;
/**
* Memcache Profiler Storage
*
* @author Andrej Hudec <pulzarraider@gmail.com>
*/
class MemcacheProfilerStorage extends BaseMemcacheProfilerStorage
{
/**
* @var Memcache
*/
private $memcache;
/**
* Internal convenience method that returns the instance of the Memcache
*
* @return Memcache
*/
protected function getMemcache()
{
if (null === $this->memcache) {
if (!preg_match('#^memcache://(.*)/(.*)$#', $this->dsn, $matches)) {
throw new \RuntimeException('Please check your configuration. You are trying to use Memcache with an invalid dsn. "' . $this->dsn . '"');
}
$host = $matches[1];
$port = $matches[2];
$memcache = new Memcache;
$memcache->addServer($host, $port);
$this->memcache = $memcache;
}
return $this->memcache;
}
/**
* {@inheritdoc}
*/
protected function getValue($key)
{
return $this->getMemcache()->get($key);
}
/**
* {@inheritdoc}
*/
protected function setValue($key, $value, $expiration = 0)
{
return $this->getMemcache()->set($key, $value, false, $expiration);
}
/**
* {@inheritdoc}
*/
protected function flush()
{
return $this->getMemcache()->flush();
}
/**
* {@inheritdoc}
*/
protected function appendValue($key, $value, $expiration = 0)
{
$memcache = $this->getMemcache();
if (method_exists($memcache, 'append')) {
//Memcache v3.0
if (!$result = $memcache->append($key, $value, false, $expiration)) {
return $memcache->set($key, $value, false, $expiration);
}
return $result;
}
//simulate append in Memcache <3.0
$content = $memcache->get($key);
return $memcache->set($key, $content . $value, false, $expiration);
}
}

View File

@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Profiler;
use Memcached;
/**
* Memcached Profiler Storage
*
* @author Andrej Hudec <pulzarraider@gmail.com>
*/
class MemcachedProfilerStorage extends BaseMemcacheProfilerStorage
{
/**
* @var Memcached
*/
private $memcached;
/**
* Internal convenience method that returns the instance of the Memcached
*
* @return Memcached
*/
protected function getMemcached()
{
if (null === $this->memcached) {
if (!preg_match('#^memcached://(.*)/(.*)$#', $this->dsn, $matches)) {
throw new \RuntimeException('Please check your configuration. You are trying to use Memcached with an invalid dsn. "' . $this->dsn . '"');
}
$host = $matches[1];
$port = $matches[2];
$memcached = new Memcached;
//disable compression to allow appending
$memcached->setOption(Memcached::OPT_COMPRESSION, false);
$memcached->addServer($host, $port);
$this->memcached = $memcached;
}
return $this->memcached;
}
/**
* {@inheritdoc}
*/
protected function getValue($key)
{
return $this->getMemcached()->get($key);
}
/**
* {@inheritdoc}
*/
protected function setValue($key, $value, $expiration = 0)
{
return $this->getMemcached()->set($key, $value, false, $expiration);
}
/**
* {@inheritdoc}
*/
protected function flush()
{
return $this->getMemcached()->flush();
}
/**
* {@inheritdoc}
*/
protected function appendValue($key, $value, $expiration = 0)
{
$memcached = $this->getMemcached();
if (!$result = $memcached->append($key, $value)) {
return $memcached->set($key, $value, $expiration);
}
return $result;
}
}

View File

@ -0,0 +1,194 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Tests\Component\HttpKernel\Profiler;
use Symfony\Component\HttpKernel\Profiler\MemcacheProfilerStorage;
use Symfony\Component\HttpKernel\Profiler\Profile;
class DummyMemcacheProfilerStorage extends MemcacheProfilerStorage
{
public function getMemcache()
{
return parent::getMemcache();
}
}
class MemcacheProfilerStorageTest extends \PHPUnit_Framework_TestCase
{
protected static $storage;
public static function tearDownAfterClass()
{
if (self::$storage) {
self::$storage->purge();
}
}
protected function setUp()
{
if (!extension_loaded('memcache')) {
$this->markTestSkipped('MemcacheProfilerStorageTest requires that the extension memcache is loaded');
}
self::$storage = new DummyMemcacheProfilerStorage('memcache://127.0.0.1/11211', '', '', 86400);
try {
self::$storage->getMemcache();
} catch(\Exception $e) {
$this->markTestSkipped('MemcacheProfilerStorageTest requires that there is a Memcache server present on localhost');
}
if (self::$storage) {
self::$storage->purge();
}
}
public function testStore()
{
for ($i = 0; $i < 10; $i ++) {
$profile = new Profile('token_'.$i);
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar');
$profile->setMethod('GET');
self::$storage->write($profile);
}
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar', 20, 'GET')), 10, '->write() stores data in the Memcache');
}
public function testChildren()
{
$parentProfile = new Profile('token_parent');
$parentProfile->setIp('127.0.0.1');
$parentProfile->setUrl('http://foo.bar/parent');
$childProfile = new Profile('token_child');
$childProfile->setIp('127.0.0.1');
$childProfile->setUrl('http://foo.bar/child');
$parentProfile->addChild($childProfile);
self::$storage->write($parentProfile);
self::$storage->write($childProfile);
// Load them from storage
$parentProfile = self::$storage->read('token_parent');
$childProfile = self::$storage->read('token_child');
// Check child has link to parent
$this->assertNotNull($childProfile->getParent());
$this->assertEquals($parentProfile->getToken(), $childProfile->getParentToken());
// Check parent has child
$children = $parentProfile->getChildren();
$this->assertEquals(1, count($children));
$this->assertEquals($childProfile->getToken(), $children[0]->getToken());
}
public function testStoreSpecialCharsInUrl()
{
// The SQLite storage accepts special characters in URLs (Even though URLs are not
// supposed to contain them)
$profile = new Profile('simple_quote');
$profile->setUrl('127.0.0.1', 'http://foo.bar/\'');
self::$storage->write($profile);
$this->assertTrue(false !== self::$storage->read('simple_quote'), '->write() accepts single quotes in URL');
$profile = new Profile('double_quote');
$profile->setUrl('127.0.0.1', 'http://foo.bar/"');
self::$storage->write($profile);
$this->assertTrue(false !== self::$storage->read('double_quote'), '->write() accepts double quotes in URL');
$profile = new Profile('backslash');
$profile->setUrl('127.0.0.1', 'http://foo.bar/\\');
self::$storage->write($profile);
$this->assertTrue(false !== self::$storage->read('backslash'), '->write() accepts backslash in URL');
$profile = new Profile('comma');
$profile->setUrl('127.0.0.1', 'http://foo.bar/,');
self::$storage->write($profile);
$this->assertTrue(false !== self::$storage->read('comma'), '->write() accepts comma in URL');
}
public function testStoreDuplicateToken()
{
$profile = new Profile('token');
$profile->setUrl('http://example.com/');
$this->assertTrue(self::$storage->write($profile), '->write() returns true when the token is unique');
$profile->setUrl('http://example.net/');
$this->assertTrue(self::$storage->write($profile), '->write() returns true when the token is already present in the Memcache');
$this->assertEquals('http://example.net/', self::$storage->read('token')->getUrl(), '->write() overwrites the current profile data');
$this->assertCount(1, self::$storage->find('', '', 1000, ''), '->find() does not return the same profile twice');
}
public function testRetrieveByIp()
{
$profile = new Profile('token');
$profile->setIp('127.0.0.1');
$profile->setMethod('GET');
self::$storage->write($profile);
$this->assertEquals(count(self::$storage->find('127.0.0.1', '', 10, 'GET')), 1, '->find() retrieve a record by IP');
$this->assertEquals(count(self::$storage->find('127.0.%.1', '', 10, 'GET')), 0, '->find() does not interpret a "%" as a wildcard in the IP');
$this->assertEquals(count(self::$storage->find('127.0._.1', '', 10, 'GET')), 0, '->find() does not interpret a "_" as a wildcard in the IP');
}
public function testRetrieveByUrl()
{
$profile = new Profile('simple_quote');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/\'');
$profile->setMethod('GET');
self::$storage->write($profile);
$profile = new Profile('double_quote');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/"');
$profile->setMethod('GET');
self::$storage->write($profile);
$profile = new Profile('backslash');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo\\bar/');
$profile->setMethod('GET');
self::$storage->write($profile);
$profile = new Profile('percent');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/%');
$profile->setMethod('GET');
self::$storage->write($profile);
$profile = new Profile('underscore');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/_');
$profile->setMethod('GET');
self::$storage->write($profile);
$profile = new Profile('semicolon');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/;');
$profile->setMethod('GET');
self::$storage->write($profile);
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar/\'', 10, 'GET')), 1, '->find() accepts single quotes in URLs');
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar/"', 10, 'GET')), 1, '->find() accepts double quotes in URLs');
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo\\bar/', 10, 'GET')), 1, '->find() accepts backslash in URLs');
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar/;', 10, 'GET')), 1, '->find() accepts semicolon in URLs');
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar/%', 10, 'GET')), 1, '->find() does not interpret a "%" as a wildcard in the URL');
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar/_', 10, 'GET')), 1, '->find() does not interpret a "_" as a wildcard in the URL');
}
}

View File

@ -0,0 +1,194 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Tests\Component\HttpKernel\Profiler;
use Symfony\Component\HttpKernel\Profiler\MemcachedProfilerStorage;
use Symfony\Component\HttpKernel\Profiler\Profile;
class DummyMemcachedProfilerStorage extends MemcachedProfilerStorage
{
public function getMemcached()
{
return parent::getMemcached();
}
}
class MemcachedProfilerStorageTest extends \PHPUnit_Framework_TestCase
{
protected static $storage;
public static function tearDownAfterClass()
{
if (self::$storage) {
self::$storage->purge();
}
}
protected function setUp()
{
if (!extension_loaded('memcached')) {
$this->markTestSkipped('MemcachedProfilerStorageTest requires that the extension memcached is loaded');
}
self::$storage = new DummyMemcachedProfilerStorage('memcached://127.0.0.1/11211', '', '', 86400);
try {
self::$storage->getMemcached();
} catch(\Exception $e) {
$this->markTestSkipped('MemcachedProfilerStorageTest requires that there is a Memcache server present on localhost');
}
if (self::$storage) {
self::$storage->purge();
}
}
public function testStore()
{
for ($i = 0; $i < 10; $i ++) {
$profile = new Profile('token_'.$i);
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar');
$profile->setMethod('GET');
self::$storage->write($profile);
}
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar', 20, 'GET')), 10, '->write() stores data in the Memcache');
}
public function testChildren()
{
$parentProfile = new Profile('token_parent');
$parentProfile->setIp('127.0.0.1');
$parentProfile->setUrl('http://foo.bar/parent');
$childProfile = new Profile('token_child');
$childProfile->setIp('127.0.0.1');
$childProfile->setUrl('http://foo.bar/child');
$parentProfile->addChild($childProfile);
self::$storage->write($parentProfile);
self::$storage->write($childProfile);
// Load them from storage
$parentProfile = self::$storage->read('token_parent');
$childProfile = self::$storage->read('token_child');
// Check child has link to parent
$this->assertNotNull($childProfile->getParent());
$this->assertEquals($parentProfile->getToken(), $childProfile->getParentToken());
// Check parent has child
$children = $parentProfile->getChildren();
$this->assertEquals(1, count($children));
$this->assertEquals($childProfile->getToken(), $children[0]->getToken());
}
public function testStoreSpecialCharsInUrl()
{
// The SQLite storage accepts special characters in URLs (Even though URLs are not
// supposed to contain them)
$profile = new Profile('simple_quote');
$profile->setUrl('127.0.0.1', 'http://foo.bar/\'');
self::$storage->write($profile);
$this->assertTrue(false !== self::$storage->read('simple_quote'), '->write() accepts single quotes in URL');
$profile = new Profile('double_quote');
$profile->setUrl('127.0.0.1', 'http://foo.bar/"');
self::$storage->write($profile);
$this->assertTrue(false !== self::$storage->read('double_quote'), '->write() accepts double quotes in URL');
$profile = new Profile('backslash');
$profile->setUrl('127.0.0.1', 'http://foo.bar/\\');
self::$storage->write($profile);
$this->assertTrue(false !== self::$storage->read('backslash'), '->write() accepts backslash in URL');
$profile = new Profile('comma');
$profile->setUrl('127.0.0.1', 'http://foo.bar/,');
self::$storage->write($profile);
$this->assertTrue(false !== self::$storage->read('comma'), '->write() accepts comma in URL');
}
public function testStoreDuplicateToken()
{
$profile = new Profile('token');
$profile->setUrl('http://example.com/');
$this->assertTrue(self::$storage->write($profile), '->write() returns true when the token is unique');
$profile->setUrl('http://example.net/');
$this->assertTrue(self::$storage->write($profile), '->write() returns true when the token is already present in the Memcache');
$this->assertEquals('http://example.net/', self::$storage->read('token')->getUrl(), '->write() overwrites the current profile data');
$this->assertCount(1, self::$storage->find('', '', 1000, ''), '->find() does not return the same profile twice');
}
public function testRetrieveByIp()
{
$profile = new Profile('token');
$profile->setIp('127.0.0.1');
$profile->setMethod('GET');
self::$storage->write($profile);
$this->assertEquals(count(self::$storage->find('127.0.0.1', '', 10, 'GET')), 1, '->find() retrieve a record by IP');
$this->assertEquals(count(self::$storage->find('127.0.%.1', '', 10, 'GET')), 0, '->find() does not interpret a "%" as a wildcard in the IP');
$this->assertEquals(count(self::$storage->find('127.0._.1', '', 10, 'GET')), 0, '->find() does not interpret a "_" as a wildcard in the IP');
}
public function testRetrieveByUrl()
{
$profile = new Profile('simple_quote');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/\'');
$profile->setMethod('GET');
self::$storage->write($profile);
$profile = new Profile('double_quote');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/"');
$profile->setMethod('GET');
self::$storage->write($profile);
$profile = new Profile('backslash');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo\\bar/');
$profile->setMethod('GET');
self::$storage->write($profile);
$profile = new Profile('percent');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/%');
$profile->setMethod('GET');
self::$storage->write($profile);
$profile = new Profile('underscore');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/_');
$profile->setMethod('GET');
self::$storage->write($profile);
$profile = new Profile('semicolon');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/;');
$profile->setMethod('GET');
self::$storage->write($profile);
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar/\'', 10, 'GET')), 1, '->find() accepts single quotes in URLs');
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar/"', 10, 'GET')), 1, '->find() accepts double quotes in URLs');
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo\\bar/', 10, 'GET')), 1, '->find() accepts backslash in URLs');
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar/;', 10, 'GET')), 1, '->find() accepts semicolon in URLs');
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar/%', 10, 'GET')), 1, '->find() does not interpret a "%" as a wildcard in the URL');
$this->assertEquals(count(self::$storage->find('127.0.0.1', 'http://foo.bar/_', 10, 'GET')), 1, '->find() does not interpret a "_" as a wildcard in the URL');
}
}