php OOP: Using eval() to build a string to access $GLOBALS - php

The following code works and does what I want, but I'm pretty sure I'm doing something dumb\awful.
I'm learning OOP and there is a tutorial I started to follow that used a "Config" class to setup some parameters for the program to use. I've noticed something similar in other tutorials. This tutorial though only included a method to retrieve the configuration (it used the $GLOBALS array) not to update it during the run time of the program. I attempted to add this functionality, but resorted to using eval() which I think is a nono? Also it was never explained in the tutorial why the $GLOBALS array was used instead of just using a static variable so I'm confused about that as well.
Here is init.php which gets included in files needing to access the config options:
<?php
$GLOBALS['config'] = array(
'mysql' => array(
'host' => '127.0.0.1',
'username' => 'root',
'password' => '123456',
'db' => NULL
),
'shell' => array(
'exe' => 'powershell.exe',
'args' => array(
'-NonInteractive',
'-NoProfile',
'-NoLogo',
'-Command'
)
)
);
spl_autoload_register(function($class){
require_once 'classes/' . $class . '.php';
});
This is the Config.php class which has a get and (my) set method to access the config array. For the set method I build a string like "$GLOBALS['config']['someConfig']['someSubConfig'] = 'newVal';" and use eval to execute it. Ultimately I use it in the program like Config::set('mysql/host','zzzzz');
<?php
class Config {
public static function get($path=NULL) {
//return all configs if not specified
$config = $GLOBALS['config'];
if($path) {
//parse path to return config
$path = explode('/', $path);
foreach($path as $element) {
if(isset($config[$element])) {
$config = $config[$element];
} else {
//if config not exist
$config = false;
}
}
}
return $config;
}
public static function set($path=NULL,$value=NULL) {
if($path) {
//parse path to return config
$path = explode('/', $path);
//Start code string for eval
$globalPosition = '$GLOBALS['."'config'".']';
foreach($path as $element) {
$globalPosition .= "['$element']";
}
$globalPosition .= "='$value';";
//End code string
eval($globalPosition);
var_dump($GLOBALS);
}
}
}

First of all, here are a few caveats:
Global variables are rarely a good idea, especially in OOP design (mainly because they couple code very tightly).
Please don't use eval().
You can quite easily modify your code to set the variable (by reference using =&) without having to use eval() at all. For example:
public static function set($path = null,$value = null)
{
if($path)
{
//parse path to return config
$path = explode('/', $path);
//Start code string for eval
$setting =& $GLOBALS['config'];
foreach($path as $element)
{
$setting =& $setting[$element];
}
$setting = $value;
var_dump($GLOBALS);
}
}

Related

Load configuration file just once

I'm working on my script to convert legacy links to seo friendly urls.
index.php
require 'AltoRouter.php';
$router = new AltoRouter();
$router->setBasePath('/router');
$urls = [
'index.php?option=com_index&task=articles&id=1',
'index.php?option=com_index&task=articles&slug=1-article-title',
'index.php?option=com_index&task=articles.category&cid=100-category1',
'index.php?option=com_shop&task=products&slug=100-amazing-product',
];
foreach($urls as $i=>$url) {
echo $router->getSefUrl($url);
}
AltoRouter.php
...
public function getSefUrl($url) {
$url_clean = str_replace('index.php?', '', $url);
parse_str($url_clean, $output);
$component = empty($output['option']) ? 'com_index' : $output['option'];
$task = empty($output['task']) ? 'index' : $output['task'];
$path = 'components/'.$component.'/routes/routes.json';
$data = json_decode(file_get_contents($path));
if (!empty($data)) {
foreach($data as $route) {
$this->map($route[0], $route[1], $route[2], $route[2]);
}
}
$route_info = $this->findUrlFromRoutes($task);
return empty($route_info) ? $url : $this->generate($route_info->task, $output);
}
...
My question: Every time when I'm using getSefUrl method I'm loading routes from external file. Is it ok? Or can I optimize code above some kind? If yes - how to?
Thanks!
You could avoid multiple fetches and decodes in your loop by breaking that out.
In AltoRouter.php
private $routes = array();
function getComponentRoutes($component)
{
if(! isset($this->routes[$component])) {
$path = 'components/'.$component.'/routes/routes.json';
$this->routes[$component] = json_decode(file_get_contents($path));
}
return $this->routes[$component];
}
You can replace that require with require_once or better use autoloading :
You may define an __autoload() function which is automatically called
in case you are trying to use a class/interface which hasn't been
defined yet. By calling this function the scripting engine is given a
last chance to load the class before PHP fails with an error.
Create a folder and put all your required classs in this folder:
function __autoload($class) {
require_once "Classes" . $class . '.php';
}

