Consider having a Class whose method is being called in iteration n times, and the method in itself has a statement that pulls data from a cache system - Redis.
Now if this method is called n times the cache is also hit n times, leading to a continuous fetch and unserializing of the same data n times, which in the case of an interpreter like PHP consumes a good amount of time & CPU.
Passing the cached data to this method could also be a no, as we might instantiate n number of instances of this class.
Hence is there a way where we can avoid hitting the cache multiple times in the context of a Class and/or Object?
Maybe we can somehow use static properties of the Object to hold the value?
First, write a service class that:
Provides getter, which:
Loads required value from cache (based on unique-key),
But also backups the loaded-value,
Finally, returns backupped-value instead of re-loading from cache.
Provides setter which updates both backup and cache.
Then simply use Laravel's feature to inject and/or get instance of said service-class.
Example
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
class MyCacheService
{
protected $backupMap = [];
/**
* Requests an instance of this-class from Laravel.
*
* #return MyCacheService|null
*/
public static function instance()
{
return \Illuminate\Support\Facades\App::make(MyCacheService::class);
}
public function get($key)
{
$backup = & $this->backupMap[$key];
if ( ! isset($backup)) {
$backup = $this->rawGet($key);
}
return $buckup;
}
public function set($key, $value)
{
$this->rawSet($key, $value);
$this->backupMap[$key] = $value;
}
/** Loads from cache */
private function rawGet($key)
{
// ... your normal loading from cache logic goes here,
// but as sub-example:
return Cache::get($key);
}
/** Saves into cache */
private function rawSet($key, $value)
{
// ... your normal saving into cache logic goes here,
// but as sub-example:
Cache::set($key, $value);
}
}
Usage:
use App\Services\MyCacheService;
use Illuminate\Support\Facades\App;
// ...
// Get instance once, like:
$cache = MyCacheService::instance();
// Or like:
/** #var MyCacheService|null $cache */
$cache = App::make(MyCacheService::class);
// Finally, reuse as many times as required, like:
$myValue = $cache->get('my-unique-key');
$myOtherValue = $cache->get('my-other-key');
Note that Laravel stores the class's instance for us, else one could use private static property named $instance, and return that from instance() method.
WARNING: as you may already know, maybe replace rawGet and rawSet's logic.
Also, place MyCacheService.php file (which's source is above) in app/Services folder.
If you are in the opinion that you should not use a service-class (which the other answer explains),
then consider using PHP's $GLOBALS variable to backup the loaded-Cache (instead of adding yet another custom static variable).
Examples
Maybe backup once for all:
In Laravel's AppServiceProvider.php file, do something like:
<?php
namespace App\Providers;
// ...
use Illuminate\Support\Facades\Cache;
class AppServiceProvider extends ServiceProvider
{
// ...
public function boot()
{
$keyList = [
'my-unique-key',
'my-other-key',
];
foreach($keyList as $key) {
$GLOBALS['cache-' . $key] = Cache::get($key);
}
}
}
Finally, use $GLOBALS['cache-my-unique-key'] anywhere required.
Or, backup per class:
<?php
use Illuminate\Support\Facades\Cache;
class YourClass {
private $myData;
public function __construct()
{
$key = 'my-unique-key';
$value = & $GLOBALS['cache-' . $key];
if ( ! isset($value)) {
$value = Cache::get($key);
}
$this->myData = $value;
}
// Finally, use `$this->myData` anywhere in this same class.
}
Note that as you may already know, in both cases we use 'cache-' prefix to not overwrite anything by mistake.
You can use static properties to store the cached data and use access it same.
class CacheClass
{
private static $cachedData;
public function retrieveData()
{
if (!isset(self::$cachedData)) {
// re-assign the value
self::$cachedData = fetchDataFromCache();
}
return self::$cachedData;
}
}
Related
I'm currently working on a project where I have to work with huge arrays. With huge, I mean 1k elements or more. Since these are a lot of arrays and i sometimes mess things up, I decided to create a class with static functions so i can call the functions which would make the entire project easier to read. This is what I currently have:
ArrayAccess.class.php:
require "dep/arrays/elements.php";
class ArrayAccess {
public static function get_value_from_element($element) {
return $elements[$element];
}
}
elements.php:
<?php
$elements = array(
"sam" => 6, ... and so on ...
I simply want to be able to use ArrayAccess::get_value_from_element($element) in my project. It is so much easier to read than all these indexes everywhere. However, the array is defined in the elements.php file - I can't use that in the class.
So how can I access the array in my class? Please note, I cannot copy it into the class, the file would be larger than 400k lines, this is not an option.
You can return a value from an include (or require in this case) and store that to a static property the first time the function is called.
elements.php:
<?php
return array("sam" => 6, ...);
DataAccess.php:
class DataAccess {
private static $elements = array();
public static function get_value_from_element($element) {
if(self::$elements === array()) {
self::$elements = require "elements.php";
}
return self::$elements[$element];
}
}
You should also avoid naming your class ArrayAccess, since it already exists in PHP.
In elements.php
<?php
return array( // use return so you can retrieve these into a variable
"sam" => 6, ... and so on ...
Then in the class
<?php
class ArrayAccess {
public static $elements = null; // set up a static var to avoid load this big array multiple times
public static function get_value_from_element($element) {
if(self::$elements === null) { // check if null to load it from the file
self::$elements = require('elements.php');
}
return self::$elements[$element]; // there you go
}
}
If you don't want do the if statement in the getter every time, you should probably find some where else to load the file into the static variable before using the getter.
An alternative is to declare $elements as global in your class:
require "dep/arrays/elements.php";
class ArrayAccess {
public static function get_value_from_element($element) {
global $elements;
return $elements[$element];
}
}
I'm curious about the best way of speccing classes that handle file operations.
Assuming I have a fictional class with a method duplicate whose job is to duplicate the contents of a file.
<?php
class FileOperator
{
public function duplicate($filename)
{
$content = file_get_contents($filename);
file_put_contents($filename, $content.$content);
}
}
I know that I can use something like vfsStream to assert the change without touching the actual filesystem (at least with assertions in PHPUnit).
How could I assert that in a spec? Or would it be approached differently?
Also, I get that I might want to extract that functionality into another class and use a Spy to assert that the FileOperator calls its dependency correctly, but then I'd still have to spec that adapter class, and my question remains.
Thanks.
This is more likely a functional test rather than an unit test, so it's hard to use phpspec in this case.
If you insist, I see two options.
If you happen to need a method to fetch the file contents too, you could write your spec this way:
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use PhpSpec\ObjectBehavior;
class FileOperatorSpec extends ObjectBehavior
{
/**
* #var vfsStreamDirectory
*/
private $workDir;
function let()
{
$this->workDir = vfsStream::setup('workDir');
}
function it_duplicates_a_content_in_a_file()
{
$this->createFile('foo', 'bar');
$this->duplicate('vfs://workDir/foo');
$this->read('vfs://workDir/foo')->shouldReturn('barbar');
}
private function createFile($path, $content)
{
$file = vfsStream::newFile($path);
$file->setContent($content);
$this->workDir->addChild($file);
}
}
Alternatively, you could use the expect helper:
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use PhpSpec\ObjectBehavior;
class FileOperatorSpec extends ObjectBehavior
{
/**
* #var vfsStreamDirectory
*/
private $workDir;
function let()
{
$this->workDir = vfsStream::setup('workDir');
}
function it_duplicates_a_content_in_a_file()
{
$this->createFile('foo', 'bar');
$this->duplicate('vfs://workDir/foo');
expect(file_get_contents('vfs://workDir/foo'))->toBe('barbar');
}
private function createFile($path, $content)
{
$file = vfsStream::newFile($path);
$file->setContent($content);
$this->workDir->addChild($file);
}
}
I'm struggling to find a correct approach to pass data between classes, which do not directly call each other, and are only related through a parent class (which I now use, but I consider it a dirty workaround rather than anything near a solution).
I have 3 classes both able to read input and write output, and based on configuration I set one to read, another one to write. It may even be the same class, they all share a parent class, but they are always two separate instances called from a controller class.
Currently I use this sort of functionality:
class daddy {
public static $data;
}
class son extends daddy {
public function setData() {
parent::$data = "candy";
}
}
class daughter extends daddy {
public function getData() {
echo parent::$data;
}
}
while($processALineFromConfig)
$son = new son;
$son->setData();
$daughter = new daughter;
$daughter->getData();
daddy::$data = null; //reset the data, in the actual code $daughter does that in parent::
}
Instantination of these classes runs in a loop, therefore I always need to reset the data after $daughter receives them, 'cos otherwise it would stay there for another pass through the loop.
I'm absolutely sure it's not how class inheritance is supposed to be used, however I'm struggling to find a real solution. It only makes sense the data should be stored in the controller which calls these classes, not the parent, but I already use return values in the setter and getter functions, and I am not passing a variable by reference to store it there to these functions 'cos I have optional parameters there and I'm trying to keep the code clean.
What would be the correct approach to pass data through the controller then?
Thanks!
The best option would be for two object share some other, third object. This would be the class for "third object" which will ensure the exchage:
class Messenger
{
private $data;
public function store($value)
{
$this->data = $value;
}
public function fetch()
{
return $this->data;
}
}
Then a class for both instance, that will need to share some state:
class FooBar
{
private $messenger;
private $name = 'Nobody';
public function __construct($messenger, $name)
{
$this->messenger = messenger;
$this->name = $name;
}
public function setSharedParam($value)
{
$this->messenger->store($value);
}
public function getSharedParameter()
{
return $this->name . ': ' . $this->messenger->fetch();
}
}
You utilize the classes like this:
$conduit = new Messenger;
$john = new FooBar($conduit, 'Crichton');
$dominar = new FooBar($conduit, 'Rygel');
$dominar->setSharedParameter('crackers');
echo $john->getSharedParameter();
// Crichton: crackers
Basically, they both are accessing the same object. This also can be further expanded by making both instance to observe the instance of Messenger.
I have a set of classes which have a habit of being called repeatedly with the same arguments. These methods generally run database requests and build arrays of objects and such, and so to cut out this duplication I've constructed a couple of caching methods to optimise. These are used like so:
Before caching applied:
public function method($arg1, $arg2) {
$result = doWork();
return $result;
}
After caching applied:
public function method($arg1, $arg2, $useCached=true) {
if ($useCached) {return $this->tryCache();}
$result = doWork();
return $this->cache($result);
}
Unfortunately I'm now left with the slightly laborious task of manually adding this to all of the methods- I believe this is a use case of the decorator pattern but I can't figure out how to implement it in a simpler way in PHP for this case.
What's the best way to do this, hopefully such that either all methods in any of these classes automatically do this, or I just have to add one line in the method etc?
I've had a look at ways to override the return statement and such but can't really see anything.
Thanks!
If you don't need Type Safety, you can use a generic Cache Decorator:
class Cached
{
public function __construct($instance, $cacheDir = null)
{
$this->instance = $instance;
$this->cacheDir = $cacheDir === null ? sys_get_temp_dir() : $cacheDir;
}
public function defineCachingForMethod($method, $timeToLive)
{
$this->methods[$method] = $timeToLive;
}
public function __call($method, $args)
{
if ($this->hasActiveCacheForMethod($method, $args)) {
return $this->getCachedMethodCall($method, $args);
} else {
return $this->cacheAndReturnMethodCall($method, $args);
}
}
// … followed by private methods implementing the caching
You would then wrap an instance that needs caching into this Decorator like this:
$cachedInstance = new Cached(new Instance);
$cachedInstance->defineCachingForMethod('foo', 3600);
Obviously, the $cachedInstance does not have a foo() method. The trick here is to utilize the magic __call method to intercept all calls to inaccessible or non-existing methods and delegate them to the decorated instance. This way we are exposing the entire public API of the decorated instance through the Decorator.
As you can see, the __call method also contains the code to check whether there is a caching defined for that method. If so, it will return the cached method call. If not, it will call the instance and cache the return.
Alternatively, you pass in a dedicated CacheBackend to the Decorator instead of implementing the Caching in the decorator itself. The Decorator would then only work as a Mediator between the decorated instance and the backend.
The drawback of this generic approach is that your Cache Decorator will not have the type of the Decorated Instance. When your consuming code expects instances of type Instance, you will get errors.
If you need type-safe decorators, you need to use the "classic" approach:
Create an Interface of the decorated instance public API. You can do that manually or, if it's a lot of work, use my Interface Distiller)
Change the TypeHints on every method expecting the decorated instance to the Interface
Have the Decorated instance implement it.
Have the Decorator implement it and delegate any methods to the decorated instance
Modify all methods that need caching
Repeat for all classes that want to use the decorator
In a nutshell
class CachedInstance implements InstanceInterface
{
public function __construct($instance, $cachingBackend)
{
// assign to properties
}
public function foo()
{
// check cachingBackend whether we need to delegate call to $instance
}
}
The drawback is, that it is more work. You need to do that for every class supposed to use caching. You'll also need to put the check to the cache backend into every function (code duplication) as well as delegating any calls that don't need caching to the decorated instance (tedious and error prone).
Use the __call magic method.
class Cachable {
private $Cache = array();
public function Method1(){
return gmstrftime('%Y-%m-%d %H:%M:%S GMT');
}
public function __call($Method, array $Arguments){
// Only 'Cached' or '_Cached' trailing methods are accepted
if(!preg_match('~^(.+)_?Cached?$~i', $Method, $Matches)){
trigger_error('Illegal Cached method.', E_USER_WARNING);
return null;
}
// The non 'Cached' or '_Cached' trailing method must exist
$NotCachedMethod = $Matches[1];
if(!method_exists($this, $NotCachedMethod)){
trigger_error('Cached method not found.', E_USER_WARNING);
return null;
}
// Rebuild if cache does not exist or is too old (5+ minutes)
$ArgumentsHash = md5(serialize($Arguments)); // Each Arguments product different output
if(
!isset($this->Cache[$NotCachedMethod])
or !isset($this->Cache[$NotCachedMethod][$ArgumentsHash])
or ((time() - $this->Cache[$NotCachedMethod][$ArgumentsHash]['Updated']) > (5 * 60))
){
// Rebuild the Cached Result
$NotCachedResult = call_user_func_array(array($this, $NotCachedMethod), $Arguments);
// Store the Cache again
$this->Cache[$NotCachedMethod][$ArgumentsHash] = array(
'Method' => $NotCachedMethod,
'Result' => $NotCachedResult,
'Updated' => time(),
);
}
// Deliver the Cached result
return $this->Cache[$NotCachedMethod][$ArgumentsHash]['Result'];
}
}
$Cache = new Cachable();
var_dump($Cache->Method1());
var_dump($Cache->Method1Cached()); // or $Cache->Method1_Cached()
sleep(5);
var_dump($Cache->Method1());
var_dump($Cache->Method1Cached()); // or $Cache->Method1_Cached()
This is used using internal storage but you can use the DB for this and create your own Transient storage. Just append _Cached or Cached to any method that exists. Obviously, you can change the lifespan and more.
This is just proof of concept. There's room for much improvement :)
Here is an extract from an article around the subject of caching in php
/**
* Caching aspect
*/
class CachingAspect implements Aspect
{
private $cache = null;
public function __construct(Memcache $cache)
{
$this->cache = $cache;
}
/**
* This advice intercepts the execution of cacheable methods
*
* The logic is pretty simple: we look for the value in the cache and if we have a cache miss
* we then invoke original method and store its result in the cache.
*
* #param MethodInvocation $invocation Invocation
*
* #Around("#annotation(Annotation\Cacheable)")
*/
public function aroundCacheable(MethodInvocation $invocation)
{
$obj = $invocation->getThis();
$class = is_object($obj) ? get_class($obj) : $obj;
$key = $class . ':' . $invocation->getMethod()->name;
$result = $this->cache->get($key);
if ($result === false) {
$result = $invocation->proceed();
$this->cache->set($key, $result);
}
return $result;
}
}
Makes more sense to me as it delivers in a SOLID implementation way.
I am not a huge fan of implementing the same with annotations, would prefer something simpler.
I have an helper class with some static functions. All the functions in the class require a ‘heavy’ initialization function to run once (as if it were a constructor).
Is there a good practice for achieving this?
The only thing I thought of was calling an init function, and breaking its flow if it has already run once (using a static $initialized var). The problem is that I need to call it on every one of the class’s functions.
Sounds like you'd be better served by a singleton rather than a bunch of static methods
class Singleton
{
/**
*
* #var Singleton
*/
private static $instance;
private function __construct()
{
// Your "heavy" initialization stuff here
}
public static function getInstance()
{
if ( is_null( self::$instance ) )
{
self::$instance = new self();
}
return self::$instance;
}
public function someMethod1()
{
// whatever
}
public function someMethod2()
{
// whatever
}
}
And then, in usage
// As opposed to this
Singleton::someMethod1();
// You'd do this
Singleton::getInstance()->someMethod1();
// file Foo.php
class Foo
{
static function init() { /* ... */ }
}
Foo::init();
This way, the initialization happens when the class file is included. You can make sure this only happens when necessary (and only once) by using autoloading.
Actually, I use a public static method __init__() on my static classes that require initialization (or at least need to execute some code). Then, in my autoloader, when it loads a class it checks is_callable($class, '__init__'). If it is, it calls that method. Quick, simple and effective...
NOTE: This is exactly what OP said they did. (But didn't show code for.) I show the details here, so that you can compare it to the accepted answer. My point is that OP's original instinct was, IMHO, better than the answer he accepted.
Given how highly upvoted the accepted answer is, I'd like to point out the "naive" answer to one-time initialization of static methods, is hardly more code than that implementation of Singleton -- and has an essential advantage.
final class MyClass {
public static function someMethod1() {
MyClass::init();
// whatever
}
public static function someMethod2() {
MyClass::init();
// whatever
}
private static $didInit = false;
private static function init() {
if (!self::$didInit) {
self::$didInit = true;
// one-time init code.
}
}
// private, so can't create an instance.
private function __construct() {
// Nothing to do - there are no instances.
}
}
The advantage of this approach, is that you get to call with the straightforward static function syntax:
MyClass::someMethod1();
Contrast it to the calls required by the accepted answer:
MyClass::getInstance->someMethod1();
As a general principle, it is best to pay the coding price once, when you code a class, to keep callers simpler.
If you are NOT using PHP 7.4's opcode.cache, then use Victor Nicollet's answer. Simple. No extra coding required. No "advanced" coding to understand. (I recommend including FrancescoMM's comment, to make sure "init" will never execute twice.) See Szczepan's explanation of why Victor's technique won't work with opcode.cache.
If you ARE using opcode.cache, then AFAIK my answer is as clean as you can get. The cost is simply adding the line MyClass::init(); at start of every public method. NOTE: If you want public properties, code them as a get / set pair of methods, so that you have a place to add that init call.
(Private members do NOT need that init call, as they are not reachable from the outside - so some public method has already been called, by the time execution reaches the private member.)
There is a way to call the init() method once and forbid it's usage, you can turn the function into private initializer and ivoke it after class declaration like this:
class Example {
private static function init() {
// do whatever needed for class initialization
}
}
(static function () {
static::init();
})->bindTo(null, Example::class)();
I am posting this as an answer because this is very important as of PHP 7.4.
The opcache.preload mechanism of PHP 7.4 makes it possible to preload opcodes for classes. If you use it to preload a file that contains a class definition and some side effects, then classes defined in that file will "exist" for all subsequent scripts executed by this FPM server and its workers, but the side effects will not be in effect, and the autoloader will not require the file containing them because the class already "exists". This completely defeats any and all static initialization techniques that rely on executing top-level code in the file that contains the class definition.
If you don't like public static initializer, reflection can be a workaround.
<?php
class LanguageUtility
{
public static function initializeClass($class)
{
try
{
// Get a static method named 'initialize'. If not found,
// ReflectionMethod() will throw a ReflectionException.
$ref = new \ReflectionMethod($class, 'initialize');
// The 'initialize' method is probably 'private'.
// Make it accessible before calling 'invoke'.
// Note that 'setAccessible' is not available
// before PHP version 5.3.2.
$ref->setAccessible(true);
// Execute the 'initialize' method.
$ref->invoke(null);
}
catch (Exception $e)
{
}
}
}
class MyClass
{
private static function initialize()
{
}
}
LanguageUtility::initializeClass('MyClass');
?>
Some tests of assigning static public properties :
settings.json :
{
"HOST": "website.com",
"NB_FOR_PAGINA": 8,
"DEF_ARR_SIZES": {
"min": 600,
"max": 1200
},
"TOKEN_TIME": 3600,
"WEBSITE_TITLE": "My website title"
}
now we want to add settings public static properties to our class
class test {
/** prepare an array to store datas */
public static $datas = array();
/**
* test::init();
*/
public static function init(){
// get json file to init.
$get_json_settings =
file_get_contents(dirname(__DIR__).'/API/settings.json');
$SETTINGS = json_decode($get_json_settings, true);
foreach( $SETTINGS as $key => $value ){
// set public static properties
self::$datas[$key] = $value;
}
}
/**
*
*/
/**
* test::get_static_properties($class_name);
*
* #param {type} $class_name
* #return {log} return all static properties of API object
*/
public static function get_static_properties($class_name) {
$class = new ReflectionClass($class_name);
echo '<b>infos Class : '.$class->name.'</b><br>';
$staticMembers = $class->getStaticProperties();
foreach( $staticMembers as $key => $value ){
echo '<pre>';
echo $key. ' -> ';
if( is_array($value) ){
var_export($value);
}
else if( is_bool($value) ){
var_export($value);
}
else{
echo $value;
}
echo '</pre>';
}
// end foreach
}
/**
* END test::get_static_properties();
*/
}
// end class test
ok now we test this code :
// consider we have the class test in API folder
spl_autoload_register(function ($class){
// call path to API folder after
$path_API = dirname(__DIR__).'/API/' . $class . '.php';
if( file_exists($path_API) ) require $path_API;
});
// end SPL auto registrer
// init class test with dynamics static properties
test::init();
test::get_static_properties('test');
var_dump(test::$HOST);
var_dump(test::$datas['HOST']);
this return :
infos Class : test
datas -> array (
'HOST' => 'website.com',
'NB_FOR_PAGINA' => 8,
'DEF_ARR_SIZES' =>
array (
'min' => 600,
'max' => 1200,
),
'TOKEN_TIME' => 3600,
'WEBSITE_TITLE' => 'My website title'
)
// var_dump(test::$HOST);
Uncaught Error: Access to undeclared static property:
test::$HOST
// var_dump(test::$datas['HOST']);
website.com
Then if we modify the class test like this :
class test {
/** Determine empty public static properties */
public static $HOST;
public static $NB_FOR_PAGINA;
public static $DEF_ARR_SIZES;
public static $TOKEN_TIME;
public static $WEBSITE_TITLE;
/**
* test::init();
*/
public static function init(){
// get json file to init.
$get_json_settings =
file_get_contents(dirname(__DIR__).'/API/settings.json');
$SETTINGS = json_decode($get_json_settings, true);
foreach( $SETTINGS as $key => $value ){
// set public static properties
self::${$key} = $value;
}
}
/**
*
*/
...
}
// end class test
// init class test with dynamics static properties
test::init();
test::get_static_properties('test');
var_dump(test::$HOST);
this return :
infos Class : test
HOST -> website.com
NB_FOR_PAGINA -> 8
DEF_ARR_SIZES -> array (
'min' => 600,
'max' => 1200,
)
TOKEN_TIME -> 3600
WEBSITE_TITLE -> My website title
// var_dump(test::$HOST);
website.com
I actually need to initialize an object with public static properties that I will reuse in many other classes, which I think is supposed to, I don't want to do new api() in every method where I would need, for example to check the host of the site or indicate it. Also I would like to make things more dynamic so that I can add as many settings as I want to my API, without having to declare them in my initialization class.
All other methods I've seen no longer work under php > 7.4
I keep looking for a solution for this problem.
Note - the RFC proposing this is still in the draft state.
class Singleton
{
private static function __static()
{
//...
}
//...
}
proposed for PHP 7.x (see https://wiki.php.net/rfc/static_class_constructor )