an elegant way to matching a route - php

im stuck on a piece of code which is looking terrible.
i got an route as string like /people/12/edit and i want to compare it with routes in my dataset.
in the dataset there are routes like:
/people
/people/:id
/people/new
/people/:id/edit
i need to know, that my route /people/12/edit goes to the internal action for /people/:id/edit
so i had the following condition to check this:
if(preg_match("/^".preg_replace('/:id/','([0-9]*)?',preg_replace('/\//','\/',$_route['route']))."$/", $route)){
// ...
}
but it seems to be a bad solution. i have to escape the slashes, i have to replace the :id parameter, and after this, i can check if the route is matching.
but it looks terrible and has a big problem. it doenst work if the parameter is not named :id.
can you give me some hint or show a better way?
thanks in advance
update:
im not using any mvc framework. its an "build your own framework and learn task"
the routes stored on a route table:
people_index get /people people#index
people_show get /people/:id people#show
people_edit get /people/:id/edit people#edit
people_update put /people/:id people#update
people_new get /people/new people#new
people_create post /people people#create
people_delete delete /people/:id people#delete
if i call link_to 'people_index' it will display /people. the condition above is part of the routing parser. it just looking for the correct uri and return (for the edit link) people#edit. after this, i know there is an resource PeoplesController and call the edit action.
i know there are lots of great mvc frameworks for php. but i want to get more experience and rebuild some rails logic into php :)

first of all I would split this up some, preferable in a reusable way, say a function to parse the mapping first.
function createpattern( $map ) {
$map = preg_replace('/:id/', '([0-9]+|new)', $map );
//add other mappings here for example.
$map = preg_replace('/:name/', '([a-z])', $map );
return $map;
}
Or just use rusty trusty str_replace for this part.
function createpattern( $map ) {
$search = array( ':id', ':name' );
$replace = array( '([0-9]+|new)', '([a-z]+)?');
$map = preg_quote($map); //maybe use it here.
$map = str_replace($search, $replace, $map );
return $map;
}
So this
/people/:id
becomes
/people/([0-9]+|new)
Somewhere you should use preg_quote, but not after adding in the regx bits.
essentially your regx is now
\/people\/([0-9]+|new)\/... etc.
preg_quote would be used to prevent a user from adding in regular expressions of there own and messing up yours, unintentional and otherwise.

Related

Pass a closure to an array value

