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?
Related
Previously it worked with the version PHP 5.6.3, pthreads, Symfony2, Doctrine2, MongoDB and everything worked very well. I decided to migrate to PHP 7.0.2, I installed pthreads, I'm still using Symfony2, Doctrine2 and MongoDB, but multi thread processing stopped working.
I have defined the following classes:
class Formula extends \Worker
{
static $document_manager;
static $elements;
public function start( int $options = NULL )
{
parent::start(PTHREADS_INHERIT_NONE);
}
public function run()
{
//Set require Autoload and AppKernel
require_once __DIR__.'/../../../../app/autoload.php';
require_once __DIR__.'/../../../../app/AppKernel.php';
//Creating a new AppKernel with the given environment
$kernel = new \AppKernel( 'dev', 1 );
//Loading the Cache and Classes
$kernel->loadClassCache();
$kernel->boot();
//Set document manager
static::$document_manager = $kernel->getContainer()->get('doctrine_mongodb')->getManager();
static::$elements = static::$document_manager->getRepository('MyBundle:Elements')->findAllActive();
$elements = array();
//Creating array of objects type Element
if ( static::$elements )
{
foreach ( static::$elements as $element )
{
$elements[] = new Element( $element );
}
}
if (!empty($elements))
{
foreach ( $elements as $element )
{
//For each element execute the run method using start
$element->start();
$element->join();
}
}
$processed_elements = array();
while ( count($elements) > 0 )
{
foreach ( $elements as $id => $element )
{
//If finished the run method
if ( !$element->is_running )
{
$processed_elements[] = $element;
//Cleaning up once this thread is done
unset($elements[$id]);
}
}
}
/**
* Performing Logic with Processed Items
*/
}
}
class Element extends \Thread
{
public $is_running;
static $document_manager;
static $medicine;
public function __construct ( $Element )
{
error_log( 'Constructing Element Thread' );
/**
* Collect item data
*/
$this->is_running = true;
}
public function start( int $options = Null )
{
parent::start(PTHREADS_INHERIT_NONE);
}
public function run()
{
//Set require Autoload and AppKernel
require_once __DIR__.'/../../../../app/autoload.php';
require_once __DIR__.'/../../../../app/AppKernel.php';
//Creating a new AppKernel with the given environment
$kernel = new \AppKernel( 'dev', 1 );
//Loading the Cache and Classes
$kernel->loadClassCache();
$kernel->boot();
//Set document manager
static::$document_manager = $kernel->getContainer()->get('doctrine_mongodb')->getManager();
/**
* Logic for obtaining the medicine ....
*/
$id_medicamento = 'xxxxxx';
static::$medicine = static::$document_manager->getRepository('MyBundle:Medicine')->find($id_medicamento);
$return = false;
//Save in the database
if ( $this->save() )
{
$return = true;
}
$this->is_running = false;
return $return;
}
protected function save()
{
error_log( 'Saving' );
//Save the data
if ( !empty(static::$medicine) )
{
//Create new instance of Indication
$indication = new Indication();
$indication->setName( 'indication name' );
$indication->setValue( 'indication value' );
$indication->setDoctor( "doctor's identification" );
//Persist Indication
static::$document_manager->persist( $indication );
//Add new Indication in Medicine
static::$medicine->addIndicacion( $indication );
//Create instance of Event
$event = new Event();
$event->setAction( 'Setting indication' );
$event->setDatetime( new \MongoDate() );
$event->setComment( 'Event comment' );
//Persist Event
static::$document_manager->persist( $event );
//Add new Event in Medicine
static::$medicine->addEvento( $event );
// Write in DB
static::$document_manager->persist( static::$medicine );
/**
* Here the bug is generated and it neither writes the Indication nor the Event
* in the Medication collection, to know which error was generated I put the
* following line between try-catch and the exception is:
* "Catchable Fatal Error: Object of class Volatile could not be converted to string"
*/
static::$document_manager->flush();
return true;
}
return false;
}
}
The error is in the save method. Any help to solve this error I am very grateful. Even any optimization of the use of Doctrine that is in both Formula and Element threads would be very helpful. Thank you.
On the line:
$indication-> setValue($ value);
The value that was received was an array and the field is defined as a string in the document.
In this case the array is considered a Volatile object and generated the bug "Catchable Fatal Error: Object of class Volatile could not be converted to string"
The solution was:
$indication-> setValue(json_encode ($ value));
i have problem with rendering template in ZF2, where template is in string in variable. There is simple example:
$template = "<div>easy</div>";
$view = new \Zend\View\Model\ViewModel();
$view->setTemplate($template);
$renderer = new \Zend\View\Renderer\PhpRenderer();
$html = $renderer->render($view);
This code fail on rendering, the renderer think that the template is a path to file. And iam reallz not sure how to tell rendere its a string.
Thx for your time and respond.
You have to extend the PhpRenderer class and override the render method, in such a way that will use the string in the $template as the actual template:
class MyPhpRenderer extends PhpRenderer {
public function render($nameOrModel, $values = null)
{
if ($nameOrModel instanceof Model) {
$model = $nameOrModel;
$nameOrModel = $model->getTemplate();
if (empty($nameOrModel)) {
throw new Exception\DomainException(sprintf(
'%s: received View Model argument, but template is empty',
__METHOD__
));
}
$options = $model->getOptions();
foreach ($options as $setting => $value) {
$method = 'set' . $setting;
if (method_exists($this, $method)) {
$this->$method($value);
}
unset($method, $setting, $value);
}
unset($options);
// Give view model awareness via ViewModel helper
$helper = $this->plugin('view_model');
$helper->setCurrent($model);
$values = $model->getVariables();
unset($model);
}
// find the script file name using the parent private method
$this->addTemplate($nameOrModel);
unset($nameOrModel); // remove $name from local scope
$this->__varsCache[] = $this->vars();
if (null !== $values) {
$this->setVars($values);
}
unset($values);
// extract all assigned vars (pre-escaped), but not 'this'.
// assigns to a double-underscored variable, to prevent naming collisions
$__vars = $this->vars()->getArrayCopy();
if (array_key_exists('this', $__vars)) {
unset($__vars['this']);
}
extract($__vars);
unset($__vars); // remove $__vars from local scope
while ($this->__template = array_pop($this->__templates)) {
$this->__file = $this->resolver($this->__template);
try {
if (!$this->__file) {
$this->__content = $this->__template; // this line does what you need
}else{
ob_start();
$includeReturn = include $this->__file;
$this->__content = ob_get_clean();
}
} catch (\Exception $ex) {
ob_end_clean();
throw $ex;
}
if ($includeReturn === false && empty($this->__content)) {
throw new Exception\UnexpectedValueException(sprintf(
'%s: Unable to render template "%s"; file include failed',
__METHOD__,
$this->__file
));
}
}
$this->setVars(array_pop($this->__varsCache));
if ($this->__filterChain instanceof FilterChain) {
return $this->__filterChain->filter($this->__content); // filter output
}
return $this->__content;
}
}
and then you code should look like:
$template = "<div>easy</div>";
$view = new \Zend\View\Model\ViewModel();
$view->setTemplate($template);
$renderer = new MyPhpRenderer();
$html = $renderer->render($view);
Try by replacing '\' with _ underscore as Zend_View_Renderer_PhpRenderer
I am building a genetics calculator, and have reduced my code to a simple format to explain my issue.
I basically have this line which instantiates a hatch object:
$hatch = new Hatch($maleGeneticsPOST, $femaleGeneticsPOST, 'leopardGecko', true);
This takes a form post for the parent genetics and sets the species type. Below is my Parent class and Child class to show how this essentially works:
class Genetics
{
public $species = '';
public $dominants = [];
public $recessives = [];
public $snows = [];
public $wildtypes = [];
function __construct($species)
{
$this->species = $species;
echo $species; // returns leopardGecko as expected
}
}
class Hatch extends Genetics
{
function __construct($father, $mother, $species, $autoHatch = true, $hatchMethod = "punnett")
{
parent::__construct($species);
// Other code for $father, $mother etc.
}
}
On the face of it, those 2 classes are working well with each other, I can set the species type in the object and Hatch will set the parent to it.
However, what I am struggling to do is to then use the $species property in the parent to set the genetics, based off of the species selected/set; here's an example:
class Genetics
{
public $species = '';
public $dominants = [];
public $recessives = [];
public $snows = [];
public $wildtypes = [];
function __construct($species)
{
$this->species = $species;
echo $species; // returns leopardGecko as expected
if($species === "leopardGecko"){
$this->dominants = ['NN', 'BB', 'TT'];
$this->recessives = ['Bb', 'Tt', 'Rr'];
$this->snows = ['Mm', 'Gg'];
$this->wildtypes = ['QQ', 'Qq'];
}
}
}
And when I try and use them further down in my Hatch class, they just return empty arrays:
foreach ($alleles as $allele) {
//echo $this->allGenetics[$allele].' ';
if (in_array($allele, $this->dominants, true)) {
//echo $this->allGenetics[$allele].' ';
array_push($geckoGenetics['Gecko']['Dominants'], $this->allGenetics[$allele]);
array_push($geckoGenetics['Gene']['Dominants'], $allele);
} elseif (in_array($allele, $this->recessives, true)) {
//echo $this->allGenetics[$allele].' ';
array_push($geckoGenetics['Gecko']['Recessives'], $this->allGenetics[$allele]);
array_push($geckoGenetics['Gene']['Recessives'], $allele);
} elseif (in_array($allele, $this->wildtypes, true)) {
//echo $this->allGenetics[$allele].' ';
array_push($geckoGenetics['Gecko']['Wildtypes'], $this->allGenetics[$allele]);
array_push($geckoGenetics['Gecko']['Recessives'], $this->allGenetics[$allele]);
array_push($geckoGenetics['Gene']['Wildtypes'], $allele);
array_push($geckoGenetics['Gene']['Recessives'], $allele);
} elseif (in_array($allele, $this->snows, true)) {
array_push($geckoGenetics['Gecko']['Snows'], $this->allGenetics[$allele]);
array_push($geckoGenetics['Gene']['Snows'], $allele);
}
}
Please note: The rest of that code works fine, I'm just talking about the $this->dominants, $this->recessives, $this->wildtypes & $this->snows variables - they return empty.
Am I missing something obvious? This is my first proper go at OOP and it's going well, apart from this bit!
'There is a difference in how you're calling the field $species and how you're trying to do so with $dominants. If you want to access fields outside of the scope of your function, you'll need to call them using $this->.
So in the constructor, if you replace the following:
public $dominants = ['NN', 'BB', 'TT'];
public $recessives = ['Bb', 'Tt', 'Rr'];
public $snows = ['Mm', 'Gg];
public $wildtypes = ['QQ', 'Qq'];
with:
$this->dominants = ['NN', 'BB', 'TT'];
$this->recessives = ['Bb', 'Tt', 'Rr'];
$this->snows = ['Mm', 'Gg'];
$this->wildtypes = ['QQ', 'Qq'];
It should work.
I write a code that autoload classes, and I encountered a problem which I think is because of weakness implementation/design type. What I want to do is to count default parameters of an object (external).
I can count the number of passed arguments to constructor, but I will need to check that inside object constructor and that method does not help me.
CODE EXAMPLE:
// This is simple
function test($arg1,$arg2,$arg3) {return func_num_args();}
// How can I count like this?
class load
{
public function __construct($id="",$path="") {}
}
$l = new load();
// How to count object default parameters count(object($l)), I need answer to be 2`
MY CODE WHERE I NEED TO USE THIS METHOD:
[File: global_cfg.php]
<?php
// File: global_cfg.php
define(ROOT, __DIR__); // Root directory
define(DEBUG, true); // Set debugging state ON or OFF
define(MODE, "producer"); // If debug mode is ON: producer, publisher, tester
/*
* PATH CONFIGURATIONS:
*/
define(DS, "/");
define(LIB, "library");
/*
* SIGN AUTOLOAD CLASSES:
* Setting class sign to true value, the autoloader will create automatically
* an instance of the class lowercase type.
*/
$signClasses = Array
(
"Ralor" => false,
"NaNExist" => true,
"Message" => array(MODE),
"Debug" => DEBUG,
"Resource" => true,
"View" => true
);
[File: autoload_classes.php]
<?php
// File: autoload_classes.php
require_once("global_cfg.php");
print "<b>Loaded classes:</b> <br>";
function __autoloadClasses($list, $suffix="class", $extension="php")
{
$path="";
foreach($list as $fileName => $classInstance)
{
$path = ROOT.DS.LIB.DS.$fileName.".".$suffix.".".$extension;
if(!file_exists($path))
{
print "Signed class ".$fileName." does not exist!<br>";
continue;
}
require_once($path);
print $path;
if($classInstance)
{
$GLOBALS[strtolower($fileName)] = new $fileName();
// ??? todo: counting default object parameters
$count = count(get_object_vars($GLOBALS[strtolower($fileName)]));
if(is_array($classInstance))
{
if($count<count($classInstance))
{
print "Arguments passed to object exceeds the limit";
}
else if($count>count($classInstance))
{
print "Insuficient arguments passed to the object!";
}
else
{
// todo: create object and pass parameters
$GLOBALS[strtolower($fileName)] = new $fileName(/*$arg1 .. $argn*/);
}
}
print $count." -> Class was instantiated!<br>";
continue;
}
print "<br>";
}
}__autoloadClasses($signClasses);
After this problem I can finish my bootstrap.
You can use ReflectionFunctionAbstract::getNumberOfParameters. For example.
class load
{
public function __construct($id = "", $path = "")
{
}
}
function getNumberOfParameters($class_name)
{
$class_reflection = new ReflectionClass($class_name);
$constructor = $class_reflection->getConstructor();
if ($constructor === null)
return 0;
else
return $constructor->getNumberOfParameters();
}
var_dump(getNumberOfParameters('load'));
I am developing a Facebook app in Zend Framework. In startAction() I am getting the following error:
The URL http://apps.facebook.com/rails_across_europe/turn/move-trains-auto is not valid.
I have included the code for startAction() below. I have also included the code for moveTrainsAutoAction (these are all TurnController actions) I can't find anything wrong with my _redirect() in startAction(). I am using the same redirect in other actions and they execute flawlessly. Would you please review my code and let me know if you find a problem? I appreciate it! Thanks.
public function startAction() {
require_once 'Train.php';
$trainModel = new Train();
$config = Zend_Registry::get('config');
require_once 'Zend/Session/Namespace.php';
$userNamespace = new Zend_Session_Namespace('User');
$trainData = $trainModel->getTrain($userNamespace->gamePlayerId);
switch($trainData['type']) {
case 'STANDARD':
default:
$unitMovement = $config->train->standard->unit_movement;
break;
case 'FAST FREIGHT':
$unitMovement = $config->train->fast_freight->unit_movement;
break;
case 'SUPER FREIGHT':
$unitMovement = $config->train->superfreight->unit_movement;
break;
case 'HEAVY FREIGHT':
$unitMovement = $config->train->heavy_freight->unit_movement;
break;
}
$trainRow = array('track_units_remaining' => $unitMovement);
$where = $trainModel->getAdapter()->quoteInto('id = ?', $trainData['id']);
$trainModel->update($trainRow, $where);
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto');
}
.
.
.
public function moveTrainsAutoAction() {
$log = Zend_Registry::get('log');
$log->debug('moveTrainsAutoAction');
require_once 'Train.php';
$trainModel = new Train();
$userNamespace = new Zend_Session_Namespace('User');
$gameNamespace = new Zend_Session_Namespace('Game');
$trainData = $trainModel->getTrain($userNamespace->gamePlayerId);
$trainRow = $this->_helper->moveTrain($trainData['dest_city_id']);
if(count($trainRow) > 0) {
if($trainRow['status'] == 'ARRIVED') {
// Pass id for last city user selected so we can return user to previous map scroll postion
$this->_redirect($config->url->absolute->fb->canvas . '/turn/unload-cargo?city_id='.$gameNamespace->endTrackCity);
} else if($trainRow['track_units_remaining'] > 0) {
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto');
} else { /* Turn has ended */
$this->_redirect($config->url->absolute->fb->canvas . '/turn/end');
}
}
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto-error'); //-set-destination-error');
}
As #Jani Hartikainen points out in his comment, there is really no need to URL-encode underscores. Try to redirect with literal underscores and see if that works, since I believe redirect makes some url encoding of its own.
Not really related to your question, but in my opinion you should refactor your code a bit to get rid of the switch-case statements (or at least localize them to a single point):
controllers/TrainController.php
[...]
public function startAction() {
require_once 'Train.php';
$trainTable = new DbTable_Train();
$config = Zend_Registry::get('config');
require_once 'Zend/Session/Namespace.php';
$userNamespace = new Zend_Session_Namespace('User');
$train = $trainTable->getTrain($userNamespace->gamePlayerId);
// Add additional operations in your getTrain-method to create subclasses
// for the train
$trainTable->trackStart($train);
$this->_redirect(
$config->url->absolute->fb->canvas . '/turn/move-trains-auto'
);
}
[...]
models/dbTable/Train.php
class DbTable_Train extends Zend_Db_Table_Abstract
{
protected $_tableName = 'Train';
[...]
/**
*
*
* #return Train|false The train of $playerId, or false if the player
* does not yet have a train
*/
public function getTrain($playerId)
{
// Fetch train row
$row = [..];
return $this->trainFromDbRow($row);
}
private function trainFromDbRow(Zend_Db_Table_Row $row)
{
$data = $row->toArray();
$trainType = 'Train_Standard';
switch($row->type) {
case 'FAST FREIGHT':
$trainType = 'Train_Freight_Fast';
break;
case 'SUPER FREIGHT':
$trainType = 'Train_Freight_Super';
break;
case 'HEAVY FREIGHT':
$trainType = 'Train_Freight_Heavy';
break;
}
return new $trainType($data);
}
public function trackStart(Train $train)
{
// Since we have subclasses here, polymorphism will ensure that we
// get the correct speed etc without having to worry about the different
// types of trains.
$trainRow = array('track_units_remaining' => $train->getSpeed());
$where = $trainModel->getAdapter()->quoteInto('id = ?', $train->getId());
$this->update($trainRow, $where);
}
[...]
/models/Train.php
abstract class Train
{
public function __construct(array $data)
{
$this->setValues($data);
}
/**
* Sets multiple values on the model by calling the
* corresponding setter instead of setting the fields
* directly. This allows validation logic etc
* to be contained in the setter-methods.
*/
public function setValues(array $data)
{
foreach($data as $field => $value)
{
$methodName = 'set' . ucfirst($field);
if(method_exists($methodName, $this))
{
$this->$methodName($value);
}
}
}
/**
* Get the id of the train. The id uniquely
* identifies the train.
* #return int
*/
public final function getId ()
{
return $this->id;
}
/**
* #return int The speed of the train / turn
*/
public abstract function getSpeed ();
[..] //More common methods for trains
}
/models/Train/Standard.php
class Train_Standard extends Train
{
public function getSpeed ()
{
return 3;
}
[...]
}
/models/Train/Freight/Super.php
class Train_Freight_Super extends Train
{
public function getSpeed ()
{
return 1;
}
public function getCapacity ()
{
return A_VALUE_MUCH_LARGER_THAN_STANDARD;
}
[...]
}
By default, this will send an HTTP 302 Redirect. Since it is writing headers, if any output is written to the HTTP output, the program will stop sending headers. Try looking at the requests and response inside Firebug.
In other case, try using non default options to the _redirect() method. For example, you can try:
$ropts = { 'exit' => true, 'prependBase' => false };
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto', $ropts);
There is another interesting option for the _redirect() method, the code option, you can send for example a HTTP 301 Moved Permanently code.
$ropts = { 'exit' => true, 'prependBase' => false, 'code' => 301 };
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto', $ropts);
I think I may have found the answer. It appears that Facebook does not play nice with redirect, so it is neccessary to use Facebook's 'fb:redirect' FBML. This appears to work:
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender();
echo '<fb:redirect url="' . $config->url->absolute->fb->canvas . '/turn/move-trains-auto"/>';