Php Memcache Design Patterns issue - php

For a small home project I am working on I have been looking for OO design patterns for Memcache implementation, but so far haven't found something I feel fits, so maybe my approach is wrong.
I have a DB connection class and an baseModel class so I want to implement caching on the baseModel where appropriate.
I have implemented the Database connection and the Cacher as Singlton patterns.
I cannot seem to get the Cacher class to read the data or trigger the echo "<p>Getting from cache"; line after I refresh the page on the base Model "loadFromDb" function
Here are the classes:
class Cacher {
protected static $cacher = null;
private static $settings;
public static function getCache() {
if (self::$cacher != null) {
return self::$cacher;
}
try {
self::$settings = parse_ini_file("./configs/main.ini");
self::$cacher = new Memcache();
self::$cacher->connect(
self::$settings['cache_server_host']
, self::$settings['cache_server_port']
);
} catch (Exception $e) {
// TODO log error and mitigate..
echo "Error connecting memcache";
die();
}
var_dump(self::$cacher->getstats());
return self::$cacher;
}
public static function getData($key) {
if (self::$cacher == null) {
self::getCache();
}
return self::$cacher->get($key);
}
public static function setData($key, $data, $expire = 0) {
if (self::$cacher == null) {
self::getCache();
}
if (self::$cacher)
return self::$cacher->set($key, $data, MEMCACHE_COMPRESSED, $expire);
}
}
class ModelBase {
protected $fields = array();
protected $class = null;
function __construct($class_name) {
$this->class = $class_name;
$this->fields = Database::getFields($class_name);
}
public function loadFromDB($id, $fromCache = true) {
$key = "loadFromDB_{$this->class}_{$id}";
if ($fromCache) {
$data = Cacher::getData($key);
if ($data) {
echo "<p>Getting from cache";
return unserialize($data);
} else {
echo "<p>No cache data. going to DB";
}
}
$values = Database::loadByID($this->class, $this->fields[0], $id);
foreach ($values as $key => $val) {
$this->$key = $val;
}
$dataSet = Cacher::setData($key, serialize($this));
echo "<p>Data set = $dataSet";
}
}
Memcache service is running and I can read data directly back if I read the cache directly after I write it, but what I want is to read the data from the DB only the first time the page loads, after that use the cache....
Any comments welcome...

Try doing it like (let the result of the cache decide if you should query the db):
<?php
public function loadFromDB($id) {
$key = "loadFromDB_{$this->class}_{$id}";
//query cache
$data = Cacher::getData($key);
//is it found
if (!empty($data)) {
//echo "<p>Getting from cache";
return unserialize($data);
}
//no so lest query db
else {
//echo "<p>No cache data. going to DB";
$values = Database::loadByID($this->class, $this->fields[0], $id);
foreach ($values as $key => $val) {
$this->$key = $val;
}
//store it in cache
//$dataSet = Cacher::setData($key, serialize($this));
$dataSet = Cacher::setData($key, serialize($values));//<<store the db result not the class
}
//echo "<p>Data set = $dataSet";
return unserialize($dataSet);
}
?>

Related

How to use a array in a class which keeps it values and is acessable from outside the class

