Created
September 19, 2017 09:20
-
-
Save andrescevp/07fc9e8dddd807b8384b969667b48370 to your computer and use it in GitHub Desktop.
Memcached Profile Storage implementation for Symfony 3.x
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace AppBundle; | |
use AppBundle\DependencyInjection\Compiler\OverrideServiceCompilerPass; | |
use Symfony\Component\DependencyInjection\ContainerBuilder; | |
use Symfony\Component\HttpKernel\Bundle\Bundle; | |
class AppBundle extends Bundle | |
{ | |
public function build(ContainerBuilder $container) | |
{ | |
parent::build($container); | |
$container->addCompilerPass(new OverrideServiceCompilerPass()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace AppBundle\Profile; | |
use Symfony\Component\HttpKernel\Profiler\Profile; | |
use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface; | |
/** | |
* Class MemcachedProfileStorage | |
* | |
* Memcached Profile Storage implementation for Symfony 3.x | |
* | |
* @package AppBundle\Profile | |
*/ | |
class MemcachedProfileStorage implements ProfilerStorageInterface | |
{ | |
const INDEX_KEY = 'profile_index'; | |
/** | |
* @var \Lsw\MemcacheBundle\Cache\AntiDogPileMemcache | |
*/ | |
private $memcache; | |
/** | |
* @param \Lsw\MemcacheBundle\Cache\AntiDogPileMemcache $memcache | |
*/ | |
public function setMemcache(\Lsw\MemcacheBundle\Cache\AntiDogPileMemcache $memcache) | |
{ | |
$this->memcache = $memcache; | |
} | |
/** | |
* Finds profiler tokens for the given criteria. | |
* | |
* @param string $ip The IP | |
* @param string $url The URL | |
* @param string $limit The maximum number of tokens to return | |
* @param string $method The request method | |
* @param int|null $start The start date to search from | |
* @param int|null $end The end date to search to | |
* | |
* @return array An array of tokens | |
*/ | |
public function find($ip, $url, $limit, $method, $start = null, $end = null, $statusCode = null) | |
{ | |
/** @var array $index */ | |
$index = $this->memcache->get(self::INDEX_KEY); | |
if (!$index || empty($index)) { | |
return []; | |
} | |
$result = []; | |
foreach ($index as $hash => $profileData) { | |
if ($ip && false === strpos($profileData['ip'], $ip) || | |
$url && false === strpos($profileData['url'], $url) || | |
$method && false === strpos($profileData['method'], $method) || | |
$statusCode && false === strpos($profileData['status_code'], $statusCode) | |
) { | |
continue; | |
} | |
if (!empty($start) && (int) $profileData['time'] < $start) { | |
continue; | |
} | |
if (!empty($end) && (int) $profileData['time'] > $end) { | |
continue; | |
} | |
$result[$profileData['token']] = array( | |
'token' => $profileData['token'], | |
'ip' => $profileData['ip'], | |
'method' => $profileData['method'], | |
'url' => $profileData['url'], | |
'time' => $profileData['time'], | |
'parent' => $profileData['parent'], | |
'status_code' => $profileData['status_code'], | |
); | |
} | |
return array_values($result); | |
} | |
/** | |
* Reads data associated with the given token. | |
* | |
* The method returns false if the token does not exist in the storage. | |
* | |
* @param string $token A token | |
* | |
* @return Profile The profile associated with token | |
*/ | |
public function read($token) | |
{ | |
$data = $this->memcache->get($token); | |
if (!$data) { | |
return; | |
} | |
return $this->createProfileFromData($token, $data); | |
} | |
/** | |
* Saves a Profile. | |
* | |
* @param Profile $profile A Profile instance | |
* | |
* @return bool Write operation successful | |
*/ | |
public function write(Profile $profile) | |
{ | |
$index = $this->memcache->get(self::INDEX_KEY); | |
if (!$index) { | |
$index = []; | |
} | |
$data = $this->generateDataArrayFromProfile($profile); | |
$profileIndexed = false; | |
$profileIndexKey = $this->generateDataHash($data); | |
if (array_key_exists($profileIndexKey, $index)) { | |
$profileIndexed = true; | |
} | |
if (!$profileIndexed) { | |
$index[$profileIndexKey] = $data; | |
} | |
$this->memcache->set(self::INDEX_KEY, $index); | |
$this->memcache->set($profile->getToken(), $data); | |
return true; | |
} | |
/** | |
* Purges all data from the database. | |
*/ | |
public function purge() | |
{ | |
throw new \RuntimeException('Please purge memcache via command line'); | |
} | |
/** | |
* @param $token | |
* @param $data | |
* @param null $parent | |
* | |
* @return \Symfony\Component\HttpKernel\Profiler\Profile | |
*/ | |
protected 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->setStatusCode($data['status_code']); | |
$profile->setCollectors($data['data']); | |
if (!$parent && $data['parent']) { | |
$parent = $this->read($data['parent']); | |
} | |
if ($parent) { | |
$profile->setParent($parent); | |
} | |
foreach ($data['children'] as $token) { | |
$tokenProfile = $this->memcache->get($token); | |
if (!$token || !$tokenProfile) { | |
continue; | |
} | |
$profile->addChild($this->createProfileFromData($token, $tokenProfile, $profile)); | |
} | |
return $profile; | |
} | |
/** | |
* @param $data | |
* | |
* @return string | |
*/ | |
protected function generateDataHash($data): string | |
{ | |
return md5(serialize($data)); | |
} | |
/** | |
* @param \Symfony\Component\HttpKernel\Profiler\Profile $profile | |
* | |
* @return array | |
*/ | |
protected function generateDataArrayFromProfile(Profile $profile): array | |
{ | |
$profileToken = $profile->getToken(); | |
$parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; | |
$childrenToken = array_filter(array_map(function ($p) use ($profileToken) { | |
return $profileToken !== $p->getToken() ? $p->getToken() : null; | |
}, $profile->getChildren())); | |
// Store profile | |
$data = [ | |
'token' => $profileToken, | |
'parent' => $parentToken, | |
'children' => $childrenToken, | |
'data' => $profile->getCollectors(), | |
'ip' => $profile->getIp(), | |
'method' => $profile->getMethod(), | |
'url' => $profile->getUrl(), | |
'time' => $profile->getTime(), | |
'status_code' => $profile->getStatusCode(), | |
]; | |
return $data; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace AppBundle\DependencyInjection\Compiler; | |
use AppBundle\Profile\MemcachedProfileStorage; | |
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | |
use Symfony\Component\DependencyInjection\ContainerBuilder; | |
use Symfony\Component\DependencyInjection\Reference; | |
class OverrideServiceCompilerPass implements CompilerPassInterface | |
{ | |
public function process(ContainerBuilder $container) | |
{ | |
$definition = $container->getDefinition('profiler.storage'); | |
$definition->setClass(MemcachedProfileStorage::class); | |
$definition->addMethodCall('setMemcache', [new Reference('memcache.default')]); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment