Why does class get redeclared multiple times? - php

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.

Related

Front controller best way to link parameters to application logic?

I am building an app using Front Controller design pattern and there is just one page index.php through which all user requests pass as parameters (versus different pages/controllers in regular design).
How can I connect these parameters to application logic?
e.g. I have two different actions:
index.php?action=userLogin&username=admin&password=qwerty //process user login
index.php?action=displayUsersTable //show registered users
Currently I have an array with all actions the system accepts (along with expected arguments) and I compare action param from URL to the key of this array and then check the required arguments for this action.
//1 = optional, 2=required
$systemActions = [
"userLogin" => [
"login" => 2,
"password" => 2
],
"displayUsersTable" => []
];
Obviously this going to become a monster array as the system grows.
Is there better approach to bind parameters sent to front controller to system actions?
As the code is "fixed" (i.e. not driven from a database) then there is no need to pump into an array (and all the processing/memory overhead that it requires. So yes, it can be improved.
But there are many options depending on how much the project will grow.
Simplest
The simplest would be simple "if" statements, or a switch. I'd start there to keep it simple.
More Complex
You say other projects have different pages / controllers - but there is a reason. And as you're asking for improvements, especially if you're expecting the project to grow to such as extent that you're looking for optimizations, then you really should consider these reasons (and split into files).
At the other end of the scale, you can split all the calls into files/classes and auto-load the files/classes.
This way you only execute the code you need (smaller file sizes), is very modular and easy to work on collaboratively. And if you add a new action, you don't need to modify the index or array - you only modify the action file you're working on.
Example (vastly simplified from a project I'm currently working on with this approach):
1) Create a "baseAction" base class the all actions will extend from. You can add common features such as cleaning/pre-processing parameters, logging, validating headers etc.
abstract class baseAction {
protected $aExpectedParams = [];
protected $aParams = [];
protected $validParams = true;
function __construct() {
foreach (self::$aExpectedParams as $name=>$aParam) {
if (isset($_GET[$name]))
if ($aParam['type'] == 'string') {
self::$aParams[$name] = $_GET[$name];
} elseif ($aParam['type'] == 'int') {
self::$aParams[$name] = (int)$_GET[$name];
}
} elseif ($aParam['required']) {
self::$validParams = false;
}
}
}
// This is the called function
abstract function execute();
}
2) Create the "action" classes, by extending the base Action. Save these in individual files (so others can collaborate on the project without interfering).
// put in 'actions/userLogin.php
class userLogin extends baseAction {
protected $aExpectedParams = [
'login' => ['type' => 'string', 'required' => true]
'password' => ['type' => 'string', 'required' => true] // NOTE: you should never actually pass password unencrypted through "get" as they'll get stuck in user logs!
];
public function execute() {
// Do Whatever
}
}
.
// put in 'actions/displayUsersTable.php
class displayUsersTable extends baseAction {
public function execute() {
// Do Whatever
}
}
3) Create an autoloader to pull in those individual files.
function myAutoloader($className) {
if (file_exists(__DIR__ . '/actions/' . $className . '.php')) {
require_once(__DIR__ . '/actions/' . $className . '.php');
}
}
spl_autoload_register ('myAutoloader');
4) Then your index.php is as clean as
$action = $_GET['action'] ?? '';
if (strlen($action) > 0 && class_exists($action) && method_exists($action, 'execute')) {
$oAction = new $action();
$oAction->execute();
} else {
// Oopsie
}
(Notes on this last snippet: the "class_exists" triggers the auto-loader. the "method_exists" is to check someone hasn't requested a common php class such as "object"; if you're being safer you should namespace this or add extra validation. This is just an example!)

Replace matches from one string in another string