I am using php 7.4.9 and have a class which reads information from a file. These informations should be all the time availabe from outside the class and it also should possible to modify that array, so that this class can write back these information on request.
I have looked for a while but could not fined a useful solution.
I got the functions working, but the array loose the values from call to call.
Edit 2020/12/12
This is the uses structure of my code
<?php
.......
function show(){
$id3 = ID3::create();
$mp3 = &ID3::$mp3Array;
if($mode == "manual"){
if($file == ""){
return "";
}
$fName = $dir . "/" . $file;
$id3->open($fName);
.......
}else if($mode == "save"){
$fName = $dir . "/" . $file;
$id3->save($fName);
return "Save done!";
}
} // end of show
class ID3{
public static $mp3Array = array();
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
function open($fName){
$mp3 = self::$mp3Array;
. // $mp3 will be filled
.........
}
function save($fName) {
$mp3 = &ID3::§mp3Array;
error_log("TagSave: ".var_export($mp3, true),0); // is always empty
foreach($mp3 as $key => $value){
........
}
}
} //end of class>
?>
If I try to save the modified array, it is always empty, if show is called again!
I have also implemented the #Logifire proposal 'create'. I got a valid pointer but the arrayis still empty.
Maybe I should point out, that it is web page. The html code sends information (form) back to the php program.
I figured out, that use of global $id3 = NULL; does not work, because the php grogramm will be always called and set the variable again to NULL each time.
I have also implemented the following code on the beginning
<?php
error_log("PHP call",0);
$id3count = 0;
if(array_key_exists("Test_id3",$GLOBALS)){
error_log("GLOBALS[Test_id3] exist!",0);
}else{
error_log("GLOBALS[Test_id3] does not exist!",0);
$GLOBALS['Test_id3'] = "NEW";
}
The $GLOBAL['Test_id3'] never exist, if the programm will be called!
I got the functions working, but the array loose the values from call to call.
As I understand you, your setup is not a long running app, you can not keep state between requests (calls).
But if you are aware of that, the issue may be you have a new instance of the class each time you call it within the same request flow, you may use a singleton if this is the case. I suggest using accessors in your class.
class MyDataList {
private array $my_array = [];
private function __construct()
{
}
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
public function setArray(array $new_array): void {
$this->my_array = $new_array;
// open, write, close file..
}
public function getArray(): array {
return $this->my_array;
}
}
$my_data_list = MyDataList::create();
Based on your edited question (2020/12/12), I extended the example code:
class MyDataList {
private array $my_array = [];
private $file_path = '';
private function __construct()
{
}
public static function create(string $file_path): self {
static $object;
if ($object === null) {
$object = new self();
$stringified = file_get_contents($file_path) ?: '';
$array = json_decode($stringified, true) ?: [];
$object->file_path = $file_path;
$object->my_array = $array;
}
return $object;
}
public function setArray(array $new_array): void {
$this->my_array = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_path, $stringified);
}
public function getArray(): array {
return $this->my_array;
}
}
$my_data_list = MyDataList::create('/path/to/file');
Note: Be aware, you need to apply error handling
Comment answers:
Is the filepath connected to the array?
Well, you will write your data as JSON to a file each time you "modify" the array via the setArray()
Does it means, that the array is stored into a file and read out each time I try to connect again?
For each request you call create() it will instantiate the internal state of the array based on the stored data in the file. ATM. The file_get_contents call may have been wrapped and only called if the $object was not instantiated. (Now updated in the example)
So I have to call setArray($array); to save the data. I was looking for a soluting to keep the data without an management to save and read the array. Is this not possible with PHP?
Maybe you want to use a session variable to store your data? But it is individual per user and not long lived data - Link: https://www.php.net/manual/en/reserved.variables.session.php
In a standard PHP setup you can not have data/state between requests, but there are solution like Swoole which makes PHP a long running app: https://www.php.net/manual/en/book.swoole.php
I need a possibility to modify the array directly.
Is it a reference to the array you want? https://3v4l.org/OsBC6
class MyDataList {
private array $my_array = [];
private function __construct()
{
}
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
public function setArray(array &$new_array): void {
$this->my_array = &$new_array;
}
public function getArray(): array {
return $this->my_array;
}
}
There is no easy way to do with PHP!
Finally I use the proposal from Logifire, but had to modified it to fullfill my requirements.
I needed more than 1 array.
One array can ibclude binary data values, which json can't handle. So I have to use base64 for the binary data values.
Here my code:
public array $mp3Array = array();
public array $findArray = array();
private $file_dir = "";
public static function create(string $fileDir): self {
static $object;
if ($object === null) {
$object = new self();
$stringified1 = file_get_contents($fileDir."/mp3Array.obj") ?: '';
$array1 = json_decode($stringified1, true) ?: [];
$stringified2 = file_get_contents($fileDir."/findArray.obj") ?: '';
$array2 = json_decode($stringified2, true) ?: [];
$object->file_dir = $fileDir;
$object->mp3Array = $object->arrayDecode($array1);
$object->findArray = $array2;
}
return $object;
}
private function arrayEncode($arr){
$tmp = [];
foreach($arr as $key => $val){
if(is_array($val)){
$tmp[$key] = $this->arrayEncode($val);
}else if ($key == "data"){
$tmp[$key] = base64_encode($val);
}else{
$tmp[$key] = $val;
}
}
return $tmp;
}
private function arrayDecode($arr){
$tmp = [];
foreach($arr as $key => $val){
if(is_array($val)){
$tmp[$key] = $this->arrayDecode($val);
}else if ($key == "data"){
$tmp[$key] = base64_decode($val);
}else{
$tmp[$key] = $val;
}
}
return $tmp;
}
public function setMp3(array $new_array): void {
$this->mp3Array = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_dir."/mp3Array.obj", $stringified);
}
public function saveMp3(): void {
$base64 = $this->arrayEncode($this->mp3Array);
$stringified = json_encode($base64);
file_put_contents($this->file_dir."/mp3Array.obj", $stringified);
}
public function setFind(array $new_array): void {
$this->findArray = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_dir."/findArray.obj", $stringified);
}
public function saveFind(): void {
$stringified = json_encode( $this->findArray);
file_put_contents($this->file_dir."/findArray.obj", $stringified);
}