Please forgive me if I'm way off here but I'm trying to create a simple template parser and to do this I'm using regular expressions to find template tags and replace them with dynamic text.
I want to be able to use a closure to return the replacement text. For example:
// Example 1
$tag['\{{title:(.*)}}\'] = function($1)
{
return $1;
};
// Example 2
$tag['\{{title:(.*)}}\'] = function($1)
{
$something = ['One', 'Two', 'Three'];
return $1 . implode($something);
};
// Now do the replacements
foreach($tag as $pattern=>$replacement)
{
preg_replace($pattern, $replacement, $template);
}
I've included example 2 to explain that the result maybe dynamic and this is why I can't simply use strings.
I also feel like I need to explain why I'd need such functionality. The patterns are meant to be expandable, so other developers can add their own patterns easily.
If I've completely off the mark and this isn't going to be achievable, could you point me in the direction to achieve the same/similar functionality?
also, side note - not my main question - but is there a way to do multiple preg_replace in one go instead of looping through, seems inefficient.

PHP key => value array to method arguments (order?)

I think this is quite interesting!!! :).
What I've got?
In the application that I'm using on some level in some objects (doesn't really matter) I get an array, for example:
$array = array(
'argument_label' => 'value_label',
'argument_name' => 'value_name',
'argument_id' => 'value_id'
)
I don't have any impact on how and when this array is created. Next, I've got a method:
public function arrayArgument($array) {
$label = isset($array['argument_label']) ? $array['argument_label'] : 'default_label';
$name = isset($array['argument_name']) ? $array['argument_name'] : 'default_name';
$id = isset($array['argument_id']) ? $array['argument_id'] : 'default_id';
// Do something
return 'something';
}
I really hate it. There is no way of proper documentation for the method arguments (as PHPDocumentator work not so well with arrays), and those issets are driving me crazy. Additionally it is a nightmare for someone who will work with this code in the future, when I will already be a "retired nerd".
What I want?
I want to have a function like that:
public function notArrayArgument(
$id='default_id',
$label='default_label',
$name='default_name'
) {
// Do something
return 'something';
}
What I can do?
When I get array, I can change some code, and make my own method run. So I need some kind of solution to get from here:
$array = array(
'argument_label' => 'value_label',
'argument_name' => 'value_name',
'argument_id' => 'value_id'
)
To here:
notArrayArgument('value_id', 'value_label', 'value_name');
Or here:
notArrayArgument($array['argument_id'], $array['argument_label'], $array['argument_name']);
What are the problems?
This is not template like. The number of variables is always different, the names are always different, and sometimes some of them are passed, sometimes not.
It should work really fast...
Calling the method arguments in the right order. Array can be sorted, not sorted or random sorted, while the arguments inside method are always in the same order. The array should be reordered to match the method arguments order, and after that the method should be called.
What I came with?
I've got an idea using reflectionClass. I can check the names of method arguments, get them in order, reorder the array and try to call this method. But this is quite resource eating solution, as reflectionClass is not so fast I think.
Solution using extract? This would be great. But after extract, I need to use exact variables names in code. And I don't know them as every time they are different, and I need an universal approach.
NEW (thx to comment): call_user_func_array(). It is great, but it only works with indexed arrays, so this will be the last but not least step of the possible solution. As the order of arguments is still unknown...
Does this problem have a nice semantic, pragmatic solution?
I read my question once more, and I hope it is clear to understand. If not, please post a comment and I will do my best to describe the problem better.
Kudos for thinking about the maintainer, but I'd argue simplicity is just as important as nice semantics and pragmatism. Consider this: if you had to ask how to write such a pattern, what are the chances that it will be obvious to the reader? I'd much rather come across code where I can just think "Yep, that's clear" than "Oh cool that's a really intricate and clever way of setting array defaults".
With this in mind, it seems to me that an array is being used in a situation more suited to a class. You have an id, a label and a name, which surely represents an entity - exactly what classes are for! Classes make it easy to set defaults and provide PHPDoc on each of their properties. You could have a constructor that simply takes one of your existing arrays and array_merge()s it with an array of defaults. For the reverse conversion, conveniently, casting an object to an array in PHP results in an associative array of its properties.
Try to use classes as George Brighton mentioned.
If you can't for some legacy or library constraint, you will have to use reflection. Don't worry too much about the performance of reflection classes, a lot of frameworks use them to do the request routing.
You can use a function like:
function arrayArgument($object, $method, $array)
{
$arguments = [];
$reflectionMethod = new ReflectionMethod(get_class($object), $method);
foreach ($reflectionMethod->getParameters() as $parameter)
{
$arguments[] = isset($array[$parameter->name]) ? $array[$parameter->name] : $parameter->getDefaultValue();
}
call_user_func_array(array($object, $method), $arguments);
}
So you can do
$instance = new MyClass();
arrayArgument($instance, 'notArrayArgument', ['name' => 'myname']);

Route parameter patterns on routes with similar parameters

I have a few routes that takes a couple of UUIDs as parameters:
Route::get('/foo/{uuid1}/{uuid2}', 'Controller#action');
I want to be able to verify that those parameters are the correct format before passing control off to the action:
Route::pattern('uuid1', '^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$');
This works fine. However, I really don't want to repeat that pattern so many times (in the real case I have it repeated 8 times for 8 different UUID route parameters).
I can't do this:
Route::get('/foo/{uuid}/{uuid}', 'Controller#action');
Because that produces an error:
Route pattern "/foo/{uuid}/{uuid}" cannot reference variable name "uuid" more than once.
I can lump them all into a single function call since I discovered Route::patterns:
Route::patterns([
'uuid1' => '^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$',
'uuid2' => '^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$',
]);
But that is still repetitious. Is there a way I can bind multiple pattern keys to a single regular expression?
Ideally I'd like to find a way that avoids something like this:
$pattern = 'uuid regex';
Route::patterns([
'uuid1' => $pattern,
'uuid2' => $pattern,
]);
There's no built in way to handle this, and I actually think the solution you found is pretty nice. Maybe a bit more elegant would be this:
Route::patterns(array_fill_keys(['uuid1', 'uuid2'], '/uuid regex/'));

Passing an array of variable length to a class method

