I have a menu which creates a GET statement in the url
<li>Contact. This get is used to get the corresponding content.
As in, the url will look like ?Page=Contact and than it will load the content from Contact.
Now in another file i have a switch that checks the GET statement in the url.
$GetStatement = $ConfigPage->getFormVariable('Page');
switch ($GetStatement){
case "Home":
$Content = new ContentHome();
$ConfigPage->SetProperty('content', $Content);
break;
case "Contact":
$Content = new ContentContact();
$ConfigPage->SetProperty('content', $Content);
break;
}
Of course there are more cases in this switch, but it's useless to show. Now this switch works flawless. But as my content grows i have to keep adding more cases. And now i am at the point i want this to be automated. Of course i have tried to. but now i have literally no idea how to do this, or what to do.
Edit:
All the different content are in different files. With all unique class name. as you can see above. ContentContact is inside file Contact.php with a class named ContentContact
While that's not a very efficient setup (I don't know that I would create one class per page) what you could do is create a function that would do the work of looking for your class for you
function loadClass($name) {
$class_name = 'Content' . $name;
if(!class_exists($class_name)) return false;
$class = new $class_name();
return $class;
}
$class = loadClass($ConfigPage->getFormVariable('Page'));
if($class) $ConfigPage->SetProperty('content', $class);
Something like this could do the trick:
function getContentInstance($stmt) {
$name = 'Content'.$stmt;
$path = 'Content/'.str_replace(".", "", $name).'.class.php'; // needs to do even more ...
if(class_exists($name) {
return new $name();
} else {
if (file_exists($path)) {
include $path;
return new $name();
}
user_error('Class '.$name.' not found');
}
}
Related
<?php
function loadModule($module,$action=null,$param=null){
$access_group = $_SESSION['access_group'];
$auth_controller = new auth();
if($auth_controller->check_access($module,$action)){
if (!class_exists($module)) {
include "modules/" . $module . "/controller/controller.php";
}
$controller = new $module();
if ($action) {
if ($param) {
return $controller->{$action}($param);
} else {
return $controller->{$action}();
}
} else {
return $controller->index();
}
}else{
if (!class_exists("forbidden")) {
include_once "modules/forbidden/controller/controller.php";
}
$forbidden_controller = new forbidden();
$forbidden_controller->view->render();
}
i seen a func like this , i dont know what happen in line 14 and 16 give me a hand and explain it.
i want to know how and where we should use this kind of codes
You're talking about this line:
$controller->{$action}();
This it less complex than it looks. You're used to the syntax of normal class method calls, like this:
$controller->getOrderNumber();
In the code in your question the method name has been replace by a variable.
See example 2 here.
Note that the two lines below are equivalent:
$controller->{$action}();
$controller->$action();
I would use variable method names very sparingly. They make reading code and debugging more difficult. On top of that they might pose a security risk.
I have a normal HTML menu that passes a GET statement to the url.
<li>Home</li>
Just like this, al though this is ofcourse only 1 item of the entire menu.
In a seperated file I have an function that checks if an GET or POST statement exist,
and If it exist and is not NULL then it will give the value back to where it was called.
public function getFormVariable($value){
switch (strtoupper($_SERVER['REQUEST_METHOD'])) {
case 'GET':
if (isset($_GET[$value]) && $_GET[$value] != NULL) {
return $_GET[$value];
}
else{
return false;
}
break;
case 'POST':
if (isset($POST[$value]) && $POST[$value] != NULL) {
return $POST[$value];
}
else{
return false;
}
break;
default:
return false;
}
}
And with the following code it takes the get value and finds the corrosponding class
(every class is in a seperated file, and every class is 1 link in my menu)
In this class there is just some regular functions/data that gives the content of that page.
$class = loadClass($ConfigPage->getFormVariable('Page'));
$ConfigPage->SetProperty('content', $class);
function loadClass($Page){
$class_name = 'Content' . $Page;
if(!class_exists($class_name)){
return 'Error: Content has not been found.';
}
$class = new $class_name();
return $class;
}
Explaining: The menu gives a GET value of 'Contact' which is then check by GetFormVariable() and then the corresponding class is found which gives back the content that class holds.
Now my question:
When the function LoadClass() cant find the name of the class it was given through the GET statement, it should return a error string. But this is not happening. I get a beautiful big orange error from PHP saying the following:
Fatal error: Call to a member function render() on a non-object in
E:\Program files\wamp\www\BDW\Class\Html_Page.Class.php on line 147
Line 147 is where to object is called
echo $this->content->render();
The Render function is as it says a normal return function inside the content classes.
Why do i get this error, and how do i fix it?
Second question. If there is no GET statement in the url. It gives the exact same error. which is pretty logical. But how do i make it show ContentHome when there is no GET statement in the url, and an ERROR when the value of the GET statement is incorrect.
Thank you for reading,
If there is anything unclear please tell me. English is not my native language, and after all. I am here to learn.
EDIT:
My knowledge of PHP is not great enough, so i decided when a class can not be found. it brings you back to home without any error. which i wanted in the first place.
Here is my new code which is still not working, why?
$class = loadClass($ConfigPage->getFormVariable('Page'));
$ConfigPage->SetProperty('content', $class);
function loadClass($Page){
$class_name = 'Content' . $Page;
$Default_Class = 'ContentHome';
if(!class_exists($class_name)){
//echo 'Error: Content has not been found.';
$class = new $Default_Class();
return $class;
}
else{
$class = new $class_name();
return $class;
}
$ConfigPage->Render();
}
It's happening because of this line :
if(!class_exists($class_name)){
return 'Error: Content has not been found.';
}
In the case the class doesn't exist, you're returning a string from the function, and afterwards trying to call a method render() on it. You can fix it either by changing this behaviour (don't return an error string, but use an Exception or trigger_error() ) or checking that the function loadClass() does return a valid object through is_object() for example.
Regarding your second question, you should expand your tests in loadClass() to handle the empty GET variable case, and substitute the empty string with a "Home" default value.
Update :
Example usage for is_object in your case :
$class = loadClass($ConfigPage->getFormVariable('Page'));
if(! is_object($class)) {
// if $class is not an object, then something went wrong above
throw new \Exception('Invalid data returned from loadClass()');
}
$ConfigPage->SetProperty('content', $class);
I'm new to PHP and I have an issue I can't seem to fix or find a solution to.
I'm trying to create a helper function that will return an 'object' filled with information pulled from an XML file. This helper function, named functions.php contains a getter method which returns a 'class' object filled with data from an SVN log.xml file.
Whenever I try to import this file using include 'functions.php'; none of the code after that line runs the calling function's page is blank.
What am I doing wrong?
Here is what the functions.php helper method and class declaration looks like:
<?php
$list_xml=simplexml_load_file("svn_list.xml");
$log_xml=simplexml_load_file("svn_log.xml");
class Entry{
var $revision;
var $date;
}
function getEntry($date){
$ret = new Entry;
foreach ($log_xml->logentry as $logentry){
if ($logentry->date == $date){
$ret->date = $logentry->date;
$ret->author = $logentry->author;
}
}
return $ret;
}
I'm not sure what the point of having a separate helper function from the class is, personally I'd combine the two. Something like this
other-file.php
require './Entry.php';
$oLogEntry = Entry::create($date, 'svn_log.xml');
echo $oLogEntry->date;
echo $oLogEntry->revision;
Entry.php
class Entry
{
public $revision;
public $date;
public $author;
public static function create($date, $file) {
$ret = new Entry;
$xml = simplexml_load_file($file);
foreach($xml->logentry as $logentry) {
if($logentry->date == $date) {
$ret->date = $logentry->date;
$ret->author = $logentry->author;
$ret->revision = $logentry->revision;
}
}
return $ret;
}
}
EDIT
In light of the fact OP is new to PHP, I'll revise my suggestion completely. How about ditching the class altogether here? There's hardly any reason to use a class I can see at this point; let's take a look at using an array instead.
I might still move the simplexml_load_file into the helper function though. Would need to see other operations to merit keeping it broken out.
entry-helper.php
function getEntry($date, $file) {
$log_xml = simplexml_load_file($file);
$entry = array();
foreach($log_xml->logentry as $logentry) {
if($logentry->date == $date) {
$entry['date'] = $logentry->date;
$entry['author'] = $logentry->author;
$entry['revision'] = $logentry->revision;
}
}
return $entry;
}
other-file.php
require './entry.php';
$aLogEntry = Entry::create($date, 'svn_log.xml');
echo $aLogEntry['date'];
echo $aLogEntry['revision'];
EDIT
One final thought.. Since you're seemingly searching for a point of interest in the log, then copying out portions of that node, why not just search for the match and return that node? Here's what I mean (a return of false indicates there was no log from that date)
function getEntry($date, $file) {
$log_xml = simplexml_load_file($file);
foreach($log_xml->logentry as $logentry) {
if($logentry->date == $date) {
return $logentry;
return false;
}
Also, what happens if you have multiple log entries from the same date? This will only return a single entry for a given date.
I would suggest using XPATH. There you can throw a single, concise XPATH expression at this log XML and get back an array of objects for all the entries from a given date. What you're working on is a good starting point, but once you have the basics, I'd move to XPATH for a clean final solution.
What I'm currently doing is this:
I have a $path variable, which is everything after index.php/ (which I hide with .htaccess) up to a question mark to ignore the querystring.
Then I use a switch with preg_match cases on that variable to determine what script it should call. For example:
switch (true)
{
case preg_match('{products/view/(?P<id>\d+)/?}', $path, $params):
require 'view_product.php';
break;
...
default:
require '404.php';
break;
}
This way I can access the product id just using $params['id'] and, if needed, use the querystring for filtering, pagination, etc.
Is there anything wrong with this approach?
You shouldn’t use switch like this.
Better use an array and foreach like:
$rules = array(
'{products/view/(?P<id>\d+)/?}' => 'view_product.php'
);
$found = false;
foreach ($rules as $pattern => $target) {
if (preg_match($pattenr, $path, $params)) {
require $target;
$found = true;
break;
}
}
if (!$found) {
require '404.php';
}
The wrong part would be the switch case . As a better practice i would suggest you store all regex into an array and test it with that . It would be easyer to save the routes to a config file , or an ini file or database or xml or whatever will make you're life easyer in to long run ( if you need to edit/add/delete new routes ) .
In the second part you could use parse_url php function instead of regex witch will speed you're script a bit .
This situation arises from someone wanting to create their own "pages" in their web site without having to get into creating the corresponding actions.
So say they have a URL like mysite.com/index/books... they want to be able to create mysite.com/index/booksmore or mysite.com/index/pancakes but not have to create any actions in the index controller. They (a non-technical person who can do simple html) basically want to create a simple, static page without having to use an action.
Like there would be some generic action in the index controller that handles requests for a non-existent action. How do you do this or is it even possible?
edit: One problem with using __call is the lack of a view file. The lack of an action becomes moot but now you have to deal with the missing view file. The framework will throw an exception if it cannot find one (though if there were a way to get it to redirect to a 404 on a missing view file __call would be doable.)
Using the magic __call method works fine, all you have to do is check if the view file exists and throw the right exception (or do enything else) if not.
public function __call($methodName, $params)
{
// An action method is called
if ('Action' == substr($methodName, -6)) {
$action = substr($methodName, 0, -6);
// We want to render scripts in the index directory, right?
$script = 'index/' . $action . '.' . $this->viewSuffix;
// Script file does not exist, throw exception that will render /error/error.phtml in 404 context
if (false === $this->view->getScriptPath($script)) {
require_once 'Zend/Controller/Action/Exception.php';
throw new Zend_Controller_Action_Exception(
sprintf('Page "%s" does not exist.', $action), 404);
}
$this->renderScript($script);
}
// no action is called? Let the parent __call handle things.
else {
parent::__call($methodName, $params);
}
}
You have to play with the router
http://framework.zend.com/manual/en/zend.controller.router.html
I think you can specify a wildcard to catch every action on a specific module (the default one to reduce the url) and define an action that will take care of render the view according to the url (or even action called)
new Zend_Controller_Router_Route('index/*',
array('controller' => 'index', 'action' => 'custom', 'module'=>'index')
in you customAction function just retrieve the params and display the right block.
I haven't tried so you might have to hack the code a little bit
If you want to use gabriel1836's _call() method you should be able to disable the layout and view and then render whatever you want.
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
I needed to have existing module/controller/actions working as normal in a Zend Framework app, but then have a catchall route that sent anything unknown to a PageController that could pick user specified urls out of a database table and display the page. I didn't want to have a controller name in front of the user specified urls. I wanted /my/custom/url not /page/my/custom/url to go via the PageController. So none of the above solutions worked for me.
I ended up extending Zend_Controller_Router_Route_Module: using almost all the default behaviour, and just tweaking the controller name a little so if the controller file exists, we route to it as normal. If it does not exist then the url must be a weird custom one, so it gets sent to the PageController with the whole url intact as a parameter.
class UDC_Controller_Router_Route_Catchall extends Zend_Controller_Router_Route_Module
{
private $_catchallController = 'page';
private $_catchallAction = 'index';
private $_paramName = 'name';
//-------------------------------------------------------------------------
/*! \brief takes most of the default behaviour from Zend_Controller_Router_Route_Module
with the following changes:
- if the path includes a valid module, then use it
- if the path includes a valid controller (file_exists) then use that
- otherwise use the catchall
*/
public function match($path, $partial = false)
{
$this->_setRequestKeys();
$values = array();
$params = array();
if (!$partial) {
$path = trim($path, self::URI_DELIMITER);
} else {
$matchedPath = $path;
}
if ($path != '') {
$path = explode(self::URI_DELIMITER, $path);
if ($this->_dispatcher && $this->_dispatcher->isValidModule($path[0])) {
$values[$this->_moduleKey] = array_shift($path);
$this->_moduleValid = true;
}
if (count($path) && !empty($path[0])) {
$module = $this->_moduleValid ? $values[$this->_moduleKey] : $this->_defaults[$this->_moduleKey];
$file = $this->_dispatcher->getControllerDirectory( $module ) . '/' . $this->_dispatcher->formatControllerName( $path[0] ) . '.php';
if (file_exists( $file ))
{
$values[$this->_controllerKey] = array_shift($path);
}
else
{
$values[$this->_controllerKey] = $this->_catchallController;
$values[$this->_actionKey] = $this->_catchallAction;
$params[$this->_paramName] = join( self::URI_DELIMITER, $path );
$path = array();
}
}
if (count($path) && !empty($path[0])) {
$values[$this->_actionKey] = array_shift($path);
}
if ($numSegs = count($path)) {
for ($i = 0; $i < $numSegs; $i = $i + 2) {
$key = urldecode($path[$i]);
$val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null;
$params[$key] = (isset($params[$key]) ? (array_merge((array) $params[$key], array($val))): $val);
}
}
}
if ($partial) {
$this->setMatchedPath($matchedPath);
}
$this->_values = $values + $params;
return $this->_values + $this->_defaults;
}
}
So my MemberController will work fine as /member/login, /member/preferences etc, and other controllers can be added at will. The ErrorController is still needed: it catches invalid actions on existing controllers.
I implemented a catch-all by overriding the dispatch method and handling the exception that is thrown when the action is not found:
public function dispatch($action)
{
try {
parent::dispatch($action);
}
catch (Zend_Controller_Action_Exception $e) {
$uristub = $this->getRequest()->getActionName();
$this->getRequest()->setActionName('index');
$this->getRequest()->setParam('uristub', $uristub);
parent::dispatch('indexAction');
}
}
You could use the magic __call() function. For example:
public function __call($name, $arguments)
{
// Render Simple HTML View
}
stunti's suggestion was the way I went with this. My particular solution is as follows (this uses indexAction() of whichever controller you specify. In my case every action was using indexAction and pulling content from a database based on the url):
Get an instance of the router (everything is in your bootstrap file, btw):
$router = $frontController->getRouter();
Create the custom route:
$router->addRoute('controllername', new Zend_Controller_Router_Route('controllername/*', array('controller'=>'controllername')));
Pass the new route to the front controller:
$frontController->setRouter($router);
I did not go with gabriel's __call method (which does work for missing methods as long as you don't need a view file) because that still throws an error about the missing corresponding view file.
For future reference, building on gabriel1836 & ejunker's thoughts, I dug up an option that gets more to the point (and upholds the MVC paradigm). Besides, it makes more sense to read "use specialized view" than "don't use any view".
// 1. Catch & process overloaded actions.
public function __call($name, $arguments)
{
// 2. Provide an appropriate renderer.
$this->_helper->viewRenderer->setRender('overload');
// 3. Bonus: give your view script a clue about what "action" was requested.
$this->view->action = $this->getFrontController()->getRequest()->getActionName();
}
#Steve as above - your solution sounds ideal for me but I am unsure how you implmeented it in the bootstrap?