Dirty Methods in Rails equivalent in PHP Codeignitor

I am new to PHP & Codeigniter but it was needed some kind of implementation in PHP.
Following are dirty methods are provided in rails framework by default, here person is model object representing row inside persons table.
person.name = 'Bob'
person.changed? # => true
person.name_changed? # => true
person.name_changed?(from: nil, to: "Bob") # => true
person.name_was # => nil
person.name_change # => [nil, "Bob"]
person.name = 'Bill'
person.name_change # => [nil, "Bill"]
I am interested in to & from specially, Please suggest whether it is possible with any way.
If you would consider Laravel's elquent framework you have a great deal of that functionality already.
Laravel Eloquent update just if changes have been made
It holds an array of the "original" values in the Model, and if any of them have been changed it will commit them to the database.
They also come with a lot of events you can plug into(beforeSave, afterSave, beforeCreate, afterCreate, validation rules, etc...) and they can be extended easily. It might be the closest compatible thing I can imagine you're looking for.
This is however not codeigniter, it relies on a different framework. If you're not dead set on codeigniter you might consider switching to a framework like Laravel or OctoberCMS depending on your needs.
Edit because you're stuck with codeigniter
You might wish to use a library like this one: https://github.com/yidas/codeigniter-model
It is then very easy to extend with some custom caching mechanisms.
The code below is something you could use as a basis for your own model implementation.
It has a very rudementary logic basis, but allows you to check on the dirty status and roll back any changes made to the model.
Note this this is very rudementary, and might even contain a few errors because I have not run this code. it's more a proof of concept to help you create a model that suits your needs.
class User extends CI_Model
{
public $table = 'users';
public $primaryKey = 'id';
public $attributes;
public $original;
public $dirty = [];
public $exists = false;
function __construct()
{
parent::Model();
}
public static function find($model_id)
{
$static = new static;
$query = $static->db->query("SELECT * FROM ' . $static->getTable() . ' WHERE ' . $this->getKeyName() . ' = ?", [$model_id]);
if($result->num_rows()) {
$static->fill($query->row_array());
$static->exists = true;
}
else {
return null;
}
return $static;
}
public function getKeyName()
{
return $this->primaryKey;
}
public function getKey()
{
return $this->getAttribute($this->getKeyName());
}
public function getTable()
{
return $this->table;
}
public function fill($attributes)
{
if(is_null($this->original)) {
$this->original = $attributes;
$this->dirty = $attributes;
}
else {
foreach($attributes as $key => $value) {
if($this->original[$key] != $value) {
$this->dirty[$key] = $value;
}
}
}
$this->attributes = $attributes;
}
public function reset()
{
$this->dirty = [];
$this->attributes = $this->original;
}
public function getAttribute($attribute_name)
{
return array_key_exists($attribute_name, $this->attributes) ? $this->attributes[$attribute_name] : null;
}
public function __get($key)
{
return $this->getAttribute($key);
}
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}
public function setAttribute($key, $value)
{
if(array_key_exists($key, $this->original)) {
if($this->original[$key] !== $value) {
$this->dirty[$key] = $value;
}
}
else {
$this->original[$key] = $value;
$this->dirty[$key] = $value;
}
$this->attributes[$key] = $value;
}
public function getDirty()
{
return $this->dirty;
}
public function isDirty()
{
return (bool)count($this->dirty);
}
public function save()
{
if($this->isDirty()) {
if($this->exists)
{
$this->db->where($this->getKeyName(), $this->getKey());
$this->db->update($this->getTable(), $this->getDirty());
$this->dirty = [];
$this->original = $this->attributes;
}
else
{
$this->db->insert($this->getTable(), $this->getDirty());
$this->dirty = [];
$this->original = $this->attributes;
$this->attributes[$this->getKeyName()] = $this->db->insert_id();
$this->original[$this->getKeyName()] = $this->getKey();
$this->exists = true;
}
}
}
}
if($user = User::find(1)) {
$user->name = "Johnny Bravo";
$user->save();
}

