CSS problems with custom Router - PHP - php

I am creating a custom Router for my web app.
I use MVC.
When I, for example, type fab.app/portfolio all is good.
But when I type fab.app/portfolio/ the css, images, and js are not displayed.
This is because the paths change. In the first case the path that it is looking for the images is fab.app/images/(the_image) but in the second case it is fab.app/portfolio/images/(the_image).
Also, in the index.php I have to have an entry for both the url with and without the slash in the end. Which I don't like. I would prefer to write it without the slash and it should work with the slash as well.
Below you can find the router and index.php
index.php
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../app/setup.php';
use Fab\Controllers;
use Fab\Router;
$router = new Router\Router();
$router->get('/', 'MainController', 'index');
$router->get('/portfolio', 'ItemsController', 'showAllItems');
$router->get('/portfolio/', 'ItemsController', 'showAllItems');
$router->get('/portfolio/[\w\d]+', 'ItemsController', 'single_item');
$router->get('/about', 'MainController', 'about');
$router->get('/contact', 'MainController', 'contact');
$router->get('/admin/dashboard', 'AdminController', 'index');
$router->get('/admin/dashboard/addItem', 'AdminController', 'addItem');
$router->get('/admin/dashboard/deleteItem', 'AdminController', 'deleteItem');
$router->get('/admin/dashboard/editItem', 'AdminController', 'editItem');
$router->get('/admin/dashboard/contactSupport', 'AdminController', 'contactSupport');
$router->post('/admin/addItem', 'AdminController', 'postAddItem');
$router->post('/admin/deleteItem', 'AdminController', 'postDeleteItem');
$router->post('/admin/editItem', 'AdminController', 'postEditItem');
//$router->get('/test', 'ItemsController', 'showAllItems');
////See inside $router
//echo "<pre>";
//print_r($router);
$router->submit();
Router.php
<?php
/**
* Created by PhpStorm.
* User: antony
* Date: 5/30/16
* Time: 3:31 PM
*/
namespace Fab\Router;
class Router
{
private $_getUri = array();
private $_getController = array();
private $_getMethod = array();
private $_postUri = array();
private $_postController = array();
private $_postMethod = array();
public function __construct()
{
}
/**
* Build a collection of internal GET URLs to look for
* #param $uri - The url that the user types in the browser
* #param $controller - The controller that will handle the url
* #param $method - The method of the controller that will run
*/
public function get($uri, $controller, $method)
{
$this->_getUri[] = $uri;
$this->_getController[] = $controller;
$this->_getMethod[] = $method;
}
/**
* Build a collection of internal POST URLs to look for
* #param $uri - The url that the user types in the browser
* #param $controller - The controller that will handle the url
* #param $method - The method of the controller that will run
*/
public function post($uri, $controller, $method)
{
$this->_postUri[] = $uri;
$this->_postController[] = $controller;
$this->_postMethod[] = $method;
}
public function submit()
{
$found = 0;
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); //get the url
//Map URL to page
foreach ($this->_getUri as $key => $value)
{
if ( $found = preg_match("#^$value$#", $path) )
{
// echo $key . ' => ' . $value; //See what the $path returns
//Find parameter if passed
$parts = explode('/', $path);
count($parts) > 2 ? $parameter=$parts[2] : $parameter=null;
//Instantiate Controller
$controller = 'Fab\Controllers\\' . $this->_getController[$key];
$controller = new $controller($parameter);
//Call the appropriate method
$method = $this->_getMethod[$key];
$controller->$method();
break;
}
}
//Show 404 page
if ( $found == 0 )
{
//Instantiate Controller
$controller = 'Fab\Controllers\MainController';
$controller = new $controller();
//Call the appropriate method
$method = 'error404';
$controller->$method();
die();
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); //get the url
foreach ($this->_postUri as $key => $value)
{
if ( $found = preg_match("#^$value$#", $path))
{
// echo $key . ' => ' . $value; //See what the $path returns
//Find parameter if passed
$parts = explode('/', $path);
count($parts) > 2 ? $parameter=$parts[2] : $parameter=null;
//Instantiate Controller
$controller = 'Fab\Controllers\\' . $this->_postController[$key];
$controller = new $controller($parameter);
//Call the appropriate method
$method = $this->_postMethod[$key];
$controller->$method();
break;
}
}
//Show 404 page
if ( $found == 0 )
{
//Instantiate Controller
$controller = 'Fab\Controllers\MainController';
$controller = new $controller();
//Call the appropriate method
$method = 'error404';
$controller->$method();
die();
}
}
}
}
.htaccess
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
RewriteEngine On
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R=301]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
AddType 'text/css; charset=UTF-8' css
</IfModule>
Example of calling css (but also images and js) in html
<link href="/css/my-admin-custom.css" rel="stylesheet">
-------------------------------------------------------------------
For sake of clarity, here is how I resolved the issue (per #Max13's answer).
This goes into Router.php:
/**
* If last char in URL is '/' redirect without it
* and also check if url is root '/' because this would result
* in infinite loop
*/
if ( ($path[strlen($path)-1] === '/') && !($path === '/') ) { //
$newPath = substr($path, 0, -1);
header("Location: $newPath", true, 302);
exit;
}

With and without trailing slash doesn't mean the same directory level, so I don't think it would be a good idea to "accept" both, in your router.
So, a good solution IMHO would be: During your checks, if the path ends with "/", then send a 302 Found header without the trailing slash.
-- Edit --
See (For header redirects and meta tag redirection, both are often used at the same time): https://www.arclab.com/en/websitelinkanalyzer/http-and-meta-redirects.html

Related

Using php GET after mod rewrite [duplicate]

I have a index.php which handle all the routing index.php?page=controller (simplified) just to split up the logic with the view.
Options +FollowSymlinks
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([\w\d~%.:_\-]+)$ index.php?page=$1 [NC]
Which basically:
http://localhost/index.php?page=controller
To
http://localhost/controller/
Can anyone help me add the Rewrite for
http://localhost/controller/param/value/param/value (And soforth)
That would be:
http://localhost/controller/?param=value&param=value
I can't get it to work with the Rewriterule.
A controller could look like this:
<?php
if (isset($_GET['action'])) {
if ($_GET['action'] == 'delete') {
do_Delete_stuff_here();
}
}
?>
And also:
<?php
if (isset($_GET['action']) && isset($_GET['x'])) {
if ($_GET['action'] == 'delete') {
do_Delete_stuff_here();
}
}
?>
Basically what people try to say is, you can make a rewrite rule like so:
RewriteRule ^(.*)$ index.php?params=$1 [NC, QSA]
This will make your actual php file like so:
index.php?params=param/value/param/value
And your actual URL would be like so:
http://url.com/params/param/value/param/value
And in your PHP file you could access your params by exploding this like so:
<?php
$params = explode( "/", $_GET['params'] );
for($i = 0; $i < count($params); $i+=2) {
echo $params[$i] ." has value: ". $params[$i+1] ."<br />";
}
?>
I think it's better if you redirect all requests to the index.php file and then extract the controller name and any other parameters using php. Same as any other frameworks such as Zend Framework.
Here is simple class that can do what you are after.
class HttpRequest
{
/**
* default controller class
*/
const CONTROLLER_CLASSNAME = 'Index';
/**
* position of controller
*/
protected $controllerkey = 0;
/**
* site base url
*/
protected $baseUrl;
/**
* current controller class name
*/
protected $controllerClassName;
/**
* list of all parameters $_GET and $_POST
*/
protected $parameters;
public function __construct()
{
// set defaults
$this->controllerClassName = self::CONTROLLER_CLASSNAME;
}
public function setBaseUrl($url)
{
$this->baseUrl = $url;
return $this;
}
public function setParameters($params)
{
$this->parameters = $params;
return $this;
}
public function getParameters()
{
if ($this->parameters == null) {
$this->parameters = array();
}
return $this->parameters;
}
public function getControllerClassName()
{
return $this->controllerClassName;
}
/**
* get value of $_GET or $_POST. $_POST override the same parameter in $_GET
*
* #param type $name
* #param type $default
* #param type $filter
* #return type
*/
public function getParam($name, $default = null)
{
if (isset($this->parameters[$name])) {
return $this->parameters[$name];
}
return $default;
}
public function getRequestUri()
{
if (!isset($_SERVER['REQUEST_URI'])) {
return '';
}
$uri = $_SERVER['REQUEST_URI'];
$uri = trim(str_replace($this->baseUrl, '', $uri), '/');
return $uri;
}
public function createRequest()
{
$uri = $this->getRequestUri();
// Uri parts
$uriParts = explode('/', $uri);
// if we are in index page
if (!isset($uriParts[$this->controllerkey])) {
return $this;
}
// format the controller class name
$this->controllerClassName = $this->formatControllerName($uriParts[$this->controllerkey]);
// remove controller name from uri
unset($uriParts[$this->controllerkey]);
// if there are no parameters left
if (empty($uriParts)) {
return $this;
}
// find and setup parameters starting from $_GET to $_POST
$i = 0;
$keyName = '';
foreach ($uriParts as $key => $value) {
if ($i == 0) {
$this->parameters[$value] = '';
$keyName = $value;
$i = 1;
} else {
$this->parameters[$keyName] = $value;
$i = 0;
}
}
// now add $_POST data
if ($_POST) {
foreach ($_POST as $postKey => $postData) {
$this->parameters[$postKey] = $postData;
}
}
return $this;
}
/**
* word seperator is '-'
* convert the string from dash seperator to camel case
*
* #param type $unformatted
* #return type
*/
protected function formatControllerName($unformatted)
{
if (strpos($unformatted, '-') !== false) {
$formattedName = array_map('ucwords', explode('-', $unformatted));
$formattedName = join('', $formattedName);
} else {
// string is one word
$formattedName = ucwords($unformatted);
}
// if the string starts with number
if (is_numeric(substr($formattedName, 0, 1))) {
$part = $part == $this->controllerkey ? 'controller' : 'action';
throw new Exception('Incorrect ' . $part . ' name "' . $formattedName . '".');
}
return ltrim($formattedName, '_');
}
}
How to use it:
$request = new HttpRequest();
$request->setBaseUrl('/your/base/url/');
$request->createRequest();
echo $request->getControllerClassName(); // return controller name. Controller name separated by '-' is going to be converted to camel case.
var_dump ($request->getParameters()); // print all other parameters $_GET & $_POST
.htaccess file:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]
Your rewrite rule would pass the entire URL:
RewriteRule ^(.*)$ index.php?params=$1 [NC]
Your index.php would interpret that full path as controller/param/value/param/value for you (my PHP is a little rusty):
$params = explode("/", $_GET['params']);
if (count($params) % 2 != 1) die("Invalid path length!");
$controller = $params[0];
$my_params = array();
for ($i = 1; $i < count($params); $i += 2) {
$my_params[$params[$i]] = $params[$i + 1];
}
How about redirect to index.php?params=param/value/param/value, and let php split the whole $_GET['params']? I think this is the way wordpress handling it.
For some reason, the selected solution did not work for me. It would constantly only return "index.php" as value of params.
After some trial and error, I found the following rules to work well. Assuming you want yoursite.com/somewhere/var1/var2/var3 to point to yoursite.com/somewhere/index.php?params=var1/var2/var3, then place the following rule in a .htaccess file in the "somewhere" directory:
Options +FollowSymLinks
RewriteEngine On
# The first 2 conditions may or may not be relevant for your needs
# If the request is not for a valid file
RewriteCond %{REQUEST_FILENAME} !-d
# If the request is not for a valid directory
RewriteCond %{REQUEST_FILENAME} !-f
# This rule converts your flat link to a query
RewriteRule ^(.*)$ index.php?params=$1 [L,NC,NE]
Then, in PHP or whichever language of your choice, simply separate the values using the explode command as pointed out by #Wesso.
For testing purposes, this should suffice in your index.php file:
if (isset($_GET['params']))
{
$params = explode( "/", $_GET['params'] );
print_r($params);
exit("YUP!");
}
Is this what your looking for?
This example demonstrates how to easily hide query string parameters using loop flag. Suppose you have URL like http://www.mysite.com/foo.asp?a=A&b=B&c=C and you want to access it as http://www.myhost.com/foo.asp/a/A/b/B/c/C
Try the following rule to achieve desired result:
RewriteRule ^(.*?\.php)/([^/]*)/([^/]*)(/.+)? $1$4?$2=$3 [NC,N,QSA]
Are you sure you are using apache server,.htaccess works only on apache server. If you are using IIS then web.config is reqired. In that case:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Homepage">
<match url="Homepage"/>
<action type="Rewrite" url="index.php" appendQueryString="true"/>
</rule>
</rules>
</rewrite>
<httpErrors errorMode="Detailed"/>
<handlers>
<add name="php" path="*.php" verb="*" modules="IsapiModule" scriptProcessor="C:\Program Files\Parallels\Plesk\Additional\PleskPHP5\php5isapi.dll" resourceType="Unspecified"/>
</handlers>
</system.webServer>
</configuration>

