I have this class:
class Route
{
protected $routes = [
"view_article" => "view/{articleUrl}",
"edit_article" => "edit/{articleId}"
];
}
How can I make a function that returns the url replacing content inside brackets?
For example if I use this code:
$route->getUrl('view_article', 'first-article');
It should return: view/first-article
Then you should make a function that return your variable?
class Route {
function getUrl($find, $replace) {
$entry = isset($this->routes[$find]) ? $this->routes[$find]: false;
if($entry) {
return str_replace(sprintf('{%s}', $find), $replace, $entry);
} else {
return return false;
}
}
}
something like that.
function getUrl($key, $value){
$value = preg_replace('/[\[{\(].*[\]}\)]/U' , $value, $this->routes[$key]);
return $value;
}
A couple of solutions here:
getUrl below will accept the route name as the first parameter, then replace any placeholders with the subsequent variables. Not the best solution - you may have too many or too few variables. When using IDEs, there will be no parameter hinting.
class Route
{
protected $routes = [
"view_article" => "view/{articleUrl}",
"edit_article" => "edit/{articleId}",
"view_page" => "view/{articleId}/{pageName}"
];
public function getUrl() {
$arg_list = func_get_args();
$route = $this->routes[$arg_list[0]];
unset($arg_list[0]);
foreach($arg_list as $arg) {
$route = preg_replace('/{[^\}]+}/', $arg, $route, 1);
}
return $route;
}
}
$route = new Route();
var_dump($route->getUrl('view_page', '17', 'hello_world')); //'view/17/hello_world'
An alternative approach is to use an array of arguments and str_replace the key=>value pairs:
class Route
{
protected $routes = [
"view_article" => "view/{articleUrl}",
"edit_article" => "edit/{articleId}",
"view_page" => "view/{articleId}/{pageName}"
];
public function getUrl($routeName, $args) {
$route = $this->routes[$routeName];
foreach($args as $key => $value) {
$route = str_replace(sprintf('{%s}', $key), $value, $route);
}
return $route;
}
}
$route = new Route();
var_dump($route->getUrl('view_page', ['articleId' => 17, 'pageName' => 'hello_world'])); //'view/17/hello_world'
In both cases, be sure to include additional checks (route exists, all the variables have been replaced, etc).
Related
What would be the best way to perform some modification on a method argument before the method is actually executed?
I have tried achieving this with a combination of attributes and use of the decorator/proxy pattern:
#[\Attribute]
class Query
{
}
class Foo
{
#[Query]
public function request(array $query = [])
{
}
public function foo(array $query = [])
{
}
#[Query]
public function bar(string $name, int $age, array $query = [])
{
}
}
class FooDecorator
{
private Foo $target;
public function __construct(Foo $target)
{
$this->target = $target;
}
public function __call(string $methodName, array $args)
{
$class = get_class($this->target);
try {
$reflection = new \ReflectionClass($class);
$methods = $reflection->getMethods();
$attributeName = __NAMESPACE__ . '\Query';
foreach ($methods as $method) {
if ($method->getName() !== $methodName) {
continue;
}
$attributes = $method->getAttributes();
foreach ($attributes as $attribute) {
if ($attribute->getName() === $attributeName) {
$parameters = $method->getParameters();
foreach ($parameters as $key => $param) {
if ($param->getName() === 'query') {
// Here we filter the parameter named 'query'
$args[$key] = $this->validateQueryParameter($args[$key]);
break;
}
}
}
}
}
} catch (\Exception $e) {
}
if (method_exists($this->target, $methodName)) {
return call_user_func_array([$this->target, $methodName], $args);
}
}
private function validateQueryParameter(array $query): array
{
$allowed = [
'foo',
'bar',
];
$query = array_filter($query = array_change_key_case($query), function ($key) use ($allowed) {
// Filter out any non-allowed keys
return in_array($key, $allowed);
}, ARRAY_FILTER_USE_KEY);
return $query;
}
}
$foo = new FooDecorator(new Foo());
// Should remove faz & baz, but keep foo in the query
$foo->query(['faz' => 1, 'baz' => 2, 'foo' => 3]);
// Should not perform anything on the query parameter
$foo->foo(['baz' => 1]);
// Should remove faz & baz, but keep foo in the query
$foo->bar('foo', 100, ['faz' => 1, 'baz' => 2, 'foo' => 3]);
This works as expected, but since I am using a decorator here I am now missing the hints for each method included in the Foo class.
I know that I could use an interface and declare all methods included in Foo, but then I would need to implement every method (the real class contains many many methods) in the decorator, which seems like overkill.
i have a object like this:
CORE::$ObjClassInstABS['ABS']['DATA']
that contains an array of Class->Method:
array (
'DATA' =>
array (
'USERDATAMANAGER' =>
Class_UserdataManager::__set_state(array(
)),
'PRODDATAMANAGER' =>
Class_ProddataManager::__set_state(array(
)),
),
)
i create a new object, of type class Like this:
CORE::$ObjClassInstABS['ABS']['ABSDATAMANAGER'] = new class;
i cant but need pass all the methods of the first object, ignoring the class of origin to the class i create on fly, and that allows me to execute the functions from the class declared on the fly.
does this exist in php 7.0 or is there any way to achieve this reach??
It would be like cloning the methods of several classes to a single and new class.
Answer for #Damian Dziaduch comments
the piece of code that i used to Dynamically Instance all class file from a directory is this, and populate the first object with instance of class:
CORE::$ObjClassInstABS['ABS']['ABSDATAMANAGER']= new class;
foreach (CORE::$ObjClassABS['DATA'] as $key => $name) {
if (strpos($name, 'class.') !== false) {
$name = basename($name);
$name = preg_replace('#\.php#', '', $name);
$names = explode(".", $name);
foreach ($names as $key => $namesr) {
$names[$key] = ucfirst(strtolower($namesr));
}
$name = implode('_', $names);
$NamesClass = $name . 'Manager';
$InstanceClass = strtoupper(preg_replace('#\Class_#', '', $NamesClass));
CORE::$ObjClassInstABS['ABS']['DATA'][$InstanceClass] = $this->$InstanceClass = new $NamesClass();
}
}
the result of it is the Array printed at start of the post CORE::$ObjClassInstABS['ABS']['DATA'] .
if you see at start of foreach i have the new class declaration to use, in loop, how can i populate CORE::$ObjClassInstABS['ABS']['ABSDATAMANAGER'] in the loop, it with all methods of the first object instance, and make it executables?
that i whant (not work):
foreach ( CORE::$ObjClassInstABS['ABS']['DATA'] as $key => $value ) {
CORE::$ObjClassInstABS['ABS']['ABSDATAMANAGER'] .= Clone($value);
}
$value represent where is storing the methods:
::__set_state(array()),
As requested.
Not sure whether this will fill you requirements... The question is whether you are able to overwrite the CORE::$ObjClassInstABS
<?php
CORE::$ObjClassInstABS = new class extends \ArrayIterator {
private $container = [];
public function __construct(array $container)
{
$this->container = [
'ABS' => [
'DATA' => [
'USERDATAMANAGER' => new class {},
'PRODDATAMANAGER' => new class {},
],
],
];
}
public function offsetExists($offset)
{
return isset($this->container[$offset]);
}
public function offsetGet($offset)
{
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetUnset($offset)
{
unset($this->container[$offset]);
}
};
I don't know that much about php. I have 3 files in my project.
1st one is system.php, which hold the hole application logic.
here its code:
<?php
require "config/config_system.php";
$config = new Config;
$config-> load('config.php');
// this is way i want to change setting.
echo $config-> replace("db.host" , "replace value");
?>
2nd one is config_system.php, which holds the configuration logic.here its code:
<?php
class Config {
protected $data;
protected $informaton;
protected $default;
public function load($file) {
$this->data = require $file;
$this->informaton = require $file;
}
public function find($key, $default = null) {
$this->default = $default;
$segments = explode(".", $key);
$data = $this->data;
foreach ($segments as $segment) {
if (isset($data[$segment])) {
$data = $data[$segment];
} else {
$data = $this->default;
break;
}
}
return $data;
}
public function exists($key) {
return $this->find($key) !== $this->default;
}
// this is the function i am trying to make valide
public function replace($value) {
$arrayvalues = explode(".", $value);
$informaton = $this->informaton;
foreach ($arrayvalues as $arrayvalue) {
if (isset($informaton[$arrayvalue])) {
$informaton = $informaton[$arrayvalue];
}
}
return $arrayvalues;
}
}
?>
and 3rd one is config.php, which holds the configurations.
<?php
return [
"installation" => [
// this is the value I want to change via a function to true.
"create_db" => "false",
"create_table" => "false"
],
"db" => [
"host" => "localhost",
"user_name" => "root",
"password" => ""
]
];
?>
Now I want to change some setting via a function. How can I do it?
public function replace ($keyset, $value){
$key_parse = explode(".",keyset);
$this->data[$key_parse[0]][$key_parse[1]] = $value;
return $this->data;
}
Use this function in your config_system.php
I'm currently developing a router for one of my projects and I need to do the following:
For example, imagine we have this array of set routes:
$routes = [
'blog/posts' => 'Path/To/Module/Blog#posts',
'blog/view/{params} => 'Path/To/Module/Blog#view',
'api/blog/create/{params}' => 'Path/To/Module/API/Blog#create'
];
and then if we pass this URL through: http://localhost/blog/posts it will dispatch the blog/posts route - that's fine.
Now, when it comes to the routes that require parameters, all I need is a method of implementing the ability to pass the parameters through, (i.e. http://localhost/blog/posts/param1/param2/param3 and the ability to prepend api to create http://localhost/api/blog/create/ to target API calls but I'm stumped.
Here's something basic, currently routes can have a pattern, and if the application paths start with that pattern then it's a match. The rest of the path is turned into params.
<?php
class Route
{
public $name;
public $pattern;
public $class;
public $method;
public $params;
}
class Router
{
public $routes;
public function __construct(array $routes)
{
$this->routes = $routes;
}
public function resolve($app_path)
{
$matched = false;
foreach($this->routes as $route) {
if(strpos($app_path, $route->pattern) === 0) {
$matched = true;
break;
}
}
if(! $matched) throw new Exception('Could not match route.');
$param_str = str_replace($route->pattern, '', $app_path);
$params = explode('/', trim($param_str, '/'));
$params = array_filter($params);
$match = clone($route);
$match->params = $params;
return $match;
}
}
class Controller
{
public function action()
{
var_dump(func_get_args());
}
}
$route = new Route;
$route->name = 'blog-posts';
$route->pattern = '/blog/posts/';
$route->class = 'Controller';
$route->method = 'action';
$router = new Router(array($route));
$match = $router->resolve('/blog/posts/foo/bar');
// Dispatch
if($match) {
call_user_func_array(array(new $match->class, $match->method), $match->params);
}
Output:
array (size=2)
0 => string 'foo' (length=3)
1 => string 'bar' (length=3)
This is a basic version - simply just a concept version to show functionality and I wouldn't suggest using it in a production environment.
$routes = [
'blog/view' => 'Example#index',
'api/forum/create' => 'other.php'
];
$url = explode('/', $_GET['url']);
if (isset($url[0]))
{
if ($url[0] == 'api')
{
$params = array_slice($url, 3);
$url = array_slice($url, 0, 3);
}
else
{
$params = array_slice($url, 2);
$url = array_slice($url, 0, 2);
}
}
$url = implode('/', $url);
if (array_key_exists($url, $routes))
{
$path = explode('/', $routes[$url]);
unset($path[count($path)]);
$segments = end($path);
$segments = explode('#', $segments);
$controller = $segments[0];
$method = $segments[1];
require_once APP_ROOT . '/app/' . $controller . '.php';
$controller = new $controller;
call_user_func_array([$controller, $method], $params);
}
I have a static class like this.
<?php
class Language
{
public static $language = array();
var $config;
function __construct($config)
{
switch(strtoupper($config['LANGUAGE']))
{
case 'ENGLISH':
self::setEnglish();
break;
case 'TURKISH':
self::setTurkish();
break;
default:
self::setEnglish();
}
}
public static function setEnglish()
{
self::$language = array(
'CHAIN_VALIDATOR_INITIALIZED' => 'ChainValidator initialized!',
'ERROR_FUNCTION_RETURNED_FALSE' => 'A function returned false.',
);
}
public static function setTurkish()
{
self::$language = array(
'CHAIN_VALIDATOR_INITIALIZED' => 'ChainValidator çalışıyor!',
'ERROR_FUNCTION_RETURNED_FALSE' => 'Bir fonksiyon false döndürdü.',
);
}
public static function getLanguage($key)
{
return isset(self::$language[$key]) ? self::$language[$key] : $key;
}
}
?>
It is being used like this,
Language::getLanguage('CHAIN_VALIDATOR_INITIALIZED')
but I have to pass more parameters. Similar to this,
Language::getLanguage('CHAIN_VALIDATOR_INITIALIZED', array(__FUNCTION__))
These parameters should be passed in an order, like %s. Final look should look like this:
"A function returned false" to;
"%s function returned false"
And output would be;
"myLovelyFunction() returned false" (the first array parameter, which is __FUNCTION__)
Sounds like piece of cake my brain stopped atm.
How can I do this?
You can use call_user_func_array and sprintf for that, e.g.
<?php
foo();
function foo() {
$text = format('CHAIN_VALIDATOR_INITIALIZED', array(__FUNCTION__, __LINE__));
echo $text;
}
function format($key, $params) {
$format = '%s#%s: function returned false'; // =getLanguage($key)
array_unshift($params, $format);
return call_user_func_array('sprintf', $params);
}
prints
foo#5: function returned false
Maybe I'm misunderstanding the question, but I think vsprintf does just what you want. It is basically a sprintf that accepts arguments as an array.
$string = "%s %s blabla";
$params = array('foo', 'bar');
$output = vsprintf($string, $params);
Changing my getLanguage function solved it. If anyone needs it, here is the solution.
public static function getLanguage($key, $parameters = null)
{
$string = self::$language[$key];
if(is_array($parameters))
{
foreach ($parameters as $k => $v)
{
$string = str_ireplace('%s', $v, $string);
}
}
return $string;
}