Opinion about using SESSION with PHP

I need opinion about using session with php. I'm useing session to store data. For instance - configuration:
First I'm loading data from config.ini to $_SESSION['config']
Then I'm using custom session class to get specific data from $_SESSION['config'][$key];
This is config function:
public static function load_config($process_sections = FALSE)
{
$array = parse_ini_file(CONFIG . DS . 'config.ini', $process_sections);
if ($array)
{
$_SESSION['configuration'] = $array;
return TRUE;
}
else
{
$_SESSION['configuration'] = array();
return FALSE;
}
}
function config($key, $default = NULL)
{
if (isset($_SESSION['configuration']))
{
if (isset($_SESSION['configuration'][$key]))
{
return $_SESSION['configuration'][$key];
}
else
{
return $default;
}
}
}
That same is with user object. I'm getting user data not from DB, but API, and storing it in $_SESSION['user']. When user object is constructs, I'm attributing all properties just from $_SESSION['user'][...], for instance:
public function __construct()
{
parent::__construct();
$this->initUser();
}
private function initUser()
{
if (Session::is('user'))
{
return $this->setUserData(Session::get('user'));
}
else
{
return FALSE;
}
}
private function setUserData($data)
{
foreach ($data as $key => $value)
{
if(property_exists($this->_name, $key))
{
$this->{$key} = $value;
}
}
return TRUE;
}
Properties are defined in class. I'm doing it just on time, when object is constructing. Is that right practice? It's working very good for me but I doubt if my method overolads server.

PHP return NULL from __construct

