I have multiple entities that have some sort of file attached to them. I have followed the cookbook entry on how to handle file uploads with doctrine (here) but my question is this:
How can I do this for multiple entities without duplicating lots of code? Right now all of my entities share the functions related to getting the path, albeit with some modifications here and there, so they are not identical, but most of the code is. What is the cleanest way of doing this?
Here is an example of two entities, notice overlapping functionality:
Image:
public function preUpload()
{
if (null === $this->file) {
return;
}
$dir = $this->getUploadRootDir().'/'.$this->getParentDir();
if (!is_dir($dir)) {
mkdir($dir);
}
$slugger = new \Sam\TourBundle\Service\SlugService();
$pathinfo = pathinfo($this->file->getClientOriginalName());
$path = $slugger->slugize($pathinfo['filename']).'.'.$pathinfo['extension'];
$this->path = $path;
// If file already exists rename it
if (file_exists($this->getAbsolutePath())) {
$i = 1;
while (file_exists($this->getAbsolutePath())) {
$this->path = $i.'-'.$this->path;
$i++;
}
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->file) {
return;
}
$dir = $this->getUploadRootDir().'/'.$this->getParentDir();
$this->file->move($dir, $this->path);
$thumb = new \Sam\TourBundle\Service\ThumbnailService();
$thumb->makeThumbnail($this->getAbsolutePath());
unset($this->file);
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
$file = $this->getAbsolutePath();
$thumb_file = $this->getThumbnailAbsolutePath();
if (file_exists($file)) {
unlink($file);
}
if (file_exists($thumb_file)) {
unlink($thumb_file);
}
}
public function getParentDir()
{
return $galerija = $this->getGalerija()->getSlug().'/';
//return null === $galerija ? null : $this->getUploadRootDir().'/'.$galerija.$this->path;
}
public function getAbsolutePath()
{
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->getParentDir().$this->path;
}
public function getThumbnailAbsolutePath()
{
$pathinfo = pathinfo($this->getAbsolutePath());
$thumbpath = $pathinfo['dirname'].'/'.$pathinfo['filename'].'_thumb.'.$pathinfo['extension'];
return null === $this->path ? null : $thumbpath;
}
public function getRootDir()
{
return __DIR__;
}
public function getWebPath()
{
return null === $this->path ? null : $this->getUploadDir().'/'.$this->getParentDir().$this->path;
}
public function getThumbnailWebPath()
{
$pathinfo = pathinfo($this->getAbsolutePath());
$thumbpath = $pathinfo['filename'].'_thumb.'.$pathinfo['extension'];
return null === $this->path ? null : $this->getUploadDir().'/'.$this->getParentDir().$thumbpath;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded documents should be saved
return realpath(__DIR__.'/../../../../web/'.$this->getUploadDir());
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw when displaying uploaded doc/image in the view
return 'images';
}
Document:
public function preUpload()
{
if (null === $this->file) {
return;
}
$dir = $this->getUploadRootDir().'/'.$this->getParentDir();
if (!is_dir($dir)) {
mkdir($dir);
}
$slugger = new \Sam\TourBundle\Service\SlugService();
$pathinfo = pathinfo($this->file->getClientOriginalName());
$path = $slugger->slugize($pathinfo['filename']).'.'.$pathinfo['extension'];
$this->path = $path;
// If file already exists rename it
if (file_exists($this->getAbsolutePath())) {
$i = 1;
while (file_exists($this->getAbsolutePath())) {
$this->path = $i.'-'.$this->path;
$i++;
}
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->file) {
return;
}
$dir = $this->getUploadRootDir().'/'.$this->getParentDir();
$this->file->move($dir, $this->path);
unset($this->file);
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
$file = $this->getAbsolutePath();
if (file_exists($file)) {
unlink($file);
}
}
public function getParentDir()
{
return $ponuda = $this->getPonuda()->getSlug().'/';
}
public function getAbsolutePath()
{
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->getParentDir().$this->path;
}
public function getRootDir()
{
return __DIR__;
}
public function getWebPath()
{
return null === $this->path ? null : $this->getUploadDir().'/'.$this->getParentDir().$this->path;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded documents should be saved
return realpath(__DIR__.'/../../../../web/'.$this->getUploadDir());
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw when displaying uploaded doc/image in the view
return 'documents';
}
I would personally create an upload service class with an method which you can pass the entities, you can inject this service wherever you need and have it written in only one place.
Generally is not a great idea to have this kind of logic in your entities I believe.
Inheritance in this case looks completely incorrect from my point of view since it's a way of adding behavior instead of a generalization, you could do it but I'd advice against it. Inheritance per se is way overused and not the cleanest way to work, and Doctrine's implementation of it is quite annoying to work with as well. Traits could do it, though I haven't had much experience with them yet in this kind of environment.
I'd strongly advice using services the way Symfony was meant to be used.
Related
i'am not very well OO, and search a few web site to write this class
now my problem is how to use this class just like the Laravel 5.5 build in Storage class
i want use like this
MyStorage::disk('dropbox')
->addDirFilter('(school|travel)')
->addDirFilter('\d{4}-\d{2}-\d{2}')
->addFileFilter('.*\.jpg')
->getMatch();
here is MyStorage class
<?php
namespace App\MySupport;
class MyStorage
{
private $flag;
private $disk;
private $matches;
public function __construct()
{
$this->flag = false;
$this->disk = '';
$this->matches = [];
}
public function disk($disk)
{
$this->disk = $disk;
return $this;
}
public function addDirFilter($filter)
{
if (! $this->flag)
{
$this->flag = true;
$subDirs[] = \Storage::disk($this->disk)->directories();
}
else
{
foreach ($this->matches as $dir)
{
$subDirs[] = \Storage::disk($this->disk)->directories($dir);
}
}
$this->findMatch($subDirs, $filter);
return $this;
}
public function addFileFilter($filter)
{
if (! $this->flag)
{
$this->flag = true;
$subFiles[] = \Storage::disk($this->disk)->files();
}
else
{
foreach ($this->matches as $dir)
{
$subFiles[] = \Storage::disk($this->disk)->files($dir);
}
}
$this->findMatch($subFiles, $filter);
return $this;
}
public function findMatch($subItems, $filter)
{
// set empty before update
$this->matches = [];
// if call this method by addDirFilter() , $subItem contain the Dir path, eg, DirA/DirB
// if call this method by addFileFilter() , $subItem contain the File path, eg, DirX/File.txt
foreach (array_collapse($subItems) as $subItem)
{
// get the last str, eg, DirB OR File.txt
$lastStr = #end(explode('/', $subItem));
if ( preg_match('/^' . $filter . '$/u', $lastStr) )
{
// update new matches
$this->matches[] = $subItem;
}
}
}
public function getMatch()
{
return $this->matches;
}
}
Usage
<?php
namespace App\Http\Controllers;
use App\MySupport\MyStorage;
class TestController extends Controller
{
public function storageTest()
{
$MyStorage = new MyStorage();
// for example in my dropbox disk have
//
// school/2018-01-01/emails.txt
// school/2018-02-02/Peter.jpg
// travel/2017-06-06/TW.jpg
$folders = $MyStorage->disk('dropbox')
->addDirFilter('school')
->addDirFilter('\d{4}-\d{2}-\d{2}')
->getMatch();
// $folders result is:
// school/2018-01-01
// school/2018-02-02
$files = $MyStorage->disk('dropbox')
->addDirFilter('(school|travel)')
->addDirFilter('\d{4}-\d{2}-\d{2}')
->addFileFilter('.*\.jpg')
->getMatch();
// $files result is:
// school/2018-02-02/Peter.jpg
// travel/2017-06-06/TW.jpg
}
}
i tested seems all fine
can anyone point me to the direction, how to use MyStorage class just like Laravel 5.5 build in Storage class, thanks
You should use static function.
Try replace your function as code below.
public static function disk($disk)
{
$instance = new MyStorage();
$instance->disk = $disk;
return $instance;
Furthermore, I would suggest you learn singleton design pattern
This is method for creating song object
public function getSong() {
return new Song($this->rackDir, $this->getLoadedDisc()->getName(), $this->song);
}
There is Song class
class Song extends CD {
private $id;
public function __construct($rack, $name, $id)
{
$this->id = $id;
parent::__construct($rack, $name);
}
public function getSelectedSongId() {
return $this->id;
}
public function getSelectedSongPath() {
$list = $this->getSongsList();
return $list[$this->id];
}
}
public function getSongInfo () {
$data = [
'name' => $this->getName(),
'size' => $this->getSize(),
];
return $data;
}
public function getSize() {
$path = $this->getPath() . '/' . $this->getName();
return filesize($path);
}
public function getName() {
return $this->getSelectedSongPath();
}
}
And there is CD Class where I check if file has audio extension.
class CD {
private $path;
private $name;
private $rack;
private $validExtensions;
public function __construct($rack, $name)
{
$this->rack = $rack . '/';
$this->name = $name;
$this->path = $this->rack . $this->name;
$this->validExtensions = ['mp3', 'mp4', 'wav'];
}
public function getPath() {
return $this->path;
}
public function getName() {
return $this->name;
}
public function getSongsList () {
$path = $this->rack . $this->name;
$songsList = [];
if (!is_dir($path)) {
return false;
}
if ($handle = opendir($path)) {
while (false !== ($file = readdir($handle)))
{
if ($file != "." && $file != ".." && in_array(strtolower(substr($file, strrpos($file, '.') + 1)), $this->validExtensions))
{
array_push($songsList, $file);
}
}
closedir($handle);
}
return $songsList;
}
}
I want to check if File is real audio file and not just file with an audio extension?
Is there is method to do that in PHP?
Karlos was in right.
I was found a solution with this code bellow.
public function validateFile (Song $song) {
$allowed = array(
'audio/mp4', 'audio/mp3', 'audio/mpeg3', 'audio/x-mpeg-3', 'audio/mpeg', 'audio/*'
);
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$info = finfo_file($finfo, $song->getSelectedSongPath());
if (!in_array($info, $allowed)) {
die( 'file is empty / corrupted');
}
return $song;
}
I am trying to determine whether a FQCN is a class, a trait, or an interface. This is what I am currently thinking of, but does anyone have any better ideas?
/**
* #return string|null Returns the type the FQCN represents, returns null on failure
*/
function fqcnType(string $fqcn) : ?string
{
if (interface_exists($fqcn) === true) {
return 'interface';
} elseif (class_exists($fqcn) === true) {
return 'class';
} elseif (trait_exists($fqcn) === true) {
return 'trait';
} elseif (function_exists($fqcn) === true) {
return 'function';
}
return null;
}
function fqcn_exists(string $fqcn) : bool
{
return fqcnType($fqcn) !== null;
}
/**
* #return string|null Returns the type the FQCN represents, returns null on failure
*/
function fqcnType(string $fqcn) : ?string
{
$types = [
'interface',
'class',
'trait',
'function',
];
foreach($types as $type) {
if(true === ($type.'_exists')($fqcn)) {
return $type;
}
}
return null;
}
function fqcn_exists(string $fqcn) : bool
{
return null !== fqcnType($fqcn);
}
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...
Im busy with an image moving function so im overriding some controller functions, and unfortuantly i required the items id for the image name so i changed form save() to postSaveHook() as i was not able to get the item id in save() but now im facing another problem i cant set form data to the newly renamed image.
Here's the code:
public function postSaveHook($model, $validData){
$item = $model->getItem();
$id = $item->get('id');
$path = JPath::clean(JPATH_SITE. DS ."images". DS ."menu_slider". DS );
$input=JFactory::getApplication()->input;
$input->get('jform', NULL, NULL);
$src_image = $this->moveOriginal($path,$id);
$imageTest = $this->findImages($src_image);
if(!empty($imageTest)){
foreach($imageTest as $images){
$this->createImageSlices($images,$src_image,$path);
}
}else{
echo 'all images are there';
}
/*this part no longer works*/
$data = JRequest::getVar( 'jform', null, 'post', 'array' );
$data['image'] = 'images'.DS.'menu_slider'.DS.'original'.DS.$src_image;
$input->post->set('jform',$data);
return parent::postSaveHook($model, $validData);
}
is there anyway i can save the data from this? or if i revert back to save, how would i get the id?
Any Help Greatly Appreciated.
I tried different ways and the absolute safest way for me was to add the following code in the model
class DPCasesModelCase extends JModelAdmin {
public function save($data) {
new EventHandler(JDispatcher::getInstance(), $this);
return parent::save($data);
}
}
class EventHandler extends JEvent {
private $model = null;
public function __construct(&$subject, $model) {
parent::__construct($subject);
$this->model = $model;
}
public function onContentChangeState($context, $pks, $value) {
if ($context != 'com_dpcases.case' && $context != 'com_dpcases.form') {
return;
}
if (! is_array($pks)) {
$pks = array($pks);
}
foreach ( $pks as $pk ) {
$this->dowork($this->model->getItem($pk), 'edit');
}
}
public function onContentAfterSave($context, $object, $isNew) {
if ($context != 'com_dpcases.case' && $context != 'com_dpcases.form') {
return;
}
$this->dowork($object, $isNew ? 'create' : 'edit');
}
private function dowork($object, $action) {
...
}
}