I am re-building a website with PHP codeigniter that contains mostly static pages and some dynamic pages interspersed. The problem is, the pages are 1-5 layers deep within the navigation (ex. example.com/about/history/people/person/photos).
I'm not sure how to implement this in the controller. I currently have a crazy set of switch statements nested in switch statements. Here is a simple example:
class About extends MY_Controller
{
//**** INDEX page ****
public function index()
{
$this->setTitle('About');
$this->setDescription('About Stuff');
$this->loadView('about/index');
}
//-------------------------HISTORY section--------------------------------
public function history($sub1 = "", $sub2 = "", $sub3 = "", $sub4 = "")
{
$path = "";
switch($sub1){
//_____ PEOPLE section ____
case 'people':
switch($sub2){
//____ PERSON section ____
case 'person':
switch($sub3){
//**** PHOTOS page ****
case 'photos':
$this->setTitle('Photos');
$this->setDescription('Photo stuff');
$path = 'people/person/photos';
break;
//**** DOCUMENTS page ****
case 'documents':
//Load special scripts
$this->setTitle('Documents');
$this->setDescription('Document stuff');
$path = 'people/person/documents';
break;
//**** INDEX page ****
default:
$this->setTitle('Person');
$this->setDescription('Person stuff');
$path = 'people/person/index';
}
break;
//**** AnotherPerson page *****
case 'anotherPerson':
//Load database
$this->setTitle('AnotherPerson');
$this->setDescription('AnotherPerson stuff');
$path = 'people/anotherperson';
break;
//**** INDEX page ****
default:
$this->setTitle('People');
$this->setDescription('People stuff');
$path = 'people/index';
}
break;
//**** INDEX page ****
default:
$this->setTitle('History');
$this->setDescription('History stuff');
$path = 'index';
}
$this->loadView('about/history/' . $path );
}
}
I feel like I am approaching this incorrectly and it feels messy. So my question is: Is there a better (more compact/clean/dynamic) way of doing this?
You have a few options.
You could put the case statements in separate function. eg. if you've case 'documents' you could put this into a private function:
private function documents(){
//Load special scripts
$this->setTitle('Documents');
$this->setDescription('Document stuff');
$path = 'people/person/documents';
}
You could make an associative array with the title, description etc. in it and check if the variables are filled. After this you can call the array by with the $sub vars.
Related
I'm making a toolkit for php applications. I've a made a routing system based on some conventions, it works well but i would like to learn how to make mod_rewrite rules or any other stuff to finally make the url good to see and good for seo.
The route system starts from a config file that set the app and url roots.
$app_root = $_SERVER["DOCUMENT_ROOT"].dirname($_SERVER["PHP_SELF"])."/";
$app_url = $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/';
define("APP_URL",$app_url);
define("APP_ROOT",$app_root);
The route always get start from index.php, wich makes instances of controllers#actions from GET parameters controllers=?&action=?
This is the index.php
<?php
include_once 'controller/Frontend.php';
require 'libraries/Router.php';
$params=array();
if(isset($_GET['controller'])&&isset($_GET['action'])){
$c = $_GET['controller'];
$a = $_GET['action'];
// add all query string additional params to method signature i.e. &id=x&category=y
$queryParams = array_keys($_GET);
$queryValues = array_values($_GET);
for ($i=2;$i<count($queryParams);$i++) {
$params[$queryParams[$i]] = $queryValues[$i];
}
if ($_POST) {
// add all query string additional params to method signature i.e. &id=x&category=y
$queryParams = array_keys($_POST);
$queryValues = array_values($_POST);
for ($i=0;$i<count($_POST);$i++) {
$params[$queryParams[$i]] = $queryValues[$i];
}
}
include_once APP_ROOT."/controller/$c.php";
$controller = new $c();
$controller->$a($params);
} else {
//attiva controller predefinito
$controller = new Frontend();
$controller->index();
}
This allow to select what controller and what action the router must call.
The router function here get the APP URL from settings.php in the root. You give im the two controllers#action params as string and it make the URL like so:
index.php?controller=X&action=Y&[params...]
<?php
require './settings.php';
function router($controller,$action,$query_data="") {
$param = is_array($query_data) ? http_build_query($query_data) : "$query_data";
$url = APP_URL."index.php?controller=$controller&action=$action&$param";
return $url;
}
function relativeRouter ($controller,$action,$query_data=""){
$param = is_array($query_data) ? http_build_query($query_data) : "$query_data";
$url = "index.php?controller=$controller&action=$action&$param";
return $url;
}
function redirectToOriginalUrl() {
$url = $_SERVER['HTTP_REQUEST_URI'];
header("location: $url");
}
function switchAction ($controller, $action) {
$r = router($controller, $action);
header("location:$r", true, 302);
}
In templates file i call router('controller,'action') to retrive url's to actions and also pass GET/POST data (collected from index.php that put's them into the method signature as array).
Don't blame me for using global POST/GET without filtering i'm still developing the thing, security things will be made after ;)
What i would like to ask if someone could share some thoughts on how to make pretty urls like site/page/action....
For example www.site.com/blog/post?id=1
(Actually the N params in the router function ($query_data) works this way, you pass array['id' => '1'] and you get ?id=1)
What are best strategies to make good urls?
Thank you so much, still learning PHP.
If there are best way to do such things just give your feedback.
I found myself an answer to the question, i post here maybe it's useful.
I've added a .htaccess file in the root:
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
This will return each request to the root/index.php file.
Index file collect routes from the HTTP request, check if the route exist in the "routes.json" file.
URL are written in this way:
site.com/controller/action. GET params are written as follows
site.com/controller/action/[params]/[value]...... This output for example site.com/blog/post/id/1
That should be also fine for REST.
Here the index.php
<?php
require 'controller/Frontend.php';
require 'Class/Router.php';
//require 'libraries/Router.php';
/*
* ** ROUTING SETTINGS **
*/
$app_root = $_SERVER["DOCUMENT_ROOT"].dirname($_SERVER["PHP_SELF"])."/";
$app_url = $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/';
define("APP_URL",$app_url);
define("APP_ROOT",$app_root);
$basepath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1));
$uri = substr($_SERVER['REQUEST_URI'], strlen($basepath));
//echo $uri;
if ($uri == "/") {
$frontend = new Frontend();
$frontend->index();
} else {
$root = ltrim ($uri, '/');
//$paths = explode("/", $uri);
$paths = parse_url($root, PHP_URL_PATH);
$route = explode("/",$paths);
$request = new \PlayPhp\Classes\Request();
// controller
$c = $route[0];
// action
$a = $route[1];
$reverse = Router::inverseRoute($c,$a);
$rest = $_SERVER['REQUEST_METHOD'];
switch ($rest) {
case 'PUT':
//rest_put($request);
break;
case 'POST':
if (Router::checkRoutes($reverse, "POST")) {
foreach ($_POST as $name => $value) {
$request->setPost($name,$value);
}
break;
} else {
Router::notFound($reverse,"POST");
}
case 'GET':
if (Router::checkRoutes($reverse, "GET")) {
for ($i = 2; $i < count($route); $i++) {
$request->setGet($route[$i], $route[++$i]);
}
break;
} else {
Router::notFound($reverse,"GET");
}
break;
case 'HEAD':
//rest_head($request);
break;
case 'DELETE':
//rest_delete($request);
break;
case 'OPTIONS':
//rest_options($request);
break;
default:
//rest_error($request);
break;
}
include_once APP_ROOT.'controller/'.$c.'.php';
$controller = new $c();
$controller->$a($request);
}
The Router class:
<?php
include 'config/app.php';
/*
* Copyright (C) 2015 yuri.blanc
*/
require 'Class/http/Request.php';
class Router {
protected static $routes;
private function __construct() {
Router::$routes = json_decode(file_get_contents(APP_ROOT.'config/routes.json'));
}
public static function getInstance(){
if (Router::$routes==null) {
new Router();
}
return Router::$routes;
}
public static function go($action,$params=null) {
$actions = explode("#", $action);
$c = strtolower($actions[0]);
$a = strtolower($actions[1]);
// set query sting to null
$queryString = null;
if(isset($params)) {
foreach ($params as $name => $value) {
$queryString .= '/'.$name.'//'.$value;
}
return APP_URL."$c/$a$queryString";
}
return APP_URL."$c/$a";
}
public static function checkRoutes($action,$method){
foreach (Router::getInstance()->routes as $valid) {
/* echo $valid->action . ' == ' . $action . '|||';
echo $valid->method . ' == ' . $method . '|||';*/
if ($valid->method == $method && $valid->action == $action) {
return true;
}
}
}
public static function inverseRoute($controller,$action) {
return ucfirst($controller)."#".$action;
}
public static function notFound($action,$method) {
die("Route not found:: $action with method $method");
}
}
I use the json_decode function to parse the json object in stdClass().
The json file looks like this:
{"routes":[
{"action":"Frontend#index", "method":"GET"},
{"action":"Frontend#register", "method":"GET"},
{"action":"Frontend#blog", "method":"GET"}
]}
This way i can whitelist routes with their methods and return 404 errors while not found.
System is still quite basic but gives and idea and works, hope someone will find useful.
So, I am newbie in php so I feel litle confused right now.
I have a joomla! site with K2 extension. I have $this->item->imageXLarge; inside K2 item.php. I need to get $this->item->imageXLarge; outside of my item.php, but exactly in same page (in a module that is rendering in current image).
What I really tried out:
$k2itemimage = $this->item->imageXLarge; - at the top of my item.php
echo $k2itemimage - inside my module, outside my item.php
This gets Fatal error: Using $this when not in object context
Any idea of how could I get $this variable of current imageXLarge?
EDIT -> setDefaultImage class
public static function setDefaultImage(&$item, $view, $params = NULL)
{
if ($view == 'item')
{
$image = 'image'.$item->params->get('itemImgSize');
$item->image = $item->$image;
switch ($item->params->get('itemImgSize'))
{
case 'XSmall' :
$item->imageWidth = $item->params->get('itemImageXS');
break;
case 'Small' :
$item->imageWidth = $item->params->get('itemImageS');
break;
case 'Medium' :
$item->imageWidth = $item->params->get('itemImageM');
break;
case 'Large' :
$item->imageWidth = $item->params->get('itemImageL');
break;
case 'XLarge' :
$item->imageWidth = $item->params->get('itemImageXL');
return $k2itemimage = $item->params->get('itemImageXL');
break;
}
}
At the top of your page write: $k2itemimage = null;
then when you want to set this variable inside your object simply write:
global $k2itemimage;
$k2itemimage = $this->item->imageXLarge;
Here is a function to check whether a variable has anything inside of it:
function die_var($data, $informativeButNotPretty=False) {
if($informativeButNotPretty) {
echo '<pre>';
var_dump($data);
die('</pre>');
}
else {
die('<pre>'.print_r($data,true).'</pre>');
}
}
Put that right at the top of your script, and where ever you are trying to assign the global var simply put on the line above:
die_var($this->item->imageXLarge,true);
I'm working on codeigniter and I wonder whats the best way to change title dynamically. Eg. title will change depending if you are on home page, single post page, category pages, etc.
The only solution i can think of is to make separate function and compare current URL ( from address bar ) with structure of the single post page, category page, home page
Something like this:
public function current_title() {
if($this->uri->segment(2) == 'post') {
// will return post title
}
if($this->uri->segment(2) == 'category') {
// will return archive title
}
if(current_url() == base_url()) {
// this is home page
}
If anyone worked with this before, any advice highly appreciated
I would not use the uri for this, but instead the controller and action name and the language class :
public function current_title()
{
$this->lang->load('titles.php', 'en');
return $this->lang->line(
$this->router->fetch_class().'.'.$this->router->fetch_method()
);
}
You will have a key like MyClass.myMethod for your translation. Just add your titles in your titles.php file :
$lang['MyClass.myMethod'] = "The title";
$lang['MyOtherClass.myOtherMethod'] = "The other title";
Read more about translation :
http://ellislab.com/codeigniter/user-guide/libraries/language.html
http://ellislab.com/codeigniter/user-guide/helpers/language_helper.html
//in the controller you should do like this:
class Home extends your_Controller {
public function __construct() {
parent:: __construct();
}
function index()
{
$this->data['pageTitle'] = 'Your page title';
$data['main_content'] = 'home';
$this->load->view('includefolder/viewname', $data);
}
}
This is how I do it:
$PHPFile = basename($_SERVER['PHP_SELF'],'.php');
switch ($PHPFile) {
case 'index': $PageTitle = 'Home'; break;
case 'products': $PageTitle = 'Products'; break;
case 'services': $PageTitle = 'Services'; break;
}
You can use string searches or whatever is needed. I use this method since I have the header of the page as a function in library.
As we have a controller function for each view so you can easily get function name from url
$this -> router -> fetch_module();
so you can work with it.
I'm currently organising my site by forcing everything through index.php using .htaccess. Then I'm including some logic that uses the $REQUEST_URI to display the relevant template (see the code). My problem is that I find it annoying to have to check the the request array value is set every single time, eg if (isset($request[1])&&$request[1]=="projects"){...
Is there a way that I can just write if ($request[1]=="projects"){...
The above line, throws an error if $request[1] is not set, ie on the home page.
Here's my complete code. It shows an individual project on mydomain.com/projects/house,
and it shows a list of projects on mydomain.com/projects
$request = explode("/", $_SERVER['REQUEST_URI']);
if (isset($request[1])&&$request[1]=="projects"){
require("./controller/projects.php");
if (isset($request[2])){
include("./view/project.html");
} else {
include("./view/project-list.html");
}
Make a function!
function current_controller() {
if(isset($request[1])
return $request[1];
}
if(current_controller() === "projects") { ... }
Or you can do
function is_current_controller($controller) {
if(isset($request[1])&&$request[1] === $controller)
return true;
return false;
}
if(is_current_controller("projects") { ... }
Why not ditch the else/elseif method as use swtich()?
Like this
$request = explode("/", $_SERVER['REQUEST_URI']);
$check = $request[];
switch($check) {
case "1";
include (folder/file1.php);
break;
case "2";
include (folder/file2.php);
break;
case "3";
include (folder/file3.php);
break;
}
The following code is a Drupal block made in php.
1) How can I implement more then one item? now i have test1 but i want test1, test2, test3 and test5.
2) how can i link a title for example test1 to my admin/settings/ menu? I want to link an item to node_import in Drupal.
function planning_block($op='list', $delta=0, $edit=array()) {
switch ($op) {
case 'list':
$blocks[0]['info'] = t('Stage administration block');
return $blocks;
case 'view':
$blocks['subject'] = t('Stage administratie');
$blocks['content'] = 'test';
return $blocks;
}
}
If you refer to the documentation of hook_block, you can declare several block inside one hook.
The $delta argument is here to help you differenciate which block your are rendering.
About your links in the title, just use the l() function when you are setting the $block['subject'] value.
Example:
function planning_block($op='list', $delta=0, $edit=array()) {
switch ($op) {
case 'list':
$blocks[0]['info'] = t('Stage administration block 1');
$blocks[1]['info'] = t('Stage administration block 2');
return $blocks;
case 'view':
switch ($delta) {
case 0:
$blocks['subject'] = t('Stage administratie');
$items = array(
l('Item 1', 'admin/settings/1'),
l('Item 2', 'admin/settings/2'),
);
$blocks['content'] = theme_item_list($items);
return $blocks;
case 1:
$blocks['subject'] = l('admin/settings/2', t('Stage administratie 2'));
$blocks['content'] = 'test 2';
return $blocks;
}
}
}
You can either create multiple blocks as shown in Artusamak's answer, or you can simply add more content to $blocks['content'] if you want it in a single block.
$blocks['content'] = l('admin/settings/1', 'test 1') . ' ' . l('admin/settings/2', 'test 2');
Note, if you just want a list of fixed links, you can do that by creating a menu and adding links to it. Every menu is automatically exposed as a block. No custom code required.