Cannot extend a class located in another file, PHP

I am trying to set up a class with commonly used tasks, such as preparing strings for input into a database and creating a PDO object. I would like to include this file in other class files and extend those classes to use the common class' code.
However, when I place the common class in its own file and include it in the class it will be used in, I receive an error that states the second class cannot be found. For example, if the class name is foo and it is extending bar (the common class, located elsewhere), the error says that foo cannot be found. But if I place the code for class bar in the same file as foo, it works.
Here are the classes in question -
Common Class
abstract class coreFunctions {
protected $contentDB;
public function __construct() {
$this->contentDB = new PDO('mysql:host=localhost;dbname=db', 'username', 'password');
}
public function cleanStr($string) {
$cleansed = trim($string);
$cleansed = stripslashes($cleansed);
$cleansed = strip_tags($cleansed);
return $cleansed;
}
}
Code from individual class
include $_SERVER['DOCUMENT_ROOT'] . '/includes/class.core-functions.php';
$mode = $_POST['mode'];
if (isset($mode)) {
$gallery = new gallery;
switch ($mode) {
case 'addAlbum':
$gallery->addAlbum($_POST['hash'], $_POST['title'],
$_POST['description']);
}
}
class gallery extends coreFunctions {
private function directoryPath($string) {
$path = trim($string);
$path = strtolower($path);
$path = preg_replace('/[^ \pL \pN]/', '', $path);
$path = preg_replace('[\s+]', '', $path);
$path = substr($path, 0, 18);
return $path;
}
public function addAlbum($hash, $title, $description) {
$title = $this->cleanStr($title);
$description = $this->cleanStr($description);
$path = $this->directoryPath($title);
if ($title && $description && $hash) {
$addAlbum = $this->contentDB->prepare("INSERT INTO gallery_albums
(albumHash, albumTitle, albumDescription,
albumPath)
VALUES
(:hash, :title, :description, :path)");
$addAlbum->execute(array('hash' => $hash, 'title' => $title, 'description' => $description,
'path' => $path));
}
}
}
The error when I try it this way is
Fatal error: Class 'gallery' not found in /home/opheliad/public_html/admin/photo-gallery/includes/class.admin_photo-gallery.php on line 10
You'll need to include or require the file with the original class. Otherwise PHP won't see it.
Make sure the include is successful, enable error reporting to see errors, or use require to trigger a fatal error on fail.
Still learning the ins and outs of OOP. After a few minutes of research I came across spl_autoload_register in the PHP documentation.
I placed the coreFunctions class in /includes/classes/coreFunctions.class.php and the gallery class in /includes/classes/gallery.class.php
My code then became:
function cvfdAutoloader($class) {
include $_SERVER['DOCUMENT_ROOT'] . '/includes/classes/' . $class . '.class.php';
}
spl_autoload_register('cvfdAutoloader');
$mode = $_POST['mode'];
if (isset($mode)) {
$gallery = new gallery;
switch ($mode) {
case 'addAlbum':
$gallery->addAlbum($_POST['hash'], $_POST['title'],
$_POST['description']);
}
}
And it works! Would someone care to shed some light on what exactly is happening here that is different from just including coreFunctions?

Organizing objects and libraries

I've been creating a small number of libraries / classes from scratch in php. I come from a codeigniter background, and I'm trying to make some libraries with similar functionality. I keep running into issues regarding objects.
Is the best way to create a super object $this somehow? My main issue is that I've created a view object and I run a function called load which looks like so:
class View {
public function __construct() {
}
public function load($file = NULL, $data = array()) {
if($file) {
$file .= '.php';
if(file_exists($file)) {
// Extract variables BEFORE including the file
extract($data);
include $file;
return TRUE;
} else {
echo 'View not found';
return FALSE;
}
} else {
return FALSE;
}
}
}
Then in my php file, I have at the top include 'libraries.php'; which looks like:
include 'database.php';
include 'view.php';
include 'input.php';
include 'form.php';
$config = array(
'host' => 'localhost',
'username' => 'username',
'password' => 'password',
'database' => 'database'
);
$database = new Database($config);
$view = new View();
$input = new Input();
$form = new Form();
From the file which I included the libraries, I am able to write something like $form->value('name'); without errors. However, if I do something like this:
$view->load('folder/index', array('var_name' => 'var_value')); then from the folder/index.php file I can access $var_name just fine, but not $form->value('name');. I get errors such as Call to a member function value() on a non-object in ...
My question is how can I organize my libraries and classes in a way that will be reusable. I don't want to use a front loader (an index.php file that everything runs through first). This may be an issue with the way I wrote my classes, but I imagine it's a larger issue regarding where things are located etc.
Put your library/class files in a common directory. Something like:
www
|_includes
| |_classes
| | |_view.php
| |_config
| |_database.php
|_other_folder
|_index.php
You can then set a common include path in your .htaccess file to this "includes" directory:
php_value include_path .:/path/to/www/includes
Then the other_folder/index.php file just needs something like:
require_once('config/database.php');
require_once('classes/view.php');

MVC; Arbitrary routing path levels and parameters

I'm working on an (oh no, not another) MVC framework in PHP, primarily for education, but also fun and profit.
Anyways, I'm having some trouble with my Router, specifically routing to the correct paths, with the correct parameters. Right now, I'm looking at a router that (using __autoload()) allows for arbitrarily long routing paths:
"path/to/controller/action"
"also/a/path/to/a/controller/action"
Routing starts at the application directory, and the routing path is essentially parallel with the file system path:
"/framework/application/path/to/controller.class.php" => "action()"
class Path_To_Controller{
public function action(){}
}
"/framework/application/also/a/path/to/a/controller.class.php" => "action()"
class Also_A_Path_To_A_Controller{
public function action(){}
}
This will allow for module configuration files to be available at varying levels of the application file system. The problem is of course, when we introduce routing path parameters, it becomes difficult differentiating where the routing path ends and the path parameters begin:
"path/to/controller/action/key1/param1/key2/param2"
Will obviously be looking for the file:
"/framework/application/path/to/controller/action/key1/param1/key2.class.php"
=> 'param2()'
//no class or method by this name can be found
This ain't good. Now this smells like a design issue of course, but I'm certain there must be a clean way to circumvent this problem.
My initial thoughts were to test each level of the routing path for directory/file existence.
If it hits 1+ directories followed by a file, additional path components are an action followed by parameters.
If it hits 1+ directories and no file is found, 404 it.
However, this is still susceptible to erroneously finding files. Sure this can be alleviated by stricter naming conventions and reserving certain words, but I'd like to avoid that if possible.
I don't know if this is the best approach. Has anyone solved such an issue in an elegant manner?
Well, to answer my own question with my own suggestion:
// split routePath and set base path
$routeParts = explode('/', $routePath);
$classPath = 'Flooid/Application';
do{
// append part to path and check if file exists
$classPath .= '/' . array_shift($routeParts);
if(is_file(FLOOID_PATH_BASE . '/' . $classPath . '.class.php')){
// transform to class name and check if method exists
$className = str_replace('/', '_', $classPath);
if(method_exists($className, $action = array_shift($routeParts))){
// build param key => value array
do{
$routeParams[current($routeParts)] = next($routeParts);
}while(next($routeParts));
// controller instance with params passed to __construct and break
$controller = new $className($routeParams);
break;
}
}
}while(!empty($routeParts));
// if controller exists call action else 404
if(isset($controller)){
$controller->{$action}();
}else{
throw new Flooid_System_ResponseException(404);
}
My autoloader is about as basic as it gets:
function __autoload($className){
require_once FLOOID_PATH_BASE . '/' . str_replace('_', '/', $className) . '.class.php';
}
This works, surprisingly well. I've yet to implement certain checks, like ensuring that the requested controller in fact extends from my Flooid_System_ControllerAbstract, but for the time being, this is what I'm running with.
Regardless, I feel this approach could benefit from critique, if not a full blown overhaul.
I've since revised this approach, though it ultimately performs the same functionality. Instead of instantiating the controller, it passes back the controller class name, method name, and parameter array. The guts of it all are in getVerifiedRouteData(), verifyRouteParts() and createParamArray(). I'm thinking I want to refactor or revamp this class though. I'm looking for insight on where I can optimize readability and usability.:
class Flooid_Core_Router {
protected $_routeTable = array();
public function getRouteTable() {
return !empty($this->_routeTable)
? $this->_routeTable
: null;
}
public function setRouteTable(Array $routeTable, $mergeTables = true) {
$this->_routeTable = $mergeTables
? array_merge($this->_routeTable, $routeTable)
: $routeTable;
return $this;
}
public function getRouteRule($routeFrom) {
return isset($this->_routeTable[$routeFrom])
? $this->_routeTable[$routeFrom]
: null;
}
public function setRouteRule($routeFrom, $routeTo, Array $routeParams = null) {
$this->_routeTable[$routeFrom] = is_null($routeParams)
? $routeTo
: array($routeTo, $routeParams);
return $this;
}
public function unsetRouteRule($routeFrom) {
if(isset($this->_routeTable[$routeFrom])){
unset($this->_routeTable[$routeFrom]);
}
return $this;
}
public function getResolvedRoutePath($routePath, $strict = false) {
// iterate table
foreach($this->_routeTable as $routeFrom => $routeData){
// if advanced rule
if(is_array($routeData)){
// build rule
list($routeTo, $routeParams) = each($routeData);
foreach($routeParams as $paramName => $paramRule){
$routeFrom = str_replace("{{$paramName}}", "(?<{$paramName}>{$paramRule})", $routeFrom);
}
// if !advanced rule
}else{
// set rule
$routeTo = $routeData;
}
// if path matches rule
if(preg_match("#^{$routeFrom}$#Di", $routePath, $paramMatch)){
// check for and iterate rule param matches
if(is_array($paramMatch)){
foreach($paramMatch as $paramKey => $paramValue){
$routeTo = str_replace("{{$paramName}}", $paramValue, $routeTo);
}
}
// return resolved path
return $routeTo;
}
}
// if !strict return original path
return !$strict
? $routePath
: false;
}
public function createParamArray(Array $routeParts) {
$params = array();
if(!empty($routeParts)){
// iterate indexed array, use odd elements as keys
do{
$params[current($routeParts)] = next($routeParts);
}while(next($routeParts));
}
return $params;
}
public function verifyRouteParts($className, $methodName) {
if(!is_subclass_of($className, 'Flooid_Core_Controller_Abstract')){
return false;
}
if(!method_exists($className, $methodName)){
return false;
}
return true;
}
public function getVerfiedRouteData($routePath) {
$classParts = $routeParts = explode('/', $routePath);
// iterate class parts
do{
// get parts
$classPath = 'Flooid/Application/' . implode('/', $classParts);
$className = str_replace('/', '_', $classPath);
$methodName = isset($routeParts[count($classParts)]);
// if verified parts
if(is_file(FLOOID_PATH_BASE . '/' . $classPath . '.class.php') && $this->verifyRouteParts($className, $methodName)){
// return data array on verified
return array(
'className'
=> $className,
'methodName'
=> $methodName,
'params'
=> $this->createParamArray(array_slice($routeParts, count($classParts) + 1)),
);
}
// if !verified parts, slide back class/method/params
$classParts = array_slice($classParts, 0, count($classParts) - 1);
}while(!empty($classParts));
// return false on not verified
return false;
}
}

Why does class get redeclared multiple times?

Ok here is a method I use for initializing models in my controller actions:
protected $_tables = array();
protected function _getTable($table)
{
if (false === array_key_exists($table, $this->_tables)) {
include APPLICATION_PATH . '/modules/'
. $this->_request->getModuleName() . '/models/' . $table . '.php';
$this->_tables[$table] = new $table();
echo 'test ';
}
return $this->_tables[$table];
}
Then when I call the _getTable() method two times (for example once in init() method and once in the controller action) it prints:
test test test test test test
On top of the page. Shouldn't it just return the object from the _tables array() because of the array_key_exists() check? In other words shouldn't the part inside the array_key_exists() function get executed only once when the method is called multiple times?
UPDATE:
So the problem is this - for some reason the layout gets printed twice (so it's layout printed and inside the layout where there is layout()->content; ?> it prints the layout again). I have no idea why it does this as it worked well on the previous server and also on localhost.
In the snippet you show:
protected $this->_tables = array();
This is not valid syntax, it should be:
protected $_tables = array();
Also, why not just use include_once and let PHP handle this for you? Alternatively, you could use the Zend_Loader. Don't reinvent the wheel.
What you are really looking for is the loading of module based resources. Instead of re-inventing the wheel, why not just use the (module) resource autoloaders of ZF? See the documentation at:
http://framework.zend.com/manual/en/zend.loader.autoloader-resource.html
When you use Zend_Application (I'm assuming you don't), you get these automatically. If you don't you could do something like
$loaders = array();
$frontController = Zend_Controller_Front::getInstance();
foreach($frontController->getControllerDirectory() as $module => $directory) {
$resourceLoader = new Zend_Application_Module_Autoloader(array(
'namespace' => ucfirst($module) . '_',
'basePath' => dirname($directory),
));
$resourceLoader->addResourceTypes(array(
'table' => array(
'path' => 'models/',
'namespace' => 'Table'
));
$loaders[$module] = $resourceLoader;
}
//build array of loaders
$loader = Zend_Loader_Autoloader::getInstance();
$loader->setAutoloaders($loaders);
//set them in the autoloader
This approach is a bit naive, but it should give you nice autoloading.

Categories