JS anon functions in PHP (without anon function support) - php

Let's say I have this JS code:
var events = ['start', 'stop', 'tick']
, obj = ... // object with events
, receiver = function(event_name, event_args){ // function to receive all events
console.log(event_name, event_args);
}
;
for(var i=0; i<events.length; i++){
obj.on(events[i], (function(event){
return function(){
return receiver(event, arguments);
};
})(events[i]));
}
What I'm doing here is to route all events to function "receiver".
I'm trying to replicate this behavior in PHP, since I already have obj.on ($obj->on) functionality.
I would like to support (slightly) old PHP versions (5.1+), so I can't make use of anonymous functions (as well as passing variables to said functions).
Here's a tentative (and incomplete) version of the above code:
$events = array('start', 'stop', 'tick');
$obj = new ... ; // object with events
function receiver($event_name, $event_args){ // function to receive all events
echo $event_name.' | '.print_r($event_args, true);
}
foreach($events as $event){
$obj->on($event, ...); // ???
// ???
return receiver($event, func_get_arguments());
// ???
}

One of the possible approaches I can see is making use of __invoke:
$events = array('start', 'stop', 'tick');
$obj = new ... ; // object with events
function receiver($event_name, $event_args){ // function to receive all events
echo $event_name.' | '.print_r($event_args, true);
}
class EventRedirector {
public $event_name = '';
public $redirector = '';
public function __invoke(){
$redirector = $this->redirector;
return $redirector($this->event_name, func_get_args());
}
public function __construct($event_name, $redirector){
$this->event_name = $event_name;
$this->redirector = $redirector;
}
}
foreach($events as $event){
$obj->on($event, new EventRedirector($event, 'receiver'));
}

Related

PHP: opposite of extends (for parent class)

I'm aware of using extends but i am wondering what's the best practice for doing the opposite:
I'm having a "parent" class called c_film and 2 child classes called c_wikipedia and c_imdb - they need to access the general settings ($aOptions) and functions / error handler from c_film.
here's a simplified version:
$aOptions = array(
"wikidata_id" => "Q63985561"; // the movie is: tenet
"verbose_output" => true,
"logging" => true
);
$o = new c_film( $aOptions );
$aData = $o->load_film(); // scrape wikipedia, scrape imdb, merge data into array
these are the requirements:
c_film has functions for scraping/parsing/error handling for all child classes/logging/misc which can be used from both child classes
c_wikipedia and c_imdb can access options / functions from c_film and trigger errors
here's my current solution (simplified):
class c_film
{
function __construct( $aOptions )
{
$this->aOptions = $aOptions;
}
function load_film()
{
$o = new c_wikipedia( $this );
$this->aWikipedia = $o->get_data();
$o = new c_imdb( $this );
$this->aImdb = $o->get_data();
$aData = $this->get_merged_data();
}
private function get_merged_data()
{
// process data / merge into one array
$aResult = array_merge( $this->aWikipedia, $this->aImdb );
result $aResult;
}
function scrape($url)
{
// scrape data / handle 404 / log errors/ html parsing
// [code here]
return $html;
}
function log($msg, $class, $function)
{
// log to file
}
function error( Throwable $t )
{
// log error into file
}
}
class c_wikipedia
{
function __construct( $oFilm ) // parent class object c_film
{
$this->oFilm = $oFilm;
}
function get_data()
{
try {
// scrape data from wikipedia
$aData = $this->get_data();
$url = $this->get_url_from_wikidata_id();
$html = $oFilm->scrape($url);
} catch(Throwable $t ){
//
$oFilm->error( $t );
}
}
private function get_data()
{
$oFilm = $this->oFilm;
$aOptions = $oFilm->aOptions;
$wikidata_id = $aOptions['wikidata_id'];
$bLog = $oFilm->aOptions['logging'];
$output = $oFilm->aOptions['verbose_output'];
// .. load + parse data
$url = // determine url
$msg = "loading data for " . $wikidata_id;
if($bLog) $oFilm->log($msg, get_class(), __FUNCTION__ ); // log to file including class name and function name
if($output) echo $msg;
$html = $oFilm->scrape($url);
return $aData;
}
}
So - is passing the c_film object to the child classes the best practice or is there a more elegant method?

Variable usable in Service

