2015-08-18 00:00:26 +00:00
< ? 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 ;
2016-04-20 16:56:34 +00:00
@ trigger_error ( 'The ' . __NAMESPACE__ . '\RedisProfilerStorage class is deprecated since Symfony 2.8 and will be removed in 3.0. Use FileProfilerStorage instead.' , E_USER_DEPRECATED );
2015-08-18 00:00:26 +00:00
/**
* RedisProfilerStorage stores profiling information in Redis .
*
* @ author Andrej Hudec < pulzarraider @ gmail . com >
* @ author Stephane PY < py . stephane1 @ gmail . com >
2016-04-20 16:56:34 +00:00
*
* @ deprecated Deprecated since Symfony 2.8 , to be removed in Symfony 3.0 .
* Use { @ link FileProfilerStorage } instead .
2015-08-18 00:00:26 +00:00
*/
class RedisProfilerStorage implements ProfilerStorageInterface
{
const TOKEN_PREFIX = 'sf_profiler_' ;
const REDIS_OPT_SERIALIZER = 1 ;
const REDIS_OPT_PREFIX = 2 ;
const REDIS_SERIALIZER_NONE = 0 ;
const REDIS_SERIALIZER_PHP = 1 ;
protected $dsn ;
protected $lifetime ;
/**
* @ var \Redis
*/
private $redis ;
/**
* Constructor .
*
* @ param string $dsn A data source name
* @ param string $username Not used
* @ param string $password Not used
* @ 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 , $start = null , $end = null )
{
$indexName = $this -> getIndexName ();
if ( ! $indexContent = $this -> getValue ( $indexName , self :: REDIS_SERIALIZER_NONE )) {
return array ();
}
$profileList = array_reverse ( explode ( " \n " , $indexContent ));
$result = array ();
foreach ( $profileList as $item ) {
if ( $limit === 0 ) {
break ;
}
if ( $item == '' ) {
continue ;
}
$values = explode ( " \t " , $item , 7 );
list ( $itemToken , $itemIp , $itemMethod , $itemUrl , $itemTime , $itemParent ) = $values ;
$statusCode = isset ( $values [ 6 ]) ? $values [ 6 ] : null ;
$itemTime = ( int ) $itemTime ;
if ( $ip && false === strpos ( $itemIp , $ip ) || $url && false === strpos ( $itemUrl , $url ) || $method && false === strpos ( $itemMethod , $method )) {
continue ;
}
if ( ! empty ( $start ) && $itemTime < $start ) {
continue ;
}
if ( ! empty ( $end ) && $itemTime > $end ) {
continue ;
}
$result [] = array (
'token' => $itemToken ,
'ip' => $itemIp ,
'method' => $itemMethod ,
'url' => $itemUrl ,
'time' => $itemTime ,
'parent' => $itemParent ,
'status_code' => $statusCode ,
);
-- $limit ;
}
return $result ;
}
/**
* { @ inheritdoc }
*/
public function purge ()
{
// delete only items from index
$indexName = $this -> getIndexName ();
$indexContent = $this -> getValue ( $indexName , self :: REDIS_SERIALIZER_NONE );
if ( ! $indexContent ) {
return false ;
}
$profileList = explode ( " \n " , $indexContent );
$result = array ();
foreach ( $profileList as $item ) {
if ( $item == '' ) {
continue ;
}
if ( false !== $pos = strpos ( $item , " \t " )) {
$result [] = $this -> getItemName ( substr ( $item , 0 , $pos ));
}
}
$result [] = $indexName ;
return $this -> delete ( $result );
}
/**
* { @ inheritdoc }
*/
public function read ( $token )
{
if ( empty ( $token )) {
return false ;
}
$profile = $this -> getValue ( $this -> getItemName ( $token ), self :: REDIS_SERIALIZER_PHP );
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 (),
);
$profileIndexed = false !== $this -> getValue ( $this -> getItemName ( $profile -> getToken ()));
if ( $this -> setValue ( $this -> getItemName ( $profile -> getToken ()), $data , $this -> lifetime , self :: REDIS_SERIALIZER_PHP )) {
if ( ! $profileIndexed ) {
// Add to index
$indexName = $this -> getIndexName ();
$indexRow = implode ( " \t " , array (
$profile -> getToken (),
$profile -> getIp (),
$profile -> getMethod (),
$profile -> getUrl (),
$profile -> getTime (),
$profile -> getParentToken (),
$profile -> getStatusCode (),
)) . " \n " ;
return $this -> appendValue ( $indexName , $indexRow , $this -> lifetime );
}
return true ;
}
return false ;
}
/**
* Internal convenience method that returns the instance of Redis .
*
* @ return \Redis
*
* @ throws \RuntimeException
*/
protected function getRedis ()
{
if ( null === $this -> redis ) {
$data = parse_url ( $this -> dsn );
if ( false === $data || ! isset ( $data [ 'scheme' ]) || $data [ 'scheme' ] !== 'redis' || ! isset ( $data [ 'host' ]) || ! isset ( $data [ 'port' ])) {
throw new \RuntimeException ( sprintf ( 'Please check your configuration. You are trying to use Redis with an invalid dsn "%s". The minimal expected format is "redis://[host]:port".' , $this -> dsn ));
}
if ( ! extension_loaded ( 'redis' )) {
throw new \RuntimeException ( 'RedisProfilerStorage requires that the redis extension is loaded.' );
}
$redis = new \Redis ();
$redis -> connect ( $data [ 'host' ], $data [ 'port' ]);
if ( isset ( $data [ 'path' ])) {
$redis -> select ( substr ( $data [ 'path' ], 1 ));
}
if ( isset ( $data [ 'pass' ])) {
$redis -> auth ( $data [ 'pass' ]);
}
$redis -> setOption ( self :: REDIS_OPT_PREFIX , self :: TOKEN_PREFIX );
$this -> redis = $redis ;
}
return $this -> redis ;
}
/**
* Set instance of the Redis .
*
* @ param \Redis $redis
*/
public function setRedis ( $redis )
{
$this -> redis = $redis ;
}
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 ), self :: REDIS_SERIALIZER_PHP )) {
continue ;
}
$profile -> addChild ( $this -> createProfileFromData ( $token , $childProfileData , $profile ));
}
return $profile ;
}
/**
* Gets the item name .
*
* @ param string $token
*
* @ return string
*/
private function getItemName ( $token )
{
$name = $token ;
if ( $this -> isItemNameValid ( $name )) {
return $name ;
}
return false ;
}
/**
* Gets the name of the index .
*
* @ return string
*/
private function getIndexName ()
{
$name = 'index' ;
if ( $this -> isItemNameValid ( $name )) {
return $name ;
}
return false ;
}
private function isItemNameValid ( $name )
{
$length = strlen ( $name );
if ( $length > 2147483648 ) {
throw new \RuntimeException ( sprintf ( 'The Redis item key "%s" is too long (%s bytes). Allowed maximum size is 2^31 bytes.' , $name , $length ));
}
return true ;
}
/**
* Retrieves an item from the Redis server .
*
* @ param string $key
* @ param int $serializer
*
* @ return mixed
*/
private function getValue ( $key , $serializer = self :: REDIS_SERIALIZER_NONE )
{
$redis = $this -> getRedis ();
$redis -> setOption ( self :: REDIS_OPT_SERIALIZER , $serializer );
return $redis -> get ( $key );
}
/**
* Stores an item on the Redis server under the specified key .
*
* @ param string $key
* @ param mixed $value
* @ param int $expiration
* @ param int $serializer
*
* @ return bool
*/
private function setValue ( $key , $value , $expiration = 0 , $serializer = self :: REDIS_SERIALIZER_NONE )
{
$redis = $this -> getRedis ();
$redis -> setOption ( self :: REDIS_OPT_SERIALIZER , $serializer );
return $redis -> setex ( $key , $expiration , $value );
}
/**
* Appends data to an existing item on the Redis server .
*
* @ param string $key
* @ param string $value
* @ param int $expiration
*
* @ return bool
*/
private function appendValue ( $key , $value , $expiration = 0 )
{
$redis = $this -> getRedis ();
$redis -> setOption ( self :: REDIS_OPT_SERIALIZER , self :: REDIS_SERIALIZER_NONE );
if ( $redis -> exists ( $key )) {
$redis -> append ( $key , $value );
return $redis -> setTimeout ( $key , $expiration );
}
return $redis -> setex ( $key , $expiration , $value );
}
/**
* Removes the specified keys .
*
* @ param array $keys
*
* @ return bool
*/
private function delete ( array $keys )
{
return ( bool ) $this -> getRedis () -> delete ( $keys );
}
}