Zend Framework create user-friendly url - php

I want create user-friendly URL like this:
mysite.com/flat-sale/london/1-room/1
When parts of URL is parameters:
flat-sale is post/list/type/1
london is city/123
1-room is rooms/1
and 1 id page/1
For doing this I created table in database - url_alias. This table has three column:
aid,url,alis
I inserted in this table next rows:
1 post/list/type/1 flat-sale
2 city/123 lonodon
3 1-room rooms/1
I am using Controller_Plugin for parsing URL:
class My_Controller_Plugin_UrlAlias extends Zend_Controller_Plugin_Abstract {
public function routeStartup(Zend_Controller_Request_Abstract $request) {
$alias = substr($request->getRequestUri(), 1);
$pattern = "([^/]+)";
//this model for CRUD from tables url_alias
$resources = new Admin_Resource_Materialalias();
$match = array();
if (preg_match_all($pattern, $alias, $match)) {
$url = array();
foreach($match['0'] as $m) {
//this is page
if (preg_match("#^[\d]+$#", $m)) {
$url[] = "page/$m";
} else {
$url[] = $resources->getUrl($m);
}
}
$url = implode("/", $url);
//echo $url;
}
if (isset($url) && strlen($url)) {
$request->setRequestUri($url);
}
}
}
This plugin is work perfectly.
But I am else needing create url like this: mysite.com/flat-sale/london/1-room/1.
For this purpose, I created new View_Helper:
class My_View_Helper_Alias extends Zend_View_Helper_Url {
public function alias(array $urlOptions = array(), $name = null, $reset = false, $encode = true) {
$url = $this->url($urlOptions, $name, $reset, $encode);
$pattern = "#([^/]+)\/([^/]+)\/([-a-zA-Z0-9_/.]+)#";
$params_pattern = "#([^/]+\/[\d]+)#";
if (preg_match($pattern, $url, $match)) {
$resources = new Admin_Resource_Materialalias();
if (preg_match_all($params_pattern, $match[3],$params)) {
$p_alias = array();
foreach($params[0] as $p) {
//add controller, action and first params
if (empty($p_alias)) {
$p = "/".$match[1].'/'.$match[2]."/".$p;
}
//this is page
if (preg_match("#page\/([\d]+)#", $p, $page)) {
$p_alias[] = $page[1];
continue;
}
//this model for CRUD from tables url_alias
$part = $resources->getAlias($p);
$p_alias[] = strlen($part)?$part:$p;
}
$alias = implode("/",$p_alias);
}
}
$alias = strlen($alias)? $alias : $url;
return $alias;
}
}
This view helper also work, but i think it is not optimal. Can anybody comments this code or maybe has same task? Thank you.
The problem is solved so, thanks for KA_lin:
routes.flat_sale_city_rooms.route = /:type/:city/:rooms/:page
routes.flat_sale_city_rooms.defaults.module = main
routes.flat_sale_city_rooms.defaults.controller = post
routes.flat_sale_city_rooms.defaults.action = list
routes.flat_sale_city_rooms.reqs.type = [^/]+
routes.flat_sale_city_rooms.reqs.city = [^/]+
routes.flat_sale_city_rooms.reqs.rooms = [^/]+
routes.flat_sale_city_rooms.defaults.page = 1
routes.flat_sale_city_rooms.reqs.page = \d+
routes.flat_sale_city.route = /:type/:city/:page
routes.flat_sale_city.defaults.module = main
routes.flat_sale_city.defaults.controller = post
routes.flat_sale_city.defaults.action = list
routes.flat_sale_city.reqs.type = [^/]+
routes.flat_sale_city.reqs.city = [^/]+
routes.flat_sale_city.defaults.page = 1
routes.flat_sale_city.reqs.page = \d+