MVC - Looking for a better way to get from URL to view

The below example shows how I am currently doing things.
index.php includes index_controller.php then index_template.php.
index_controller.php
$uri = explode('/', $_SERVER['REQUEST_URI']);
$action = $uri[1];
$call = $uri[2];
$tmp = explode('?', $call);
$call = $tmp[0];
$call = preg_replace('/-/', ' ', $call);
switch ($action) {
case "about":
$page = "about.inc.php";
$title = "About Us";
$description = "Description of page";
break;
case "category":
try {
//PDO query to make sure category ($call) exists
}
catch (PDOException $e) {
logError($e->getMessage());
}
if (query->rowCount() < 1) {
$page = "404.inc.php";
$title = "404 Error";
}
else {
//Meta information for selected category pulled from DB and put into variables.
$page = "category.inc.php";
break;
default:
$page = "404.inc.php";
$title = "404 Error";
}
The above example shows 2 of around 12 different page options in the switch statement. A simple request (about) and a more complex request (category).
index_template.php has all my head, body, and footer HTML. It sets the meta data for the page, sets up the sites structure, and includes whatever file the $page variable is set to in index_controller.php
Using the above example, if someone goes to mysite.com/category/books index_controller.php will see if the books category exists and if it does category.inc.php will be included.
category.inc.php does another PDO query to get all the items and information required to display a list of items for the selected category. It also includes a template file to structure the display of the returned items.
I am trying to achieve a MVC type structure (without using a framework like Codeigniter or CakePHP), but I don't really have the model end down.
How can I get the user from the URL to the view using classes and/or functions instead of all the includes I am currently using?
If you feel I didn't do a good job explaining the other files mentioned I can provide code examples from those files as well.
Any help, input, or suggestions will be greatly appreciated.
EDIT: Clarified question as per comment below.
With a little trick and with .htaccess you can make this much easier.
I use this method in my self made MVC based application. You can copy paste the whole code or just use a part of it. The main logic is in the Bootstrap class.
.htaccess
Options -Indexes
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]
RewriteRule ^$ /news [R]
index.php
require 'brInit.php';
$app = new \brInclude\brClasses\mvc\Bootstrap();
I use brInit to automatically include classes and to include my config file
Bootstrap.php
class Bootstrap {
private $url = array();
/** #var Controller $controller */
private $controller;
function __construct(){
try {
$this->_getUrl();
$this->_loginUser();
if(!$this->_setController($this->url[0]))
throw new Exception('404');
if(!$this->_executeControllersMethod())
throw new Exception('404');
} catch(Exception $e){
$this->_error($e->getMessage());
}
}
private function _error($msg){
$this->url = array('Error', 'error', $msg);
$this->_setController($this->url[0]);
$this->_loginUser();
$this->_executeControllersMethod();
}
private function _getUrl(){
if(isset($_GET['url']))
$this->url = explode('/', rtrim($_GET['url'], '/'));
else
$this->url = array('news');
unset($_GET['url']);
}
private function _setController($name){
$path = 'brInclude/brMVC/controller/';
if(!file_exists($path)) return false;
$url = ucfirst($name) . 'Controller';
$namespace = str_replace('/', '\\', $path);
$file = $path . $url . '.php';
if(!file_exists($file)) return false;
$classWithNamespace = $namespace . $url;
$this->controller = new $classWithNamespace;
$this->controller->view->name = $name;
return true;
}
private function _loginUser(){
$model = new UserModel();
$user = $model->login();
Controller::$user = $user;
}
private function _executeControllersMethod(){
if(isset($this->url[1])){
if(method_exists($this->controller, $this->url[1])){
$count = count($this->url);
if($count > 2)
call_user_func_array(
array($this->controller, $this->url[1]),
array_slice($this->url, 2)
);
else
$this->controller->{$this->url[1]}();
} else {
return false;
}
} else {
$this->controller->index();
}
return true;
}
public static function isLoginRequired(){
return self::$loginRequired;
}
}

