Skip to content

Instantly share code, notes, and snippets.

@andrescevp
Created September 19, 2017 09:20
Show Gist options
  • Save andrescevp/07fc9e8dddd807b8384b969667b48370 to your computer and use it in GitHub Desktop.
Save andrescevp/07fc9e8dddd807b8384b969667b48370 to your computer and use it in GitHub Desktop.
Memcached Profile Storage implementation for Symfony 3.x
<?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());
}
}
<?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;
}
}
<?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