Try in Bootstrap.php to add something like:
$route = new Zend_Controller_Router_Route (
'user/summary/:id/connection',
array('controller' => 'user',
'action' => 'get-summary',
'id' => FALSE,
)
);
$router->addRoute('equity', $route);
And this creates custom url`s you can call:
user/my-personal-url,
user/my-personal-url/connection
Assuming Zend 1 is used

Why don't you use Zend Framework routing? Have a look to this example: http://framework.zend.com/manual/2.0/en/user-guide/routing-and-controllers.html

you can use routing for user friendly urls
have a look for example
http://framework.zend.com/manual/1.5/en/zend.controller.router.html

Related

PHP routing - How can I implement 404 page on wrong url routes?

everyone.
I have a basic router created in PHP.
I can redirect to any page I want, if there is a callback function the callback function gets executed and if there is a page (String instead of a function) the page loads the correct file. However I can't figure out how to implement 404 page on non-existing route.
I tried to reuse the preg_match() function, but that gave me no results and if I place the notFound() (404 page) in the else block, it always gets executed regardless of the correct url or not.
if(preg_match($pattern, $path, $matches) && $httpMethod === $route['method']) {
}else{
self::notFound(); //THIS GETS EXECUTED ON EVERY ROUTE
}
This is my Code.
<?php
class Router{
public static $routes = [];
public static function get($route, $callback){
self::$routes[] = [
'route' => $route,
'callback' => $callback,
'method' => 'GET'
];
}
public static function resolve(){
$path = $_SERVER['REQUEST_URI'];
$httpMethod = $_SERVER['REQUEST_METHOD'];
$methodMatch = false;
$routeMatch = false;
foreach(self::$routes as $route){
// convert urls like '/users/:uid/posts/:pid' to regular expression
$pattern = "#^" . preg_replace('/\\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($route['route'])) . "$#D";
$matches = Array();
// check if the current request matches the expression
if(preg_match($pattern, $path, $matches) && $httpMethod === $route['method']) {
// remove the first match
array_shift($matches);
// call the callback with the matched positions as params
if(is_callable($route['callback'])){
call_user_func_array($route['callback'], $matches);
}else{
self::render($route['callback']);
}
}
}
}
public static function render($file, $viewsFolder='./views/'){
include($viewsFolder . $file);
}
public static function notFound(){
http_response_code(400);
include('./views/404.php');
exit();
}
}
Router::get("/", "home.php");
Router::get("/user/:id", function($val1) {
$data = array(
"Nicole",
"Sarah",
"Jinx",
"Sarai"
);
echo $data[$val1] ?? "No data";
});
Router::get("/user/profile/:id", "admin.php");
Router::resolve();
?>
You can add notFound() at the very end of resolve() method, and a return when you hit a match:
public static function resolve(){
$path = $_SERVER['REQUEST_URI'];
$httpMethod = $_SERVER['REQUEST_METHOD'];
$methodMatch = false;
$routeMatch = false;
foreach(self::$routes as $route){
$pattern = "#^" . preg_replace('/\\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($route['route'])) . "$#D";
$matches = Array();
if(preg_match($pattern, $path, $matches) && $httpMethod === $route['method']) {
array_shift($matches);
if(is_callable($route['callback'])){
call_user_func_array($route['callback'], $matches);
}else{
self::render($route['callback']);
}
return;
}
}
notFound();
}

php regular expression concatenation unwanted backslashes

I'm trying to build a regular expression in php. I tested it here https://regex101.com/ and it works fine, but that was before I knew I'd have to implement it in php and it adds backslashes where not needed.
Here's my code:
$datePattern = "\[((19|20)\d\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\]";
$tag = "[a-z]+(?:-[a-z]+)*";
$regroupmentPattern = "\[($tag)?\]";
$taglistPattern = "\[((?:$tag)?(?:;(?:$tag))*)\]";
$countryPattern = "\[([a-z]{2})\]";
$freePattern = "\[([^\[\]]*)\]";
$extensionPattern = "\.(jpg|png)";
$repetitionPattern = "(?:\(\d+\))?";
$fullPattern = "/^$datePattern$regroupmentPattern$taglistPattern$countryPattern$freePattern$freePattern$extensionPattern$repetitionPattern$/";
Here is what I want :
^\[((19|20)\d\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\]\[([a-z]+(?:-[a-z]+)*)?\]\[((?:[a-z]+(?:-[a-z]+)*)?(?:;(?:[a-z]+(?:-[a-z]+)*))*)\]\[([a-z]{2})\]\[([^\[\]]*)\]\[([^\[\]]*)\](?:\(\d+\))?\.(jpg|png)$
And here's what I get :
"\"\\/^\\\\[((19|20)\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\\\\]\\\\[([a-z]+(?:-[a-z]+)*)?\\\\]\\\\[((?:[a-z]+(?:-[a-z]+)*)?(?:;(?:[a-z]+(?:-[a-z]+)*))*)\\\\]\\\\[([a-z]{2})\\\\]\\\\[([^\\\\[\\\\]]*)\\\\]\\\\[([^\\\\[\\\\]]*)\\\\]\\\\.(jpg|png)(?:\\\\(\\\\d+\\\\))?$\\/\""
I assume there must be some sort of escape function, I tried preg_quote but it added yet even more backslashes.
Btw here's my full code:
<?php
class Gallery {
// Name of the gallery, used to build folder path
private $name;
function __construct($name) {
$this->name = $name;
}
/*
* Returns the list of file names in a gallery folder,
* or false if the folder doesn't exist
*/
public function getFileNames() {
$path = "../../gallery/$this->name";
if (is_dir($path)) {
$allFileNamesArray = scandir($path, SCANDIR_SORT_ASCENDING);
$filteredFileNamesArray = array();
// Building regular expression
$datePattern = "\[((19|20)\d\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\]";
$tag = "[a-z]+(?:-[a-z]+)*";
$regroupmentPattern = "\[($tag)?\]";
$taglistPattern = "\[((?:$tag)?(?:;(?:$tag))*)\]";
$countryPattern = "\[([a-z]{2})\]";
$freePattern = "\[([^\[\]]*)\]";
$extensionPattern = "\.(jpg|png)";
$repetitionPattern = "(?:\(\d+\))?";
$fullPattern = "/^$datePattern$regroupmentPattern$taglistPattern$countryPattern$freePattern$freePattern$extensionPattern$repetitionPattern$/";
foreach ($allFileNamesArray as $fileName) {
$matches = array();
if (preg_match($fullPattern, $fileName, $matches, PREG_UNMATCHED_AS_NULL)) {
$filteredFileNamesArray[] = $fileName;
}
var_dump($matches);
}
return json_encode($fullPattern);
}
else {
return false;
}
}
}
?>
(Here I returned fullPattern istead of filteredFileNamesArray for debugging purpose)
You swapped $repetitionPattern and $extensionPattern.
Use
$fullPattern = "/^$datePattern$regroupmentPattern$taglistPattern$countryPattern$freePattern$freePattern$repetitionPattern$extensionPattern$/";
It will result in
^\[((19|20)\d\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\]\[([a-z]+(?:-[a-z]+)*)?\]\[((?:[a-z]+(?:-[a-z]+)*)?(?:;(?:[a-z]+(?:-[a-z]+)*))*)\]\[([a-z]{2})\]\[([^\[\]]*)\]\[([^\[\]]*)\](?:\(\d+\))?\.(jpg|png)$ pattern.
See the regex demo online.

Find Word Which comes first in php

I have 2 words like %sku% and %any% that will be used in the sites url structure.
This data will be saved in a database and I need to find out which comes first.
E.g.
In the below url %sku% comes first
http://example.com/%sku%/product/%any%
While in the below url %any% comes first
http://example.com/%any%/product/%sku%
Furthermore I cant be sure that the structure will be consistent it could be like any of the below:
http://example.com/%sku%/product/%any%
http://example.com/%any%/product/%sku%
http://example.com/%any%/%sku%
http://example.com/product/%sku%
http://example.com/product/%any%
I want to check which comes first and which comes last.. but %sku% and%any%` are defined by me.. so i can be 100% sure that those tags are going to be used.
The following code will return the first and last occurring items from a designated $attributes array.
$string = 'http://example.com/%sku%/product/%any%';
// values to check for
$attributes = ['%sku%', '%any%'];
$results = array();
foreach($attributes as $attribute)
{
// Get position of attribute in uri string
$pos = strpos($string, $attribute);
// if it exists we add it to the array with the position
if($pos)
{
$results[$attribute] = $pos;
}
}
// Get the first occuring attribute
$firstOccuringAttribute = array_search( min($results), $results);
// Get the last occuring attribute
$lastOccuringAttribute = array_search( max($results), $results);
This could be refactored into something a bit more readable:
$uri = 'http://example.com/%sku%/product/%any%';
$attributes = ['%sku%', '%any%'];
$lastAttribute = getLastAttribute($uri, $attributes);
$firstAttribute = getFirstAttribtue($uri, $attributes);
function getAttributeWeighting($uri, $attributes)
{
$results = array();
foreach($attributes as $attribute)
{
$pos = strpos($uri, $attribute);
if($pos)
{
$results[$attribute] = $pos;
}
}
return $results;
}
function getFirstAttribute($uri, $attributes)
{
$attributeWeighting = getAttributeWeighting($uri, $attributes);
return array_search( min($attributeWeighting), $attributeWeighting);
}
function getLastAttribute($uri, $attributes)
{
$attributeWeighting = getAttributeWeighting($uri, $attributes);
return array_search( max($attributeWeighting), $attributeWeighting);
}
Just use strpos
something like:
$URL = "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$posOfSku=strlen($URL);
$posOfAny=strlen($URL);
if(strpos($URL ,'%sku%') !== false) {
$posOfSku = strpos($URL ,'%sku%');
}
if(strpos($URL ,'%any%') !== false) {
$posOfAny= strpos($URL ,'%any%');
}
$result = ($posOfAny < $posOfSku) ? 'any came 1st' : 'sku came 1st';
echo $result;