I am trying to create a little MVC framework for myself and am using a url structure similar to: http://www.example.com/className/methodName/var1/var2/var3. I want to make it able to accomodate for any number of variables.
I have all of the information how I need it, except for the /var1/var2/var3 part. Currently I have it in an array exploded() upon the "/", so: var[0] = "var1",var[1] = "var2",var[2] = "var3"
Since the method which will be called will be unknown and each method can require a different amount parameters I want to figure out a way to be able to pass the parameters as:
$controller->$function($var1, $var2, $var3);
rather than
$controller->$function($var);
I realize I can string together a comma delimited variable such as $var1, $var2, $var3 and use eval() to do what I want, but I am wondering if there is a better way to go about it. I would rather not use eval() for this as it will be using user submitted data.
Worst case scenario, I figure I would just try to cleanse the data before the eval, but I'd still like to avoid it.
One other potential Idea I had was to use a foreach() and loop through each element inside of the the method call. But even that seems a little messy to me.
On a side note, is the standard way to pass a variable amount of parameters to group them together in an array and pass in a single parameter?
If anyone knows of a more elegant way to do this, I would really appreciate it.
Use call_user_func_array
call_user_func_array(array($controller, $function), array($var1, $var2...));
#Dogbert's answer would complement what I'm about to suggest.
You could create a function that accepts no parameters and you're free to pass as many parameters (or no parameters at all), you'll only have to use PHP functions like: func_get_args(), func_num_args() and func_get_arg(int arg_index) to do your manipulation.
An Example follows below:
function syncOrderRelation(){
$num_arguments = func_num_args();
$arguments = func_get_args();
#gets all the arguments in the request
list($userID,$spID,$productID) = $arguments;
#if you know what you're expecting or
for($i = 0; $i < $num_arguments; $i++){
$argument_at_index = func_get_arg($i);
}
}
You could call this function in the following ways:
syncOrderRelation();
syncOrderRelation($var1,$var2,var3);
syncOrderRelation($var1);
#or you could call it as #Dogbert suggested since the function will accept whatever
#arguments you supply
The rest is up to you...Happy coding!

Is it possible to render the content of a file without using eval in PHP?

I'm trying to create a simple framework in PHP which will include a file (index.bel) and render the variables within the file. For instance, the index.bel could contain the following:
<h1>{$variable_name}</h1>
How would I achieve this without using eval or demanding the users of the framework to type index.bel like this:
$index = "<h1>{$variable_name}</h1>";
In other words: Is it possible to render the content of a file without using eval? A working solution for my problem is this:
index.php:
<?php
$variable_name = 'Welcome!';
eval ('print "'.file_get_contents ("index.bel").'";');
index.bel:
<h1>{$variable_name}</h1>
I know many have recommended you to add template engine, but if you want to create your own, easiest way in your case is use str_replace:
$index = file_get_contents ("index.bel");
$replace_from = array ('$variable_a', '$variable_b');
$replace_to = array ($var_a_value, $var_b_value);
$index = str_replace($replace_from,$replace_to,$index);
Now that is for simple variable replace, but you soon want more tags, more functionality, and one way to do things like these are using preg_replace_callback. You might want to take a look at it, as it will eventually make possible to replace variables, include other files {include:file.bel}, replace text like {img:foo.png}.
EDIT: reading more your comments, you are on your way to create own framework. Take a look at preg_replace_callback as it gives you more ways to handle things.
Very simple example here:
...
$index = preg_replace_callback ('/{([^}]+)}>/i', 'preg_callback', $index);
...
function preg_callback($matches) {
var_dump($matches);
$s = preg_split("/:/",$matches[1]); // string matched split by :
$f = 'func_'.strtolower($s[0]); // all functions are like func_img,func_include, ...
$ret = $f($s); // call function with all split parameters
return $ret;
}
function func_img($s) {
return '<img src="'.$s[1].'" />';
}
From here you can improve this (many ways), for example dividing all functionalities to classes, if you want.
Yes, this is possible, but why are you making your own framework? The code you provided clearly looks like Smarty Template. You could try to look how they did it.
A possible way to run those code is splitting them into pieces. You split on the dollar sign and the next symbol which is not an underscore, a letter or an number. Once you did that. You could parse it into a variable.
$var = 'variable_name'; // Split it first
echo $$var; // Get the given variable
Did you mean something like this?

Categories