how to make nice rewrited urls from a router

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.

PHP POST to RESTful API implemented with apache mod_rewrite

I created a PHP RESTful API following this example (code at the bottom). From what I understand, the API converts non-existing URI components to GET parameters in the address via the .htaccess file like this:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule api/v1/(.*)$ api/v1/api.php?request=$1 [QSA,NC,L]
</IfModule>
So if I do, e.g.:
http://mysite/api/v1/endpoint1/param1
endpoint1/param1 can be parsed in the API implementation to call a PHP function endpoint1(param1).
Now my issue is, param1 is really long, and I want use AJAX to POST param1, but the POST doesn't go through
For example, for the RESTful API:
http://mysite/api/v1/endpoint1
I POSTed data using AJAX as follows:
$.post('http://mysite/api/v1/endpoint1/',data,callback,'json');
In API.class.php, $_SERVER['REQEUEST_METHOD'] is 'POST', but $_POST is empty array.
My questions are:
What is actually happening when I POST to a virtual URL to be handled by mod_rewrite. Is the end result a GET request by mode_rewrite or a POST request by my AJAX call?
How can I modify the code to get the POSTed data through? (Is it possible to ask mod_rewrite to use the POST method instead?)
I am confused here, any pointers are appreciated.
The interface for the relevant REST api is (API.class.php, please see the example for complete code):
<?php
abstract class API
{
protected $method = '';
protected $endpoint = '';
protected $args = Array();
protected $file = Null;
public function __construct($request) {
header("Access-Control-Allow-Orgin: *");
header("Access-Control-Allow-Methods: *");
header("Content-Type: application/json");
$this->args = explode('/', rtrim($request, '/'));
$this->endpoint = array_shift($this->args);
$this->method = $_SERVER['REQUEST_METHOD'];
if ($this->method == 'POST' && array_key_exists('HTTP_X_HTTP_METHOD', $_SERVER)) {
if ($_SERVER['HTTP_X_HTTP_METHOD'] == 'DELETE') {
$this->method = 'DELETE';
} else if ($_SERVER['HTTP_X_HTTP_METHOD'] == 'PUT') {
$this->method = 'PUT';
} else {
throw new Exception("Unexpected Header");
}
}
switch($this->method) {
case 'DELETE':
case 'POST':
$this->request = $this->_cleanInputs($_POST);
$this->args[] = ???; //Problem line: how do I add post to the args
break;
case 'GET':
$this->request = $this->_cleanInputs($_GET);
break;
case 'PUT':
$this->request = $this->_cleanInputs($_GET);
$this->file = file_get_contents("php://input");
break;
default:
$this->_response('Invalid Method', 405);
break;
}
}
public function processAPI() {
if ((int)method_exists($this, $this->endpoint) > 0) {
return $this->_response($this->{$this->endpoint}($this->args));
}
return $this->_response("No Endpoint: $this->endpoint", 404);
}
private function _response($data, $status = 200) {
header("HTTP/1.1 " . $status . " " . $this->_requestStatus($status));
return json_encode($data);
}
private function _cleanInputs($data) {
$clean_input = Array();
if (is_array($data)) {
foreach ($data as $k => $v) {
$clean_input[$k] = $this->_cleanInputs($v);
}
} else {
$clean_input = trim(strip_tags($data));
}
return $clean_input;
}
private function _requestStatus($code) {
$status = array(
200 => 'OK',
404 => 'Not Found',
405 => 'Method Not Allowed',
500 => 'Internal Server Error',
);
return ($status[$code])?$status[$code]:$status[500];
}
}
?>
I believe this is the issue you are running into:
Is it possible to redirect post data?
You should change this line in your .htaccess file from:
RewriteRule api/v1/(.*)$ api/v1/api.php?request=$1 [QSA,NC,L]
to:
RewriteRule api/v1/(.*)$ api/v1/api.php?request=$1 [QSA,NC,P]