How to detect if a class or method name have multiple words?

Similar questions have been asked before but this one is different.
I am using Zend Framework 1.12 and I need to check how many words a class or method name contains.
I.e. "helloWorld" contains 2 words and "hello" contains 1 word.
Right now this is what I am doing.
$inflector = new Zend_Filter_Inflector(':class');
$inflector->setRules(array(
':class' => array('Word_CamelCaseToUnderscore')
));
//This will return Hello_World
$className = ucfirst($inflector->filter(array('class' => $className)));
$names = explode("_", $className);
if(count($names) > 1){
echo 'multiple words';
} else {
echo 'single word';
}
Is there a better way of doing this?
Thanks in advance for your help.
I like your solution.
For fun I can propose to create your own filter.
In my library Doydoytools I create this filter Doydoytools/Filter/Word/CounterWords.php:
class Doydoytools_Filter_Word_CounterWords extends Zend_Filter_Word_CamelCaseToUnderscore
{
public function filter($value)
{
return count(explode($this->_separator, parent::filter($value)));
}
}
So for the class Application_Plugin_PSession
$inflector = new Zend_Filter_Inflector(':class');
$inflector->setRules(array(':class' => array(new Doydoytools_Filter_Word_CounterWords())));
$count = $inflector->filter(array('class' => __CLASS__));
The result is 4 With your methode the name is Application_Plugin_P_Session
I writed code like this:
function getMethodByField( $field )
{
$result = '';
if( !empty($field) ) {
$method = '';
$strMethodArr = explode('_', $field);
foreach( $strMethodArr as $str ) {
$method .= ucfirst( $str );
}
$result = $method;
}
return $result;
}

