Background :
I am trying to create a flat file key value store. When i use the code below the array that has been written to the file gets deleted and i end up with an empty file. On the next request the file gets populated with the array. This process loops itself on every page request.
What i have tried :
Various methods with "json_encode()"/"json_decode()".
Also tried to do the same with "serialize()"/"unserialize()".
Tried various coding structures with "array_merge()" and "array_merge_recursive()".
Nothing seems to work! I end up with the same loop of an empty file -> file with array and it continues
Question :
Can someone more experienced tell me what i am doing wrong?
code :
/**
* Class Orm
*/
class Orm
{
/**
* #var string The Root database directory with a trailing slash. default is "./database/".
*/
static $dir = "./database/";
/**
* #var array
*/
protected $data;
/**
* #var string
*/
protected $file = "";
/**
* #param $file
* #return string
*/
public function load_table($file)
{
try {
if (file_exists(self::$dir . $file)) {
return $this->file = $file;
} else {
throw new Exception("The file " . $file . " cannot be created, because it already exists");
}
} catch (Exception $error) {
return $error->getMessage();
}
}
/**
* #param String
* #param array $values An associative array of values to store.
* #return array
*/
public function set($key, $values = array())
{
try{
if (!empty($key) && !empty($values)){
return $this->data[$key] = $values;
} else {
throw new Exception();
}
} catch (Exception $error){
return $error->getMessage();
}
}
public function save()
{
try{
if (file_exists(self::$dir . $this->file)) {
if (filesize(self::$dir . $this->file) == 0)
{
file_put_contents(self::$dir . $this->file, print_r($this->data, TRUE));
}else{
$tmp = file_get_contents(self::$dir . $this->file);
$content = array_merge($tmp, $this->data);
file_put_contents(self::$dir . $this->file, print_r($content, TRUE));
}
} else {
throw new Exception();
}
} catch(Exception $error){
return $error->getMessage();
}
}
}
$user = new Orm();
$user->load_table("users");
$user->set("Tito",array("age" => "32", "occupation" => "cont"));
$user->save();
PS : I thought this would be a nice project to familiarize myself with Php. So please do not advise to use SQL as this is for learning and understanding Php only.
I can not say why your code fails to do that or not. However I would create another object as well that takes care of loading and saving a string to disk. Nothing more and nothing less:
class StringStore
{
private $path;
public function __construct($path) {
$this->path = $path;
}
/**
* #return string
*/
public function load() {
... move your load code in here
return $buffer;
}
/**
* #param string $buffer
*/
public function save($buffer) {
... move your save code in here
}
}
That might look a bit less, however you can move a larger part of code out of the ORM class. If the problem is with storing to disk (e.g. maybe some mistake made?) you can fix it in the store class then.
If the mistake has been made in the string processing, then you know you need to look in the ORM class instead to continue fixing.
Related
I am using a library, and it has the following process to attach many operations to one event:
$action = (new EventBuilder($target))->addOperation($Operation1)->addOperation($Operation2)->addOperation($Operation3)->compile();
I am not sure how to dynamically add operations depending on what I need done.
Something like this
$action = (new EventBuilder($target));
while (some event) {
$action = $action->addOperation($OperationX);
}
$action->compile();
I need to be able to dynamically add operations in while loop and when all have been added run it.
Your proposed solution will work. The EventBuilder provides what is known as a Fluent Interface, which means that there are methods that return an instance of the builder itself, allowing you to chain calls to addOperation as many times as you want, then call the compile method to yield a result. However you are free to ignore the return value of addOperation as long as you have a variable containing an instance of the builder that you can eventually call compile on.
Take a walk with me...
// Some boilerplate classes to work with
class Target
{
private ?string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
class Operation
{
private ?string $verb;
public function __construct(string $verb)
{
$this->verb = $verb;
}
public function getVerb(): string
{
return $this->verb;
}
}
class Action
{
private ?Target $target;
private array $operations = [];
public function __construct(Target $target, array $operations)
{
$this->target = $target;
$this->operations = $operations;
}
/**
* Do the things
* #return array
*/
public function run(): array
{
$output = [];
foreach ($this->operations as $currOperation)
{
$output[] = $currOperation->getVerb() . ' the ' . $this->target->getName();
}
return $output;
}
}
Here is a basic explanation of what your EventBuilder is doing under the covers:
class EventBuilder
{
private ?Target $target;
private array $operations = [];
public function __construct(Target $target)
{
$this->target = $target;
}
/**
* #param Operation $operation
* #return $this
*/
public function addOperation(Operation $operation): EventBuilder
{
$this->operations[] = $operation;
// Fluent interface - return a reference to the instance
return $this;
}
public function compile(): Action
{
return new Action($this->target, $this->operations);
}
}
Let's try both techniques and prove they will produce the same result:
// Mock some operations
$myOperations = [
new Operation('Repair'),
new Operation('Clean'),
new Operation('Drive')
];
// Create a target
$target = new Target('Car');
/*
* Since the EventBuilder implements a fluent interface (returns an instance of itself from addOperation),
* we can chain the method calls together and just put a call to compile() at the end, which will return
* an Action instance
*/
$fluentAction = (new EventBuilder($target))
->addOperation($myOperations[0])
->addOperation($myOperations[1])
->addOperation($myOperations[2])
->compile();
// Run the action
$fluentResult = $fluentAction->run();
// Traditional approach, create an instance and call the addOperation method as needed
$builder = new EventBuilder($target);
// Pass our mocked operations
while (($currAction = array_shift($myOperations)))
{
/*
* We can ignore the result from addOperation here, just keep calling the method
* on the builder variable
*/
$builder->addOperation($currAction);
}
/*
* After we've added all of our operations, we can call compile on the builder instance to
* generate our Action.
*/
$traditionalAction = $builder->compile();
// Run the action
$traditionalResult = $traditionalAction->run();
// Verify that the results from both techniques are identical
assert($fluentResult == $traditionalResult, 'Results from both techniques should be identical');
// Enjoy the fruits of our labor
echo json_encode($traditionalResult, JSON_PRETTY_PRINT).PHP_EOL;
Output:
[
"Repair the Car",
"Clean the Car",
"Drive the Car"
]
Rob Ruchte thank you for detailed explanation, one thing I did not include was that each operation itself had ->build() call and I needed to move that to each $builder for it to work.
Into my project in Codeigniter 4 I want to delete the record from database as well as I want to unlink the file present into that record but it gives me the error of
Trying to access array offset on value of type null
Till now I have tried this but its not working according to what do I want, below is my code
public function delete($id = null)
{
$model = new AbcModel();
try{
$id=$this->request->getPost('id');
$this->record = $model->where('id', $id)->first();
$image = $this->record['image'];
if(unlink('.'.$image)){
$model->where('id', $id)->delete();
echo "delete";
}
}
catch(\Exception $e){
echo $e->getMessage();
}
}
}
My Model
<?php namespace App\Models;
class AbcModel extends MyModel
{
protected $table = 'abc';
protected $primaryKey = 'id';
}
Into the database I have saved the file as /public/uploads/abcfolder/Tulips.jpg
I think you have problem with your delete query not with unlinking so you are getting the error of Trying to access array offset on value of type null. So I have rectified your codes try it once
$id=$this->request->getPost('id');
$model = new CurriculumModel();
$record = $model->find($id);
$image = $record['image'];
$file='.'.$image;
if(is_file($file))
{
unlink($file);
$model->delete($id);
echo 'delete';
}
This code does works for me, I am not sure whether it will work for you or not but give it a try.
so it like me
<?php namespace Modules\Common\Libraries;
use CodeIgniter\HTTP\Response;
class CustomFile
{
protected string $error;
public function __construct()
{
$this->error = '';
}
/**
* #return string
*/
public function getError(): string
{
return $this->error;
}
public function removeSingleFile( $path)
{
try {
if (file_exists($path)) {
unlink($path);
}
} catch (\Exception $e) {
$this->error = $e->getMessage();
}
}
}
in your ctl or service
/**
* edit function
* #method : DELETE with params ID
* #param $id
* #param $foreignKey
*/
public function delete($id)
{
$cfs = new CustomFile();
$model = new MyModel();
$deleteById = $model->where(['id' => $id])->findAll();
if (is_null($deleteById)) $this->httpException(lang('Shared.api.exist'), ResponseInterface::HTTP_NOT_FOUND);
$model->where(['id' => $id])->delete();
foreach ($deleteById as $path) {
$cfs->removeSingleFile(ROOTPATH . $path->path);
}
}
I want to create GUI for failed_jobs and associate them with other tables record. So user could know which jobs property value failed during job handle and could be retried.
Laravel Job has a function failed(\Exception $exception) but it is called after the exception but before the record is saved in the failed_jobs table.
Also Laravel has Queue:failing(FailedJob $job) event but there I have only serialized job but not failed_jobs.
Did anyone run into similar problem? Is there any relation with processed job and failed one?
After much fussing, I accomplished this by embedding the related model within the Exception that is stored to the database. You could easily do similar but store only the id within the exception and use it to look up the model later. Up to you...
Wherever the exception that fails the job occurs:
try {
doSomethingThatFails();
} catch (\Exception $e) {
throw new MyException(OtherModel::find($id), 'Some string error message.');
}
app/Exceptions/MyException.php:
<?php
namespace App\Exceptions;
use App\Models\OtherModel;
use Exception;
class MyException extends Exception
{
/**
* #var OtherModel
*/
private $otherModel = null;
/**
* Construct
*/
public function __construct(OtherModel $otherModel, string $message)
{
$this->otherModel = $otherModel;
parent::__construct(json_encode((object)[
'message' => $message,
'other_model' => $otherModel,
]));
}
/**
* Get OtherModel
*/
public function getOtherModel(): ?object
{
return $this->otherModel;
}
}
That will store a string containing an object to the exception column on the failed_jobs table. Then you just need to decode that later on...
app/Models/FailedJob (or just app/FailedJob):
/**
* Return the human-readable message
*/
protected function getMessageAttribute(): string
{
if (!$payload = $this->getPayload()) {
return 'Unexpected error.';
}
$data = $this->decodeMyExceptionData();
return $data->message ?? '';
}
/**
* Return the related record
*/
protected function getRelatedRecordAttribute(): ?object
{
if (!$payload = $this->getPayload()) {
return null;
}
$data = $this->decodeMyExceptionData();
return $data->other_model ?? null;
}
/**
* Return the payload
*/
private function getPayload(): ?object
{
$payload = json_decode($this->payload);
return $payload ?? null;
}
/**
* Return the data encoded in a WashClubTransactionProcessingException
*/
private function decodeMyExceptionData(): ?object
{
$data = json_decode(preg_replace('/^App\\\Exceptions\\\WashClubTransactionProcessingException: ([^\n]*) in.*/s', '$1', $this->exception));
return $data ?? null;
}
anywhere:
$failedJob = FailedJob::find(1);
dd([
$failedJob->message,
$failedJob->related_record,
]);
I have a simple fileupload (image upload) which resizes this image to a smaller one. To create a phpUnit test for this resize function I need a mock object which represents an image.
Is the best practice to use a 'testimage' or is there a better way??
<?php
namespace tests\control;
use SebastianBergmann\CodeCoverage\TestCase;
class FileHandlerTest extends TestCase
{
/**
* #var array
*/
private $files = [];
/**
* Put your temp image into your filesystem.
* (Not good as unit tests must work with any system,
* but my research about mocking resources gave me nothing)
*/
protected function setUp()
{
if (file_exists(dirname(__FILE__) . '/testImage.jpg')) {
try {
rmdir(dirname(__FILE__) . '/testImage.jpg');
} catch (\Exception $e) {
throw new \Exception('You have no permission to delete files in this directory:' . $e);
}
} else {
$image = imagecreate(500, 500);
try {
imagejpeg($image, dirname(__FILE__) . '/testImage.jpg');
} catch (\Exception $e) {
throw new \Exception('You have no permission to create files in this directory:' . $e);
}
}
$this->files[] = '/testImage.jpg';
}
public function testOperation()
{
$example = new \FileHandler($this->files, dirname(__FILE__));
/**
* Set here all the variable inside your file,
* your code isn't working at this moment.
* Properties to set: uploadFolder, files
*/
$example->process();
/**
* Get you new image from test dir here.
* It's yours TODO :p
*/
/** #var resource $newPicture */
$size = getimagesize($newPicture);
/**
* also count it yourself, as I can't reproduce your code
*/
$this->assertEquals('200', $size[0]);
$this->assertEquals('200', $size[1]);
}
/**
* Deleting of test images. No try/catch here as it would fire on setup
*/
protected function tearDown()
{
if (file_exists(dirname(__FILE__) . '/testImage.jpeg')) {
rmdir(dirname(__FILE__) . '/testImage.jpeg');
}
if (file_exists(dirname(__FILE__) . '/newImage.jpeg')) {
rmdir(dirname(__FILE__) . '/newImage.jpeg');
}
}
}
You still have to make some work about it. And I'd prefer not to test such things. SetUp-method's comment is about it.
I'm trying to make open source Cache library. The purpose of library is to provide the way of storing the variable (can be object, can be be array, can be anything) to files and then retrieved it back on call. (usually those variable value is result of massive database queries and calculations).
The basic aim of the project is to practice the Object Oriented Design Principle Called Solid.
If any one can indicate where i'm violating solid principle and how to fix it
I totally understand stackoverflow is not a code writing service but hey i'm making this library open source, so it would benefit our community.
So here is my file structure.
I'm new to UML so please ignore if any errors found
here is the classes implementation.
Cache
namespace library\pingle\cache;
use library\pingle\cache\config\CacheConfigurator;
use library\pingle\cache\file\FileHandler;
/**
* #property CacheReader $cache_reader
* #property CacheWriter $cache_write
*/
Class Cache {
private $config;
private $file_hander;
private $cache_reader;
private $cache_write;
private $cache_directory;
private $cache_kept_days;
private $cache_file_prams;
private $function_name;
private $file_path;
function __construct(CacheConfigurator $config) {
$this->file_hander = new FileHandler();
$this->config = $config;
list($this->cache_directory, $this->function_name, $this->cache_kept_days, $this->cache_file_prams) = $this->config->getConfig();
$this->file_path = $this->generateFileName($this->cache_file_prams);
}
public function read() {
if (is_null($this->cache_reader)) {
$this->cache_reader = new CacheReader($this->file_hander);
}
return $this->cache_reader->readCache($this->file_path);
}
public function write($data) {
if (is_null($this->cache_write)) {
$this->cache_write = new CacheWriter($this->file_hander);
}
if (!$this->file_hander->checkDirectory($this->cache_directory . "/" . $this->function_name)) {
$this->file_hander->createDirectory($this->cache_directory . "/" . $this->function_name);
}
$this->cache_write->writeCache($this->file_path, $data);
}
public function check() {
if ($this->file_hander->checkFileExits($this->file_path)) {
if (time() - filemtime($this->file_path) >= 60 * 60 * 24 * $this->cache_kept_days) {
return false;
}
return true;
} else {
return false;
}
}
private function generateFileName(Array $nameprams) {
$this->file_name = "";
$file = "CC";
foreach ($nameprams as $key => $value) {
$file .= "-$key|$value-";
}
$file .= ".bak";
return $this->cache_directory . "/" . $this->function_name . "/" . $file;
}
}
AbstractCache
<?php
namespace library\pingle\cache;
use library\pingle\cache\file\FileHandler;
abstract Class AbstractCache {
protected $file_handler;
public function __construct(FileHandler $file_handler) {
$this->file_handler = $file_handler;
}
protected function checkDirectory($path) {
//check directory exists
$dircheck = $this->file_handler->checkDirectory(dirname($path));
if ($dircheck) {
//check directory permission
if ($this->file_handler->checkPermission(dirname($path))) {
return true;
} else {
throw new \Exception("Directory ($path) Permission Error.");
}
} else {
throw new \Exception("Directory ($path) not found.");
}
}
}
CacheReader
<?php
namespace library\pingle\cache;
use library\pingle\cache\file\FileHandler;
/**
* #property FileHandler $file_handler
*/
Class CacheReader extends AbstractCache {
public function __construct(FileHandler $file_handler) {
parent::__construct($file_handler);
}
public function readCache($path) {
if ($this->checkDirectory($path)) {
//delete the file if it exits
if ($this->file_handler->checkFileExits($path)) {
return $this->file_handler->readFile($path);
} else {
throw new \Exception("File ($path) not found");
}
}
}
}
CacheWriter
<?php
namespace library\pingle\cache;
use library\pingle\cache\file\FileHandler;
/**
* #property FileHandler $file_handler
*/
Class CacheWriter extends AbstractCache {
public function __construct(FileHandler $file_handler) {
parent::__construct($file_handler);
}
function writeCache($path, $data) {
if ($this->checkDirectory($path)) {
//delete the file if it exits
if ($this->file_handler->checkFileExits($path)) {
$this->file_handler->deleteFile($path);
}
//write cache
$this->file_handler->writeFile($path, $data);
}
}
}
FileHandler
<?php
namespace library\pingle\cache\file;
Class FileHandler {
public function writeFile($path, $data) {
$content = serialize($data);
file_put_contents($path, $content);
}
public function createDirectory($path) {
mkdir($path);
}
public function deleteFile($path) {
unlink($path);
}
public function checkDirectory($path) {
if (file_exists($path)) {
return true;
} else {
return false;
}
}
public function checkPermission($path) {
if (is_writable($path)) {
return true;
} else {
return false;
}
}
public function checkFileExits($path) {
if (is_file($path)) {
return true;
}
return false;
}
public function readFile($path) {
return unserialize(file_get_contents($path));
}
public function checkFileCreated($path, $format = "Y-m-d") {
return date($format, filemtime($path));
}
}
CacheConfigurator
<?php
namespace library\pingle\cache\config;
/**
* #property PramsFormatter $prams_formatter
*/
class CacheConfigurator {
private $prams_formatter;
private $cache_directory;
private $cache_kept_days;
private $cache_file_prams;
private $function_name;
function __construct($file_prams) {
$this->cache_file_prams = $file_prams;
$this->cache_directory = ""; //Def Path
}
public function setCacheDirectory($cache_directory) {
$this->cache_directory = $cache_directory;
return $this;
}
public function setFunction($function) {
$this->function_name = $function;
return $this;
}
public function setCacheKeptDays($cache_kept_days) {
$this->cache_kept_days = $cache_kept_days;
return $this;
}
public function getConfig() {
$this->prams_formatter = new PramsFormatter($this->cache_file_prams);
$this->cache_file_prams = $this->prams_formatter->getFormattedPrams();
$this->function_name = $this->prams_formatter->cleanValue($this->function_name);
return array($this->cache_directory, $this->function_name, $this->cache_kept_days, $this->cache_file_prams);
}
}
PramsFormatter
<?php
namespace library\pingle\cache\config;
class PramsFormatter {
private $cache_file_prams;
public function __construct(Array $prams) {
$this->cache_file_prams = $prams;
$this->formatPrams();
}
public function formatPrams() {
if (is_array($this->cache_file_prams)) {
foreach ($this->cache_file_prams as $k => $value) {
$this->cache_file_prams[$k] = $this->cleanValue($value);
}
}
}
public function cleanValue($value) {
if (is_array($value)) {
throw new \Exception("Array as paramter value is not accepted");
} else {
return str_replace(array(" ", " ", ".", "/", "\\"), "-", $value);
}
}
public function getFormattedPrams() {
return $this->cache_file_prams;
}
}
Usage
$cache_config = new CacheConfigurator(array('carrier_id' => $invoicedata['carrier_id'], 'month' => $month, 'year' => $year));
$cache_config->setFunction('Inter-department Calls');
$cache_config->setCacheKeptDays(30);
$cache_config->setCacheDirectory("bin/cache");
$cache = new Cache($cache_config);
if ($cache->check()) {
$node = $cache->read();
} else {
//calculate node
$cache->write($node);
}
Git Repository with Improve Design
https://github.com/FaizRasool/EPC
Very good question, but an entire book could probably be written on this, which makes it quite hard to answer.
I'd start with this simple question: what better describes caching in the choices below?
Caching is a mechanism that allows to store the result of a function to a file for a number of days in order to provide fast access to it.
Caching is a mechanism that allows to retain the result of an operation for as long as the associated retention policy is satisfied in order to provide fast access to it.
None of the definitions are perfect and that's not the point, but what I wanted to emphasis is that #1 explains caching with very specific infrastructure details while #2 defines the mechanism in a more abstract way.
Hopefully, you will now have realized one of the biggest flaws of your design IMO. The various abstractions are wrong:
The abstraction of the storage mechanism is dependent of a specific infrastructure detail: the entire API revolves around files. What if I wanted an in-memory cache?
The data retention policy algorithm is very specific: data will be retained only for a specific number of days. What if I wanted to express the cache in minutes where the counter resets evertime the data is accessed?
One advice I'd give you is to always challenge your abstractions and make sure that they are not too specific so that your code can be extensible and reusable, but not too wide either. Paying attention to the language of the problem domain greatly helps with that.
There's obviously much more than this that could be said and sometimes being technology-dependent is the right choice, but I think my answer will help...