Counting parameters of object constructor (external method) - php

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'));

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?

PHP class not setting one of my variables

I have a class that I use to display pages, and I set many class wide variables in the constructor. One of them is coming up null, even though I can see in the call to create the object that the argument is passed. Here is the constructor for the class:
public function __contruct(string $pageTitle, string $page, bool $csrfFlag, string $pageType) {
$this->pageTitle = $pageTitle;
$this->page = $page;
$this->csrfFlag = $csrfFlag;
// Validating page type passed
switch ($pageType) {
case "main":
case "profile":
case "admin":
$this->pageType = $pageType;
default:
throw new Exception("A page type variable was passed that is unknown. That variable was $pageType");
}
}
And here is the particular object creation call:
$display = new PageDisplay('Login', 'login.php', true, 'auth_page');
The problem I'm having is that the variable marked $page isn't being passed through (and I know that because I try and call a late function in the class that displays pages and when it comes to the line about displaying the actual page (in this case, login.php) it gives me this error:Failed opening '' for inclusion). Here is that function if you would like to see it:
// Ultimately builds the page to show the user
public function buildPage(bool $needsHeadTags) : void {
// Generate CSRF token if needed
if ($this->csrfFlag === true) { $csrfToken = hash("sha512", random_bytes(64)); }
// Get extra HTML
if ($needsHeadTags === true) { $extraHeadTags = $this->getHeadTags(); }
$headerHtml = $this->getHeader();
$pageTitle = $this->pageTitle;
// Show page
include_once $this->page; // where the error is thrown
}
But I can see that it is passed in the constructor. What am I doing wrong?
<?php
declare(strict_types=1);
class PageDisplay {
private $simplePageNav = true;
private $header = "";
private $pageTitle;
private $page;
private $csrfFlag;
private $pageType;
// Sets the variables the rest of the class will use
public function __contruct(string $pageTitle, string $page, bool $csrfFlag, string $pageType) {
$this->pageTitle = $pageTitle;
$this->page = $page;
$this->csrfFlag = $csrfFlag;
// Validating page type passed
switch ($pageType) {
case "main":
case "profile":
case "admin":
$this->pageType = $pageType;
default:
throw new Exception("A page type variable was passed that is unknown. That variable was $pageType");
}
}
public function getPage() : string {
return $this->page;
}
// If the function returns true, it is just a simple navigation to the page
public static function isPageNav(bool $getAllowed) : bool {
// Checking if GET parameters are allowed, then checking if the correct things are empty, then return boolean with what we find
if (!$getAllowed) {
$simplePageNav = (empty($_POST) && empty($_GET) && empty(file_get_contents("php://input"))) ? true : false;
} else {
$simplePageNav = (empty($_POST) && empty(file_get_contents("php://input"))) ? true : false;
}
return $simplePageNav;
}
// Gets what the navigation should be based on what type of page the user went to (general web, profile, admin, etc...)
private function getHeader() : string {
// Control statement to display website correctly
switch($this->pageType) {
case "auth_page":
return "";
break;
}
return "hard";
}
// Gets what the additional head tags should be based on what type of page the user went to (general web, profile, admin, etc...)
private function getHeadTags() : string {
// Control statement to display website correctly
switch($this->pageType) {
//
}
}
// Ultimately builds the page to show the user
public function buildPage(bool $needsHeadTags) : void {
// Generate CSRF token if needed
if ($this->csrfFlag === true) { $csrfToken = hash("sha512", random_bytes(64)); }
// Get extra HTML
if ($needsHeadTags === true) { $extraHeadTags = $this->getHeadTags(); }
$headerHtml = $this->getHeader();
$pageTitle = $this->pageTitle;
// Show page
include_once $this->page;
}
}
PHP has a number of "special" methods that can be added to classes that get automatically called at points during the class lifecycle. The __construct function is executed on object initialisation, and __destruct on object destruction, when there are no remaining references to the object or when the script or program ends. It's totally legal to create other class functions that also start with a double underscore, so PHP will not complain about this:
class PageDisplay {
// Sets the variables the rest of the class will use
public function __contruct(string $pageTitle, string $page, bool $csrfFlag, string $pageType) {
// code not executed unless $object->__contruct is called
}
}
but the code in the __contruct function will not be executed when a new PageDisplay object is created.
The fix is simple:
class PageDisplay {
// Sets the variables the rest of the class will use
public function __construct(string $pageTitle, string $page, bool $csrfFlag, string $pageType) {
// code executed on calling new PageDisplay('...')
}
}