CakePhp: Url based internationalization

I've a small problem with my internationalization:
I want to have some url looking like this: http://mywebsite/eng/controller/action/params...
I found this http://nuts-and-bolts-of-cakephp.com/2008/11/28/cakephp-url-based-language-switching-for-i18n-and-l10n-internationalization-and-localization/
This is working nice most of time. But I've one case where this hasn't the expected result.
When I'm using $this->Html->link with named parameters, I don't get my nice structure, but something like http://mywebsite/controller/action/paramX:aaa/paramxY:bbb/language:eng
I think this is a routing problem, but I can't figure what is going wrong?
Thank you very much
This is because cakephp doens't find a route in routes.php that corresponds to this link. In other words, you'll have to define this route in the routes.php file
Router::connect('/:language/:controller/:action/:paramX/:paramY');
Once this set, $this->Html->link will output a nice url
I finally did this:
I created a custom CakeRoute, in this cakeRoute, I override the "match" url and the _writeUrl method.
Now every thing is working like a charm :)
For those which are interessted by the route class:
<?php
class I18nRoute extends CakeRoute {
/**
* Constructor for a Route
* Add a regex condition on the lang param to be sure it matches the available langs
*
* #param string $template Template string with parameter placeholders
* #param array $defaults Array of defaults for the route.
* #param string $params Array of parameters and additional options for the Route
* #return void
* #access public
*/
public function __construct($template, $defaults = array(), $options = array()) {
//$defaults['language'] = Configure::read('Config.language');
$options = array_merge((array)$options, array(
'language' => join('|', Configure::read('Config.languages'))
));
parent::__construct($template, $defaults, $options);
}
/**
* Attempt to match a url array. If the url matches the route parameters + settings, then
* return a generated string url. If the url doesn't match the route parameters false will be returned.
* This method handles the reverse routing or conversion of url arrays into string urls.
*
* #param array $url An array of parameters to check matching with.
* #return mixed Either a string url for the parameters if they match or false.
* #access public
*/
public function match($url) {
if (empty($url['language'])) {
$url['language'] = Configure::read('Config.language');
}
if (!$this->compiled()) {
$this->compile();
}
$defaults = $this->defaults;
if (isset($defaults['prefix'])) {
$url['prefix'] = $defaults['prefix'];
}
//check that all the key names are in the url
$keyNames = array_flip($this->keys);
if (array_intersect_key($keyNames, $url) != $keyNames) {
return false;
}
$diffUnfiltered = Set::diff($url, $defaults);
$diff = array();
foreach ($diffUnfiltered as $key => $var) {
if ($var === 0 || $var === '0' || !empty($var)) {
$diff[$key] = $var;
}
}
//if a not a greedy route, no extra params are allowed.
if (!$this->_greedy && array_diff_key($diff, $keyNames) != array()) {
return false;
}
//remove defaults that are also keys. They can cause match failures
foreach ($this->keys as $key) {
unset($defaults[$key]);
}
$filteredDefaults = array_filter($defaults);
//if the difference between the url diff and defaults contains keys from defaults its not a match
if (array_intersect_key($filteredDefaults, $diffUnfiltered) !== array()) {
return false;
}
$passedArgsAndParams = array_diff_key($diff, $filteredDefaults, $keyNames);
list($named, $params) = Router::getNamedElements($passedArgsAndParams, $url['controller'], $url['action']);
//remove any pass params, they have numeric indexes, skip any params that are in the defaults
$pass = array();
$i = 0;
while (isset($url[$i])) {
if (!isset($diff[$i])) {
$i++;
continue;
}
$pass[] = $url[$i];
unset($url[$i], $params[$i]);
$i++;
}
/*
//still some left over parameters that weren't named or passed args, bail.
//We don't want this behavior, we use most of args for the matching, and if we have more, we just allow them as parameters
if (!empty($params)) {
return false;
}*/
//check patterns for routed params
if (!empty($this->options)) {
foreach ($this->options as $key => $pattern) {
if (array_key_exists($key, $url) && !preg_match('#^' . $pattern . '$#', $url[$key])) {
return false;
}
}
}
return $this->_writeUrl(array_merge($url, compact('pass', 'named')));
}
function _writeUrl($params) {
if (isset($params['prefix'], $params['action'])) {
$params['action'] = str_replace($params['prefix'] . '_', '', $params['action']);
unset($params['prefix']);
}
if (is_array($params['pass'])) {
$params['pass'] = implode('/', $params['pass']);
}
$instance =& Router::getInstance();
$separator = $instance->named['separator'];
if (!empty($params['named']) && is_array($params['named'])) {
$named = array();
foreach ($params['named'] as $key => $value) {
$named[] = $key . $separator . $value;
}
$params['pass'] = $params['pass'] . '/' . implode('/', $named);
}
$out = $this->template;
$search = $replace = array();
foreach ($this->keys as $key) {
$string = null;
if (isset($params[$key])) {
$string = $params[$key];
} elseif (strpos($out, $key) != strlen($out) - strlen($key)) {
$key .= '/';
}
$search[] = ':' . $key;
$replace[] = $string;
}
$out = str_replace($search, $replace, $out);
if (strpos($this->template, '*')) {
$out = str_replace('*', $params['pass'], $out);
}
$out = str_replace('//', '/', $out);
//Modified part: allows us to print unused parameters
foreach($params as $key => $value){
$found = false;
foreach($replace as $repValue){
if($value==$repValue){
$found=true;
break;
}
}
if(!$found && !empty($value)){
$out.="/$key:$value";
}
}
return $out;
}
}
And you can set the route like this:
Router::connect('/:language/:controller/*', array(), array('routeClass' => 'I18nRoute'));

Categories