I'm writing a router for my PHP MVC application, and I currently need to find a way to use matches in a route as variables for controllers and actions.
For example, if I have the following route: /users/qub1/home
I would like to use a regex similar to this: \/users\/(?!/).*\/(?!/).*
Then I would like to specify the action like this: $2 (in the example, this would be home)
And the parameter to pass to the action like this: $1 (in the example, this would be qub1).
This would then execute code similar to this:
$controller = new UsersController();
$controller->$2($1);
Configured routes are stored as such:
public function setRoute($route, $regex = false, $controller = 'Index', $action = 'index', $parameters = array()) {
if(!$regex) {
$route = preg_quote($route, '/');
}
$this->routes[] = [
'route' => $route,
'controller' => $controller,
'action' => $action,
'parameters' => $parameters
];
}
Where the above example would be stored like this: $router->setRoute('\/users\/(?!/).*\/(?!/).*', true, 'User', '$2', [$1]);
So essentially, I want to use matched groups from one regex expression as variables to replace inside another regex expression (if that makes sense).
I hope I've described my problem accurately enough. Thanks for the help.
EDIT:
The code I'm currently using to parse routes (it doesn't work, but it should illustrate what I'm trying to achieve):
public function executeRoute($route) {
// Loop over available routes
foreach($this->routes as $currentRoute) {
// Check if the current route matches the provided route
if(preg_match('/^' . $currentRoute['route'] . '$/', '/' . $route, $matches)) {
// If it matches, perform the current route's action
// Define names
$controllerClass = preg_replace('\$.*\d', $matches[str_replace('$', '', '$1')], ucfirst($currentRoute['controller'] . 'Controller'));
$actionMethod = preg_replace('\$.*\d', $matches[str_replace('$', '', '$1')], strtolower($currentRoute['action']) . 'Action');
$parameters = preg_replace('\$.*\d', $matches[str_replace('$', '', '$1')], join(', ', $currentRoute['parameters']));
// Create the controller
$controller = new $controllerClass();
$controller->$actionMethod($parameters);
// Return
return;
}
}
}
While I am not sure that it is a very well designed approach, it is doable. This is the code that replaces yours within the if:
// you already specify the controller name, so no need for replacing
$controllerClass = ucfirst($currentRoute['controller'] . 'Controller');
// also here, no need to replace. You just need to get the right element from the array
$actionMethod = strtolower($matches[ltrim($currentRoute['action'], '$')] . 'Action';
// here I make the assumption that this parameter is an array. You might want to add a check here
$parameters = array();
foreach ($currentRoute['parameters'] as $parameter) {
$parameters[] = $matches[ltrim($parameter, '$')];
}
// check before instantiating
if (!class_exists($controllerClass)) {
die('invalid controller');
}
$controller = new $controllerClass();
// also check before invoking the method
if (!method_exists($controller, $actionMethod)) {
die('invalid method');
}
// this PHP function allows to call the function with a variable number of parameters
call_user_func_array(array($controller, $actionMethod), $parameters);
One reason why your approach is not very favorable is that you make a lot of assumptions:
the regex needs to have as many groups as you use in the other parameters
if you are imprecise with the regex, it might be possible to call any method in your code
Maybe this will be good enough for your project but you should consider using a well-established router if you want to create something not for educational purposes.

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

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

Partials in lithium

Normally I use the Zend Framework and this is something I miss in Lithium. Partials. There is a render method in the view where you can use 'elements' which is the closest I got.
<?php $this->_render('element', 'form); ?>
This does work, however it requires that the form.html.php file is in the /views/elements folder. Is it possible to let it search in another path? Like /views/users/ so it gets the file /views/users/form.html.php.
I have tried the following, since I found out that the render method does accept an options argument wherein you can specify a path. So I made an Helper to fix this problem for me.
namespace app\extensions\helper;
use lithium\template\TemplateException;
class Partial extends \lithium\template\Helper
{
public function render($name, $folder = 'elements', $data = array())
{
$path = LITHIUM_APP_PATH . '/views/' . $folder;
$options['paths']['element'] = '{:library}/views/' . $folder . '/{:template}.{:type}.php';
return $this->_context->view()->render(
array('element' => $name),
$data,
$options
);
}
}
However it still only searches in the /view/elements folder, not in the path I specified.
Is there something I am doing wrong?
Why using plugins when this stuff can hopefully be done by Lithium :-)
I don't know Zend, but here is an exemple to configure elements default paths differently, to load them from the related view folder, instead of a shared path.
And let's add one more thing: we want to differentiate elements/partials from a normal view, by appending un underscore to the name of the file (mimic Rails partials)
First, reconfigure Media during the bootstrap process (config/bootstrap/media.php)
Media::type('default', null, array(
'view' => 'lithium\template\View',
'paths' => array(
'layout' => '{:library}/views/layouts/{:layout}.{:type}.php',
'template' => '{:library}/views/{:controller}/{:template}.{:type}.php',
'element' => array(
'{:library}/views/{:controller}/_{:template}.{:type}.php',
'{:library}/views/elements/{:template}.{:type}.php'
)
)
));
Then, use it
Suppose a controller Documents. Call on a view:
<?= $this->_render('element', 'foo', $data, array('controller' => 'documents')); ?>
This will look for a file inside views/documents/_foo.html.php and if doesn't exists, fallback to /views/elements/foo.html.php
This kind of simple re-configuration of framework defaults, can be done in Lithium for a bunch of stuffs (default controllers paths to create namespaces, views paths, libraries, etc ...)
One more example to re-maps your template paths so you can have stuff like pages/users_{username}.php instead of the Lithium default:
https://gist.github.com/1854561
Fixed it. Works like a charm. Zend like Partials in Lithium.
<?php
namespace app\extensions\helper;
use lithium\template\View;
class Partial extends \lithium\template\Helper
{
public function render($name, $folder = 'elements', array $data = array())
{
$view = new View(array(
'paths' => array(
'template' => '{:library}/views/' . $folder . '/' . $name . '.{:type}.php'
)
));
return $view->render('all', $data);
}
}
Can be used in templates like:
<?php echo $this->partial->render('filename', 'foldername', compact('foo', 'bar')); ?>
There is a plugin for partials. https://github.com/dmondark/li3_partials

where to save custom autoloaders in zend?

I am trying to use phpThumb in my application by making a custom autoloader.
class My_Loader_Autoloader_PhpThumb implements Zend_Loader_Autoloader_Interface {
static protected $php_thumb_classes = array(
'PhpThumb' => 'PhpThumb.inc.php',
'ThumbBase' => 'ThumbBase.inc.php',
'PhpThumbFactory' => 'ThumbLib.inc.php',
'GdThumb' => 'GdThumb.inc.php',
'GdReflectionLib' => 'thumb_plugins/gd_reflection.inc.php',
);
/**
* Autoload a class
*
* #param string $class
* #return mixed
* False [if unable to load $class]
* get_class($class) [if $class is successfully loaded]
*/
public function autoload($class) {
$file = APPLICATION_PATH . '/../library/PhpThumb/' . self::$php_thumb_classes[$class];
if (is_file($file)) {
require_once($file);
return $class;
}
return false;
}
}
i saved this file as PhpThumb.php in the loaders/Autoloader folder. Then added this line to the bootstrap file:
Zend_Loader_Autoloader::getInstance()->pushAutoloader(new My_Loader_Autoloader_PhpThumb());
But it produces an error saying the class was not found. I am guessing that the 'CustomLoader_PhpThumb.php' needs to be saved somewhere else. Any idea guys ?
Update1:
Bootstrap.php file contents
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initAutoload()
{
$autoLoader=Zend_Loader_Autoloader::getInstance();
$resourceLoader=new Zend_Loader_Autoloader_Resource(array(
'basePath'=>APPLICATION_PATH,
'namespace'=>'',
'resourceTypes'=>array(
'form'=>array(
'path'=>'forms/',
'namespace'=>'Form_'
),
'models'=>array(
'path'=>'models/',
'namespace'=>'Model_'
),
)
));
//return $autoLoader;
$resourceLoader->addResourceType('loader', 'loaders/', 'My_Loader_');
$autoLoader->pushAutoloader($resourceLoader);
$autoLoader->pushAutoloader(new My_Loader_Autoloader_PhpThumb());
}
}
?>
I'm also using PhpThumb and the same autoloader. In my case however it is called My_Loader_Autoloader_PhpThumb and is located in APPLICATION_PATH . '/loaders/Autoloader/PhpThumb.php.
In my Bootstrap.php, first I load the loaders path to the Zend_Loader_Autoloader and then I push the My_Loader_Autoloader_PhpThumb autoloader as follows:
$autoLoader = Zend_Loader_Autoloader::getInstance();
$resourceLoader = new Zend_Loader_Autoloader_Resource(array(
'basePath' => APPLICATION_PATH,
'namespace' => '',
));
$resourceLoader->addResourceType('loader', 'loaders/', 'My_Loader_');
$autoLoader->pushAutoloader($resourceLoader);
$autoLoader->pushAutoloader(new My_Loader_Autoloader_PhpThumb());
Hope this will help.
PhpThumb.php should be saved to library/CustomLoader folder, and you should setup autoloader for this class also, for example in application.ini:
autoloaderNamespaces[] = "CustomLoader_"
or in Bootstrap.php:
$autoloader->registerNamespace('CustomLoader_');
I couldn't try, I hope it works!
I did it in another way, long more easy, without any autoloader, just using "My".
1.- Create a phpThumbFolder with all the files of the library into project/library/My/PhpThumb
2.- Create a file named PhpThumbFactory.php into that folder with the following content:
require_once('ThumbLib.inc.php');
class My_PhpThumb_PhpThumbFactory
{
public static function create ($filename = null, $options = array(), $isDataStream = false)
{
return PhpThumbFactory::create($filename, $options, $isDataStream);
}
}
3.- Enjoy:
public function editimageAction()
{
//rutaImg is the physical path to my images defined in application.ini
$rutaImg = Zend_Registry::get('config')->rutaImg. "test.jpg";
//Call to our "gateway" class
$thumb = My_PhpThumb_PhpThumbFactory::create($rutaImg);
//Resize
$thumb->adaptiveResize(20, 20);
//Show
$thumb->show();
$this->_helper->viewRenderer->setNoRender();
$this->_helper->layout->disableLayout();
}
I hope it can help anyone ;)
P.D. The "gateway" class is to avoid modifying any code of the library, so any possible update is easy.

Categories