I'm trying to write some classes which pull my data out of my database and create objects based on that data. I'm working with CodeIgniter.
The problem is that if an id is supplied to the __construct method but no row in the database table has that id, an object is returned but with all the properties set to NULL.
Is there a way that I can check this and return NULL instead of an object if there is no corresponding row?
class JS_Model extends CI_Model
{
protected $database_table_name;
protected $database_keys;
...
public function __construct($id = NULL){
if($id){
$this->getFromDatabase($id);
}
}
...
function getFromDatabase($id){
foreach($this->database_keys as $key){
$this->db->select($key);
}
$this->db->from($this->database_table_name);
$this->db->where('id', $id);
$this->db->limit(1);
$q = $this->db->get();
if ($q->num_rows() > 0){
foreach($q->result() as $property){
foreach($property as $key => $value){
$this->$key = $value;
}
}
} else {
// NEED TO SET THE OBJECT TO NULL FOR THIS CASE
}
}
...
}
Any help would be appreciated.
This is not possible, once __construct is called, you will receive an object instance.
The correct way to handle this is to throw an Exception from inside the constructor.
class JS_Model extends CI_Model
{
protected $database_table_name;
protected $database_keys;
public function __construct($id = NULL){
if($id){
$row = $this->getFromDatabase($id);
if (!$row) {
throw new Exception('requested row not found');
}
}
}
}
try {
$record = new JS_Model(1);
} catch (Exception $e) {
echo "record could not be found";
}
// from here on out, we can safely assume $record holds a valid record
why dont you make your constructor private and use a method such as GetInstance(); something like this. Syntax may be incorrect as i dont write much php anymore :-(.
class JS_Model extends CI_Model
{
protected $database_table_name;
protected $database_keys;
...
private function __construct($id = NULL){
}
public static function GetInstance($id)
{
$x = new JS_Model($id);
$x->getFromDatabase($id);
if(is_object($x))
{
return $x;
}
return null;
}
...
function getFromDatabase($id){
foreach($this->database_keys as $key){
$this->db->select($key);
}
$this->db->from($this->database_table_name);
$this->db->where('id', $id);
$this->db->limit(1);
$q = $this->db->get();
if ($q->num_rows() > 0){
foreach($q->result() as $property){
foreach($property as $key => $value){
$this->$key = $value;
}
}
} else {
return null;
}
}
...
}

Is it possible to make a names of the tables in the database as a methods of my class?

I am very like to use PDO, so it might be on.
Usage could be as:
$m = new MDB();
$m->Users()->GetRec($param);
Users() is a name of table in the database, GetRec($param) is my function.
A way I go looking like this:
class MDB extends DB {
function __construct(){
parent::__construct();
if ($result = $this->pdo->query("SHOW TABLES"))
{
while ($row = $result->fetch(PDO::FETCH_NUM))
{
// this is only my imagination (not working at all)
__set($row[0],true);
}
}
}
// set
public function __set($name, $value)
{
// here could be a method (not properties)
$this->$name = $value;
}
Sure it's all seems not exactly what I want. So I am able to get some suggestions and advices in this question.
upd 1.
Thanks for the magic method __call and now I'm trying to make it within. Watch my updated code:
class MDB extends DB {
function __construct(){
parent::__construct();
}
public function __call( $method, $param )
{
$tables = array();
if ($result = $this->pdo->query("SHOW TABLES"))
{
while ($row = $result->fetch(PDO::FETCH_NUM))
{
$tables[] = $row[0];
}
}
if (in_array($method,$tables))
{
return $this;
}
else
{
return FALSE;
}
}
Well, seems it works for me!
Yes it's possible! The wrong row in your code is:
__set($row[0],true);
There you should call:
$this->$row[0] = true;
Take a look a the simple example below and at the overloading documentation of PHP http://www.php.net/manual/en/language.oop5.overloading.php.
class A{
//The array which will contain your data
private $tables;
function __construct($array){
$counter = 1;
foreach($array as $value){
//That's the right call!
$this->$value = $counter++;
}
}
function __set($name, $value){
$this->tables[$name] = $value;
}
function __get($name){
if (array_key_exists($name, $this->tables)) {
return $this->tables[$name];
}
}
}
$array = array('a1', 'a2', 'a3', 'a4', 'a5');
$a = new A($array);
foreach($array as $key){
echo $a->$key;
}
Hope this help!

Categories