I am a beginner in php. I have a WadoService service and a StudiesRestController controller. I want to use controller data in the service.
public function getPatientAction(Request $request, $studyUID)
{
$studyRepository = new StudyRepository(
$this->get('nexus_db'),
$this->get('logger'),
$this->get('translator')
);
$study = $studyRepository->getStudy($studyUID);
if (!$study) {
throw new NotFoundHttpException("No study found with studyuid $studyUID");
}
$patientInfo = new RestResponse(
SerializerBuilder::create()
->build()
->serialize($study->getPatient(), 'json')
);
return $patientInfo;
}
Is this possible? I have tried to put this in the function getPatientAction()without result:
/* #var $wadoService WadoService */
$wadoService = $this->container->get(WadoService::SERVICE_NAME);
$wadoService = new RestResponse(
SerializerBuilder::create()
->build()
->serialize($study->getPatient(), 'json')
);
To pass a variable from your controller to your service, you do it it like that :
$wadoService = $this->container->get(WadoService::SERVICE_NAME)->yourServiceMethod($yourVari);

Parameters to a class constructor function

I'm trying to adapt a class of mine that handles tags for events stored in a JSON file. You can create tags, delete them, restore them, view them, etc. In the code below for this library you can see that I retrieve the array from the file during the constructor function so I use it and manipulate it throughout my classes' functions.
class tagHandler {
private $tagsFile = "/home/thomassm/public_html/functions/php/tags.json";
private $LstTags;
private $LstReturn;
function __construct() {
$this->LstTags = array();
if(!file_exists ($this->tagsFile)){
$fHND = fopen($this->tagsFile, "w");
$tmpArray = array(array("EID","EName","EColor", "EDel"));
fwrite($fHND, json_encode($tmpArray));
fclose($fHND);
}
$encodedInput = file ($this->tagsFile);
$this->LstTags = json_decode($encodedInput[0], true);
if(!$this->LstTags) $this->LstTags = array();
}
function __destruct(){
$this->update();
}
public function update(){
$this->LstTags = array_values($this->LstTags);
$fHND = fopen($this->tagsFile, "w");
fwrite($fHND, json_encode($this->LstTags));
fclose($fHND);
//empty memory region
$this->LstTags = array();
$encodedInput = file ($this->tagsFile);
$this->LstTags = json_decode($encodedInput[0], true);
}
//More functions that use the collected array here.
I am trying to adapt the class to deal with people signed up to my events. Each event has a record in my database that will store a field for an array of males who sign up and females who sign up. I wish for the constructor class to get the arrays(s) from the record so they can be manipulated like the previous class. The issue is to get the array I have to search the DB for a record with the Event ID (EID) and that will require a variable passed to the constructor function. To make things worse, this parameter has to be able to change in a loop. For example, the page listing all the events will have to use this class in a loop going through each record, so it can retrieve the array to manipulate it and then show it in a table / fullcalendar before repeating the process to get the next event. I have put the code I have so far below. Its not complete (some variables haven't been renamed to male and female, etc) and may be completely wrong, but it will give you a base to explain from.
class signupHandler {
private $LstMaleS;
private $LstFemaleS;
private $LstReturn;
function __construct($IntEID) {
$this->LstTags = array();
$StrQuery = "SELECT MaleS, FemaleS FROM tblEvents WHERE EID = ?";
if ($statement = TF_Core::$MySQLi->DB->prepare($StrQuery)) {
$statement->bind_param('s',$IntEID);
$statement->execute ();
$results = $statement->get_result ();
}
$this->LstTags = json_decode($encodedInput[0], true);
if(!$this->LstTags) $this->LstTags = array();
}
Thanks,
Tom
function decodeNames($StrNames){
$this->LstNames = array();
$this->LstNames = json_decode($StrNames, true);
if(!$this->LstNames) $this->LstNames = array();
$this->LstNames = array_values($this->LstNames);
}
function update(){
$this->LstNames = array_values($this->LstNames);
return json_encode($this->LstNames);
}
public function addSignUp($StrNames, $StrUsername, $StrStatus){
$this->decodeNames($StrNames);
$BlnPresent = false;
for($i = 0; $i < count($this->LstNames); $i++){
if($this->LstNames[$i][0] == $StrUsername){
$this->LstNames[$i][1] = $StrStatus;
$BlnPresent = true;
}
}
if($BlnPresent == false){
array_push($this->LstNames, array($StrUsername, $StrStatus, date("Y-m-d H:i:s")));
}
return $this->update();
}
I have decided to pass the encoded JSON array to the class each time I call a function from it. Before every function it is decoded and turned into an array and at the end it is then re-encoded and returned back to the file calling it. Now I no longer have any constructor or destruct functions.

Fatal error: Cannot redeclare class pDraw

I am generating a pdf from the mPDF library using the Yii framework. For this PDF I need to add charts generated on the server, so I'm using the pChart library to generate them. I created a simple object class to set the pChart properties in an easier way for the PDF.
This is the class object:
<?php
Class Chart {
protected $fontsFolder = null;
protected $data = array();
protected $resolution = array();
public $chartType = null;
public $fontSize = 18;
public $displayValues = false;
public $dirFile = null;
function __construct() {
Yii::import('application.vendors.pChart.class.*', true);
$this->fontsFolder = Yii::getPathOfALias('application.vendors.pChart.fonts');
}
// Set data points
public function data($data) {
if(!isset($data))
throw new CException('Data array missing');
if(!is_array($data))
throw new CException('Data must be an array');
$this->data[] = $data;
}
// Set label points
public function labels($labels) {
if(!isset($labels))
throw new CException('Labels data must be assgined');
if(!is_array($labels))
throw new CException('Labels data must be an array');
if(isset($this->data['labels']))
throw new CException('Labels data is already assigned');
$this->data['labels'] = $labels;
}
// Set resolution image
public function resolution($x, $y) {
if(isset($x) && isset($y)) {
if(is_array($x) && is_array($y))
throw new CException('Array to String error');
$this->resolution['x'] = $x;
$this->resolution['y'] = $y;
} else
throw new CException('Resolution data missing');
}
public function Debug() {
var_dump($this->fontsFolder, $this->data, $this->resolution);
}
// Render chart with given data
public function renderChart() {
if(!$this->data)
throw new CException('Data property must be assigned');
if(!$this->resolution)
throw new CException('Resolution property must be assigned');
if(!$this->chartType)
throw new CException('Chart type cannot be null');
if(!$this->dirFile)
throw new CException('Directory file must be assigned');
$this->render();
}
protected function render() {
switch ($this->chartType) {
case 'lineChart':
$this->lineChart();
break;
default:
throw new CEXception('"'.$this->chartType.'" is not a valid chart type');
break;
}
}
protected function lineChart() {
include('pDraw.class.php');
include('pImage.class.php');
include('pData.class.php');
$data = new pData();
foreach($this->data as $key => $value) {
if(is_int($key))
$data->addPoints($value);
}
$data->addPoints($this->data['labels'], 'labels');
$data->setAbscissa('labels');
$picture = new pImage($this->resolution['x'], $this->resolution['y'], $data);
$picture->setFontProperties(array('FontName'=>$this->fontsFolder.'/Forgotte.ttf'), $this->fontSize);
$picture->setGraphArea(60, 40, 970, 190);
$picture->drawScale(array(
'GridR'=>200,
'GridG'=>200,
'GridB'=>200,
'DrawXLines'=>false
));
$picture->drawLineChart(array('DisplayValues'=>$this->displayValues));
$picture->render($this->dirFile);
}
}
?>
And here is how I'm instantiating the object with PDF settings:
<?php
Class SeguimientoController extends Controller {
// public $layout = '//layouts/column2';
public function actionIndex() {
// Cargar libreria pdf y estilos
$mPDF = Yii::app()->ePdf->mpdf();
$mPDF->debug = true;
$mPDF->showImageErrors = true;
$stylesheet = file_get_contents(Yii::getPathOfAlias('webroot.css.reporte') . '/main.css');
$mPDF->WriteHTML($stylesheet, 1);
// Generate first chart
$chart = new Chart;
$chart->data(array(1,2,3,4,5));
$chart->Labels(array('asd', 'dead', 'eads', 'daed', 'fasd'));
$chart->resolution(1000, 200);
$chart->chartType = 'lineChart';
$chart->displayValues = true;
$chart->dirFile = Yii::getPathOfAlias('webroot.images').'/chart.png';
$chart->renderChart();
$mPDF->WriteHTml(CHtml::image('/basedato1/images/chart.png', 'chart'), 2);
// Generate second chart
$chart1 = new Chart;
$chart1->data(array(1,2,3,4,5));
$chart1->Labels(array('asd', 'dead', 'eads', 'daed', 'fasd'));
$chart1->resolution(1000, 200);
$chart1->chartType = 'lineChart';
$chart1->displayValues = true;
$chart1->dirFile = Yii::getPathOfAlias('webroot.images').'/chart.png';
$chart1->renderChart();
$mPDF->WriteHTml(CHtml::image('/basedato1/images/chart.png', 'chart'), 2);
$mPDF->OutPut();
}
}
?>
By testing the output of the view step by step, everything goes fine, the chart is rendered and saved as a temp file so the pdf can gather it. But when I have to render the second chart, I get a fatal error.
I have tried creating another class to be accessed by static functions because I thought the error could be made from multiple instances of the same object. But again, on second render I get the same error.
Just in case, if you need to know how the error outputs, here it is: Fatal error: Cannot redeclare class pDraw in C:\Servidor\basedato1\protected\vendors\pChart\class\pDraw.class.php on line 104.
Use include_once instead of include.
include_once('pDraw.class.php');
include_once('pImage.class.php');
include_once('pData.class.php');

Calling a PHP class method using AJAX

I have come up with the following bits of code to call a method via AJAX in my PHP classes:
PHP:
class Ajax extends Controller {
private $class;
private $method;
private $params;
function __construct()
{
$this->params = $_POST; // Call params
$call = explode('->', $this->params['call']);
$this->class = new $call[0]; // e.g. controller->method
$this->method = $call[1];
array_shift($this->params);
$this->parse();
}
public function index()
{
//Dummy
}
public function parse()
{
$r = '';
$r = call_user_func_array(array($this->class, $this->method), $this->params);
echo $r;
}
}
Client:
function creditCheck2(id)
{
$.post(ROOT + 'Ajax', {call: 'Record->creditState', id: id, enquiryid: enquiryId}, function(data) {
alert(data)
}, 'json')
}
It seems to work great, but is it secure and could it be better?
Just for reference, I have added my code with the changes suggested by the answers:
class Call extends Controller {
private $class;
private $method;
private $params;
private $authClasses = array(
'Gallery'
);
function __construct()
{
$this->params = $_POST; // Call params
$call = explode('->', $this->params['call']);
if(!in_array($call[0], $this->authClasses))
{
die();
}
$this->class = new $call[0]; // e.g. controller->method
$this->method = $call[1];
unset($this->params['call']);
$this->parse();
}
public function parse()
{
$r = '';
$param = array();
// Params in any order...
$mRef = new ReflectionMethod($this->class, $this->method);
foreach($mRef->getParameters() as $p) {
$param[$p->name] = $this->params[$p->name];
}
$this->params = $param;
if($r = #call_user_func_array(array($this->class, $this->method), $this->params))
{
echo $r;
}
else {
}
}
}
Small issues
It could be better in that array_shift($this->params) unnecessarily assumes that the first item in the params array will always be call. That's not true and it does not agree with the direct access $this->params['call'] you are doing a little earlier. The array_shift should be replaced with simply unset($this->params['call']).
Bigger issues
There is also the problem that the order of values in the params array must match the order of parameters in the signature of the method you are trying to call. I don't think there is a guarantee that the order will be the same as the order of the parameters in the AJAX request, so that's a theoretical problem.
VERY big problem
More importantly, this way of doing things forces the author of the AJAX code to match the order of parameters in the signature of the method you are trying to call. This introduces a horrible level of coupling and is a major problem. What's worse, changing the order of the parameters by mistake will not be apparent. Consider:
public function bankTransfer($fromAccount, $toAccount, $amount);
$.post(ROOT + 'Ajax', {
call: 'Bank->bankTransfer',
from: "sender",
to: "recipient",
amount: 42
}, function(data) { ... });
This would work. But if you do this
$.post(ROOT + 'Ajax', {
call: 'Bank->bankTransfer',
to: "recipient", // swapped the order of
from: "sender", // these two lines
amount: 42
}, function(data) { ... });
You will get the opposite result of what is expected. I believe it's immediately obvious that this is extremely bad.
To solve the problem you would have to use reflection to match the array keys in $this->params with the formal names of the parameters of the method being called.
Security
Finally, this code is insecure in that anyone can make a request that directs your code to call any method of any class with the appropriate parameters -- even methods that should not be accessible from a web environment.
This is another serious problem and cannot really be fixed unless you introduce some type of filtering to the dispatch logic.
It seems to work great, but is it secure and could it be better?
Are you using your own framework or using other framework? I believe that it isn't secure at all, if the attacker know what might be inside your framework. For example: there is database class in your framework, attacker can do the following:
{call: 'Database->execute', sql: 'SELECT * FROM information_schema.`tables`'}
Filtering
You can limit the number of class that you allow user to access. For example:
if (!in_array($this->class, array('Record', 'Hello'))) {
die();
}
Reflection
This is sample of reflection that I just learn (Thanks to #Jon for the reference). This solve the problem of passing argument in different order from the PHP function.
class Email
{
public function send($from, $to, $msg) {
return "Send $from to $to: $msg";
}
}
$rawParam = array('msg' => 'Hello World',
'to' => 'to#gmail.com',
'from' => 'from#gmail.com');
$param = array();
// Rearrange
$methodRef = new ReflectionMethod('Email', 'send');
foreach($methodRef->getParameters() as $p) {
$param[$p->name] = $rawParam[$p->name];
}
var_dump($rawParam);
var_dump($param);

Categories