ioncube_license_properties() returns different values at different program parts

I trying to use function ioncube_license_properties() to get encoded property at my project. But I have a trouble - function return false at class method and return actual value at procedure file.
I have next code. Class:
class User {
/**
* Check is can login with new user.
* #return bool
*/
public static function validateLicense()
{
if (function_exists('ioncube_license_properties')) {
$count = ioncube_license_properties()['allowedUserCount']['value']; // ioncube_license_properties() returns false there
if ($count === 10) {
//info($message . 'User can login.');
} else {
//warning($message . 'Access denied for.');
}
return $isCanLogin;
} else {
//warning('Can\'t find Ioncube function `ioncube_license_properties()`.');
return true;
}
}
}
simple php file (it's a view file on MVC model):
if(function_exists('ioncube_license_properties')) {
var_dump(['validateLicense' => User::validateLicense()]); // false always
var_dump(['$allowedUserCount' => $allowedUserCount = ioncube_license_properties()['allowedUserCount']['value']]); // returns actual value int(10)
}
Why function returns incorrect value at the class method and how to fix this?
Project use Yii2.

An example of an MVC controller

I have been reading a lot about how and why to use an MVC approach in an application. I have seen and understand examples of a Model, I have seen and understand examples of the View.... but I am STILL kind of fuzzy on the controller. I would really love to see a thorough enough example of a controller(s). (in PHP if possible, but any language will help)
Thank you.
PS: It would also be great if I could see an example of an index.php page, which decides which controller to use and how.
EDIT: I know what the job of the controller is, I just don't really understand how to accomplish this in OOP.
Request example
Put something like this in your index.php:
<?php
// Holds data like $baseUrl etc.
include 'config.php';
$requestUrl = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$requestString = substr($requestUrl, strlen($baseUrl));
$urlParams = explode('/', $requestString);
// TODO: Consider security (see comments)
$controllerName = ucfirst(array_shift($urlParams)).'Controller';
$actionName = strtolower(array_shift($urlParams)).'Action';
// Here you should probably gather the rest as params
// Call the action
$controller = new $controllerName;
$controller->$actionName();
Really basic, but you get the idea... (I also didn't take care of loading the controller class, but I guess that can be done either via autoloading or you know how to do it.)
Simple controller example (controllers/login.php):
<?php
class LoginController
{
function loginAction()
{
$username = $this->request->get('username');
$password = $this->request->get('password');
$this->loadModel('users');
if ($this->users->validate($username, $password))
{
$userData = $this->users->fetch($username);
AuthStorage::save($username, $userData);
$this->redirect('secret_area');
}
else
{
$this->view->message = 'Invalid login';
$this->view->render('error');
}
}
function logoutAction()
{
if (AuthStorage::logged())
{
AuthStorage::remove();
$this->redirect('index');
}
else
{
$this->view->message = 'You are not logged in.';
$this->view->render('error');
}
}
}
As you see, the controller takes care of the "flow" of the application - the so-called application logic. It does not take care about data storage and presentation. It rather gathers all the necessary data (depending on the current request) and assigns it to the view...
Note that this would not work with any framework I know, but I'm sure you know what the functions are supposed to do.
Imagine three screens in a UI, a screen where a user enters some search criteria, a screen where a list of summaries of matching records is displayed and a screen where, once a record is selected it is displayed for editing. There will be some logic relating to the initial search on the lines of
if search criteria are matched by no records
redisplay criteria screen, with message saying "none found"
else if search criteria are matched by exactly one record
display edit screen with chosen record
else (we have lots of records)
display list screen with matching records
Where should that logic go? Not in the view or model surely? Hence this is the job of the controller. The controller would also be responsible for taking the criteria and invoking the Model method for the search.
<?php
class App {
protected static $router;
public static function getRouter() {
return self::$router;
}
public static function run($uri) {
self::$router = new Router($uri);
//get controller class
$controller_class = ucfirst(self::$router->getController()) . 'Controller';
//get method
$controller_method = strtolower((self::$router->getMethodPrefix() != "" ? self::$router->getMethodPrefix() . '_' : '') . self::$router->getAction());
if(method_exists($controller_class, $controller_method)){
$controller_obj = new $controller_class();
$view_path = $controller_obj->$controller_method();
$view_obj = new View($controller_obj->getData(), $view_path);
$content = $view_obj->render();
}else{
throw new Exception("Called method does not exists!");
}
//layout
$route_path = self::getRouter()->getRoute();
$layout = ROOT . '/views/layout/' . $route_path . '.phtml';
$layout_view_obj = new View(compact('content'), $layout);
echo $layout_view_obj->render();
}
public static function redirect($uri){
print("<script>window.location.href='{$uri}'</script>");
exit();
}
}
<?php
class Router {
protected $uri;
protected $controller;
protected $action;
protected $params;
protected $route;
protected $method_prefix;
/**
*
* #return mixed
*/
function getUri() {
return $this->uri;
}
/**
*
* #return mixed
*/
function getController() {
return $this->controller;
}
/**
*
* #return mixed
*/
function getAction() {
return $this->action;
}
/**
*
* #return mixed
*/
function getParams() {
return $this->params;
}
function getRoute() {
return $this->route;
}
function getMethodPrefix() {
return $this->method_prefix;
}
public function __construct($uri) {
$this->uri = urldecode(trim($uri, "/"));
//defaults
$routes = Config::get("routes");
$this->route = Config::get("default_route");
$this->controller = Config::get("default_controller");
$this->action = Config::get("default_action");
$this->method_prefix= isset($routes[$this->route]) ? $routes[$this->route] : '';
//get uri params
$uri_parts = explode("?", $this->uri);
$path = $uri_parts[0];
$path_parts = explode("/", $path);
if(count($path_parts)){
//get route
if(in_array(strtolower(current($path_parts)), array_keys($routes))){
$this->route = strtolower(current($path_parts));
$this->method_prefix = isset($routes[$this->route]) ? $routes[$this->route] : '';
array_shift($path_parts);
}
//get controller
if(current($path_parts)){
$this->controller = strtolower(current($path_parts));
array_shift($path_parts);
}
//get action
if(current($path_parts)){
$this->action = strtolower(current($path_parts));
array_shift($path_parts);
}
//reset is for parameters
//$this->params = $path_parts;
//processing params from url to array
$aParams = array();
if(current($path_parts)){
for($i=0; $i<count($path_parts); $i++){
$aParams[$path_parts[$i]] = isset($path_parts[$i+1]) ? $path_parts[$i+1] : null;
$i++;
}
}
$this->params = (object)$aParams;
}
}
}
Create folder structure
Setup .htaccess & virtual hosts
Create config class to build config array
Controller
Create router class with protected non static, with getters
Create init.php with config include & autoload and include paths (lib, controlelrs,models)
Create config file with routes, default values (route, controllers, action)
Set values in router - defaults
Set uri paths, explode the uri and set route, controller, action, params ,process params.
Create app class to run the application by passing uri - (protected router obj, run func)
Create controller parent class to inherit all other controllers (protected data, model, params - non static)
set data, params in constructor.
Create controller and extend with above parent class and add default method.
Call the controller class and method in run function. method has to be with prefix.
Call the method if exisist
Views
Create a parent view class to generate views. (data, path) with default path, set controller, , render funcs to
return the full tempalte path (non static)
Create render function with ob_start(), ob_get_clean to return and send the content to browser.
Change app class to parse the data to view class. if path is returned, pass to view class too.
Layouts..layout is depend on router. re parse the layout html to view and render
Please check this:
<?php
global $conn;
require_once("../config/database.php");
require_once("../config/model.php");
$conn= new Db;
$event = isset($_GET['event']) ? $_GET['event'] : '';
if ($event == 'save') {
if($conn->insert("employee", $_POST)){
$data = array(
'success' => true,
'message' => 'Saving Successful!',
);
}
echo json_encode($data);
}
if ($event == 'update') {
if($conn->update("employee", $_POST, "id=" . $_POST['id'])){
$data = array(
'success' => true,
'message' => 'Update Successful!',
);
}
echo json_encode($data);
}
if ($event == 'delete') {
if($conn->delete("employee", "id=" . $_POST['id'])){
$data = array(
'success' => true,
'message' => 'Delete Successful!',
);
}
echo json_encode($data);
}
if ($event == 'edit') {
$data = $conn->get("select * from employee where id={$_POST['id']};")[0];
echo json_encode($data);
}
?>

Why is this Zend Framework _redirect() call failing?

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"/>';

Categories