.htaccess rewrite GET variables

I have a index.php which handle all the routing index.php?page=controller (simplified) just to split up the logic with the view.
Options +FollowSymlinks
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([\w\d~%.:_\-]+)$ index.php?page=$1 [NC]
Which basically:
http://localhost/index.php?page=controller
To
http://localhost/controller/
Can anyone help me add the Rewrite for
http://localhost/controller/param/value/param/value (And soforth)
That would be:
http://localhost/controller/?param=value&param=value
I can't get it to work with the Rewriterule.
A controller could look like this:
<?php
if (isset($_GET['action'])) {
if ($_GET['action'] == 'delete') {
do_Delete_stuff_here();
}
}
?>
And also:
<?php
if (isset($_GET['action']) && isset($_GET['x'])) {
if ($_GET['action'] == 'delete') {
do_Delete_stuff_here();
}
}
?>
Basically what people try to say is, you can make a rewrite rule like so:
RewriteRule ^(.*)$ index.php?params=$1 [NC, QSA]
This will make your actual php file like so:
index.php?params=param/value/param/value
And your actual URL would be like so:
http://url.com/params/param/value/param/value
And in your PHP file you could access your params by exploding this like so:
<?php
$params = explode( "/", $_GET['params'] );
for($i = 0; $i < count($params); $i+=2) {
echo $params[$i] ." has value: ". $params[$i+1] ."<br />";
}
?>
I think it's better if you redirect all requests to the index.php file and then extract the controller name and any other parameters using php. Same as any other frameworks such as Zend Framework.
Here is simple class that can do what you are after.
class HttpRequest
{
/**
* default controller class
*/
const CONTROLLER_CLASSNAME = 'Index';
/**
* position of controller
*/
protected $controllerkey = 0;
/**
* site base url
*/
protected $baseUrl;
/**
* current controller class name
*/
protected $controllerClassName;
/**
* list of all parameters $_GET and $_POST
*/
protected $parameters;
public function __construct()
{
// set defaults
$this->controllerClassName = self::CONTROLLER_CLASSNAME;
}
public function setBaseUrl($url)
{
$this->baseUrl = $url;
return $this;
}
public function setParameters($params)
{
$this->parameters = $params;
return $this;
}
public function getParameters()
{
if ($this->parameters == null) {
$this->parameters = array();
}
return $this->parameters;
}
public function getControllerClassName()
{
return $this->controllerClassName;
}
/**
* get value of $_GET or $_POST. $_POST override the same parameter in $_GET
*
* #param type $name
* #param type $default
* #param type $filter
* #return type
*/
public function getParam($name, $default = null)
{
if (isset($this->parameters[$name])) {
return $this->parameters[$name];
}
return $default;
}
public function getRequestUri()
{
if (!isset($_SERVER['REQUEST_URI'])) {
return '';
}
$uri = $_SERVER['REQUEST_URI'];
$uri = trim(str_replace($this->baseUrl, '', $uri), '/');
return $uri;
}
public function createRequest()
{
$uri = $this->getRequestUri();
// Uri parts
$uriParts = explode('/', $uri);
// if we are in index page
if (!isset($uriParts[$this->controllerkey])) {
return $this;
}
// format the controller class name
$this->controllerClassName = $this->formatControllerName($uriParts[$this->controllerkey]);
// remove controller name from uri
unset($uriParts[$this->controllerkey]);
// if there are no parameters left
if (empty($uriParts)) {
return $this;
}
// find and setup parameters starting from $_GET to $_POST
$i = 0;
$keyName = '';
foreach ($uriParts as $key => $value) {
if ($i == 0) {
$this->parameters[$value] = '';
$keyName = $value;
$i = 1;
} else {
$this->parameters[$keyName] = $value;
$i = 0;
}
}
// now add $_POST data
if ($_POST) {
foreach ($_POST as $postKey => $postData) {
$this->parameters[$postKey] = $postData;
}
}
return $this;
}
/**
* word seperator is '-'
* convert the string from dash seperator to camel case
*
* #param type $unformatted
* #return type
*/
protected function formatControllerName($unformatted)
{
if (strpos($unformatted, '-') !== false) {
$formattedName = array_map('ucwords', explode('-', $unformatted));
$formattedName = join('', $formattedName);
} else {
// string is one word
$formattedName = ucwords($unformatted);
}
// if the string starts with number
if (is_numeric(substr($formattedName, 0, 1))) {
$part = $part == $this->controllerkey ? 'controller' : 'action';
throw new Exception('Incorrect ' . $part . ' name "' . $formattedName . '".');
}
return ltrim($formattedName, '_');
}
}
How to use it:
$request = new HttpRequest();
$request->setBaseUrl('/your/base/url/');
$request->createRequest();
echo $request->getControllerClassName(); // return controller name. Controller name separated by '-' is going to be converted to camel case.
var_dump ($request->getParameters()); // print all other parameters $_GET & $_POST
.htaccess file:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]
Your rewrite rule would pass the entire URL:
RewriteRule ^(.*)$ index.php?params=$1 [NC]
Your index.php would interpret that full path as controller/param/value/param/value for you (my PHP is a little rusty):
$params = explode("/", $_GET['params']);
if (count($params) % 2 != 1) die("Invalid path length!");
$controller = $params[0];
$my_params = array();
for ($i = 1; $i < count($params); $i += 2) {
$my_params[$params[$i]] = $params[$i + 1];
}
How about redirect to index.php?params=param/value/param/value, and let php split the whole $_GET['params']? I think this is the way wordpress handling it.
For some reason, the selected solution did not work for me. It would constantly only return "index.php" as value of params.
After some trial and error, I found the following rules to work well. Assuming you want yoursite.com/somewhere/var1/var2/var3 to point to yoursite.com/somewhere/index.php?params=var1/var2/var3, then place the following rule in a .htaccess file in the "somewhere" directory:
Options +FollowSymLinks
RewriteEngine On
# The first 2 conditions may or may not be relevant for your needs
# If the request is not for a valid file
RewriteCond %{REQUEST_FILENAME} !-d
# If the request is not for a valid directory
RewriteCond %{REQUEST_FILENAME} !-f
# This rule converts your flat link to a query
RewriteRule ^(.*)$ index.php?params=$1 [L,NC,NE]
Then, in PHP or whichever language of your choice, simply separate the values using the explode command as pointed out by #Wesso.
For testing purposes, this should suffice in your index.php file:
if (isset($_GET['params']))
{
$params = explode( "/", $_GET['params'] );
print_r($params);
exit("YUP!");
}
Is this what your looking for?
This example demonstrates how to easily hide query string parameters using loop flag. Suppose you have URL like http://www.mysite.com/foo.asp?a=A&b=B&c=C and you want to access it as http://www.myhost.com/foo.asp/a/A/b/B/c/C
Try the following rule to achieve desired result:
RewriteRule ^(.*?\.php)/([^/]*)/([^/]*)(/.+)? $1$4?$2=$3 [NC,N,QSA]
Are you sure you are using apache server,.htaccess works only on apache server. If you are using IIS then web.config is reqired. In that case:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Homepage">
<match url="Homepage"/>
<action type="Rewrite" url="index.php" appendQueryString="true"/>
</rule>
</rules>
</rewrite>
<httpErrors errorMode="Detailed"/>
<handlers>
<add name="php" path="*.php" verb="*" modules="IsapiModule" scriptProcessor="C:\Program Files\Parallels\Plesk\Additional\PleskPHP5\php5isapi.dll" resourceType="Unspecified"/>
</handlers>
</system.webServer>
</configuration>

Categories