I am building a class to send API calls to Rapidshare and return the results of said call. Here's how I want the call to be done:
$rs = new rs();
$params = array(
'sub' => 'listfiles_v1',
'type' => 'prem',
'login' => '10347455',
'password' => 'not_real_pass',
'realfolder' => '0',
'fields' => 'filename,downloads,size',
);
print_r($rs->apiCall($params));
And here's the class so far:
class RS
{
var $baseUrl = 'http://api.rapidshare.com/cgi-bin/rsapi.cgi?sub=';
function apiCall($params)
{
$newUrl = $baseUrl;
$keys = array_keys($params);
$count = count($params);
for($i = 0; $i < $count; $i++)
{
$newUrl .= $keys[$i];
$newUrl .= '&';
$newUrl .= $params[$keys[$i]];
}
return $newUrl;
}
}
Obviously i'm returning $newUrl and using print_r() to test the query string, and this is what it comes out as with the code shown above:
sub&listfiles_v1type&premlogin&10347455password&_not_real_passrealfolder&0fields&filename,downloads,size
When it should be:
http://api.rapidshare.com/cgi-bin/rsapi.cgi?sub=listfiles_v1&type=prem&login=10347455&password=not_real_pass&realfolder=0&fields=filename,downloads,size
Hopefully you can see what I'm trying to do here :P It's probably a silly mistake that I'm failing to find or a logical error.
Thanks in advance.
You should have:
$newUrl = $this->baseUrl;
You need to use $this to refer to members of that class from within that class. Also don't use var to declare members. It's PHP4 and (afaik) deprecated. Instead use private (etc.
Lastly, your loop to create the parameters can be greatly simplified and the logic isn't correct for what you want to achieve. Try:
class RS {
private $baseUrl = 'http://api.rapidshare.com/cgi-bin/rsapi.cgi?';
function apiCall($params) {
$newUrl = $this->baseUrl;
foreach ($params as $k => $v) {
$newUrl .= urlencode($k) . '=' . urlencode($v) . '&';
}
return $newUrl;
}
}
Or, even better, use http_build_query():
class RS {
private $baseUrl = 'http://api.rapidshare.com/cgi-bin/rsapi.cgi?';
function apiCall($params) {
return $this->baseUrl . http_build_query($params);
}
}
Related
I have a PHP function that outputs quote marks like so when rendered in the html.
onload="this.rel='stylesheet'"
What I want is the following:
onload="this.rel='stylesheet'"
Here is the function causing the first example to happen - does anyone know how I can solve this?
public function get_css($opts, $index, $count)
{
$out = array();
$version = filemtime($_SERVER['DOCUMENT_ROOT'].'/assets/css/app.min.css');
$str = "this.rel='stylesheet'";
$out[] = $this->_single_tag('link', array(
'rel'=>'preload',
'as'=>'style',
'type'=>'text/css',
'href'=>'/assets/css/app.min.'.$version.'.css',
'onload'=>$str,
));
return implode("\n\t", $out)."\n";
}
Here is the function for _single_tag
protected function _single_tag($tag=false, array $attrs)
{
if ($tag===false) return;
return PerchXMLTag::create($tag, 'single', $attrs);
}
The problem comes from the PerchXMLTag::create() method which does some HTML encoding on the values supplied to it.
Looking at the Perch documentation there doesn't seem to be a way to disable this, so my suggestion would be to replace the code within the get_css function with something that just outputs the raw HTML:
public function get_css($opts, $index, $count)
{
$out = array();
$version = filemtime($_SERVER['DOCUMENT_ROOT'].'/assets/css/app.min.css');
$str = "this.rel='stylesheet'";
$out[] = "<link rel='preload' as='style' type='text/css' href='/assets/css/app.min.{$version}.css' onload='{$str}' />";
return implode("\n\t", $out)."\n";
}
public function get_css($opts, $index, $count)
{
$out = array();
$version = filemtime($_SERVER['DOCUMENT_ROOT'].'/assets/css/app.min.css');
$str = "this.rel=\"stylesheet\"";
$out[] = $this->_single_tag('link', array(
'rel'=>'preload',
'as'=>'style',
'type'=>'text/css',
'href'=>'/assets/css/app.min.'.$version.'.css',
'onload'=>$str,
));
return implode("\n\t", $out)."\n";
}
Try with that.
My input is quite simple:
$input = '( ( "M" AND ( "(" OR "AND" ) ) OR "T" )';
where ( starts a new node on tree and ) ends it. AND and OR words are reserved for boolean operation so until they are not inside "" marks, they have a special meaning. In my DSL AND and OR clauses alter By node level so that there can be only either AND or OR clauses at level. If AND comes after OR, it will start a new subnode. All characters inside "" should be regarded as they are. Finally " could be escaped with \" as usual.
What is a good way to make translate sentence which look like this in PHP:
$output = array(array(array("M" , array("(", "AND")) , "T"), FALSE);
Note that FALSE is an indicator, that the root level had OR keyword. If input was:
( ( "M" AND ( "(" OR "AND" ) ) AND "T" )
then output would be:
$output = array(array(array("M", array("(", "AND")), "T"), TRUE);
It is tempting to use replace('(', 'array('); and eval code, but then escaping characters and wrapping literals would become an issue.
At the moment I'm not implementing NOT boolean operator on DSL.
Thanks for any help. JavaSript code are ok too.
Python example:
I made some tests with Python before going to PHP and Javascript. What I did was:
find string literals with regex
replace literals with generated keys
store literals to assoc list
split input to single level list by parenthesis
find root level boolean operator
get rid of boolean operators and white space
replace literal keys with stored values
It might work but I'm sure there must be much more sophisticated way to do it.
http://codepad.org/PdgQLviI
Here's my side-project's library modification. It should handle these kind of strings - perform some stress tests and let me know if it breaks somewhere.
Tokenizer type class is needed to extract and tokenize variables, so they don't interfere with syntax parsing and tokenize parethesis so they could be matched directly (lazy-evaluated content wouldn't catch nested level and greedy would cover all contexts on the same level). It also has some keyword syntax (a little more than needed, since it will be parsed only for root level). Throws InvalidArgumentException when trying to access variables registry with wrong key, and RuntimeException when parenthesis don't match.
class TokenizedInput
{
const VAR_REGEXP = '\"(?P<string>.*?)\"';
const BLOCK_OPEN_REGEXP = '\(';
const BLOCK_CLOSE_REGEXP = '\)';
const KEYWORD_REGEXP = '(?<keyword>OR|AND)';
// Token: <TOKEN_DELIM_LEFT><TYPE_TOKEN><ID_DELIM>$id<TOKEN_DELIM_RIGHT>
const TOKEN_DELIM_LEFT = '<';
const TOKEN_DELIM_RIGHT = '>';
const VAR_TOKEN = 'VAR';
const KEYWORD_TOKEN = 'KEYWORD';
const BLOCK_OPEN_TOKEN = 'BLOCK';
const BLOCK_CLOSE_TOKEN = 'ENDBLOCK';
const ID_DELIM = ':';
const ID_REGEXP = '[0-9]+';
private $original;
private $tokenized;
private $data = [];
private $blockLevel = 0;
private $varTokenId = 0;
protected $procedure = [
'varTokens' => self::VAR_REGEXP,
'keywordToken' => self::KEYWORD_REGEXP,
'blockTokens' => '(?P<open>' . self::BLOCK_OPEN_REGEXP . ')|(?P<close>' . self::BLOCK_CLOSE_REGEXP . ')'
];
private $tokenMatch;
public function __construct($input) {
$this->original = (string) $input;
}
public function string() {
isset($this->tokenized) or $this->tokenize();
return $this->tokenized;
}
public function variable($key) {
isset($this->tokenized) or $this->tokenize();
if (!isset($this->data[$key])) {
throw new InvalidArgumentException("Variable id:($key) does not exist.");
}
return $this->data[$key];
}
public function tokenSearchRegexp() {
if (!isset($this->tokenMatch)) {
$strings = $this->stringSearchRegexp();
$blocks = $this->blockSearchRegexp();
$this->tokenMatch = '#(?:' . $strings . '|' . $blocks . ')#';
}
return $this->tokenMatch;
}
public function stringSearchRegexp($id = null) {
$id = $id ?: self::ID_REGEXP;
return preg_quote(self::TOKEN_DELIM_LEFT . self::VAR_TOKEN . self::ID_DELIM)
. '(?P<id>' . $id . ')'
. preg_quote(self::TOKEN_DELIM_RIGHT);
}
public function blockSearchRegexp($level = null) {
$level = $level ?: self::ID_REGEXP;
$block_open = preg_quote(self::TOKEN_DELIM_LEFT . self::BLOCK_OPEN_TOKEN . self::ID_DELIM)
. '(?P<level>' . $level . ')'
. preg_quote(self::TOKEN_DELIM_RIGHT);
$block_close = preg_quote(self::TOKEN_DELIM_LEFT . self::BLOCK_CLOSE_TOKEN . self::ID_DELIM)
. '\k<level>'
. preg_quote(self::TOKEN_DELIM_RIGHT);
return $block_open . '(?P<contents>.*)' . $block_close;
}
public function keywordSearchRegexp($keyword = null) {
$keyword = $keyword ? '(?P<keyword>' . $keyword . ')' : self::KEYWORD_REGEXP;
return preg_quote(self::TOKEN_DELIM_LEFT . self::KEYWORD_TOKEN . self::ID_DELIM)
. $keyword
. preg_quote(self::TOKEN_DELIM_RIGHT);
}
private function tokenize() {
$current = $this->original;
foreach ($this->procedure as $method => $pattern) {
$current = preg_replace_callback('#(?:' . $pattern . ')#', [$this, $method], $current);
}
if ($this->blockLevel) {
throw new RuntimeException("Syntax error. Parenthesis mismatch." . $this->blockLevel);
}
$this->tokenized = $current;
}
protected function blockTokens($match) {
if (isset($match['close'])) {
$token = self::BLOCK_CLOSE_TOKEN . self::ID_DELIM . --$this->blockLevel;
} else {
$token = self::BLOCK_OPEN_TOKEN . self::ID_DELIM . $this->blockLevel++;
}
return $this->addDelimiters($token);
}
protected function varTokens($match) {
$this->data[$this->varTokenId] = $match[1];
return $this->addDelimiters(self::VAR_TOKEN . self::ID_DELIM . $this->varTokenId++);
}
protected function keywordToken($match) {
return $this->addDelimiters(self::KEYWORD_TOKEN . self::ID_DELIM . $match[1]);
}
private function addDelimiters($token) {
return self::TOKEN_DELIM_LEFT . $token . self::TOKEN_DELIM_RIGHT;
}
}
Parser type class performs matching on tokenized string - pulls out registered variables and goes recursively into nested contexts by clonig itself.
Operator type handling is unusual, which makes it more of a derived class, but it's hard to achieve satysfying abstraction in Parsers' world anyway.
class ParsedInput
{
private $input;
private $result;
private $context;
public function __construct(TokenizedInput $input) {
$this->input = $input;
}
public function result() {
if (isset($this->result)) { return $this->result; }
$this->parse($this->input->string());
$this->addOperator();
return $this->result;
}
private function parse($string, $context = 'root') {
$this->context = $context;
preg_replace_callback(
$this->input->tokenSearchRegexp(),
[$this, 'buildStructure'],
$string
);
return $this->result;
}
protected function buildStructure($match) {
if (isset($match['contents'])) { $this->parseBlock($match['contents'], $match['level']); }
elseif (isset($match['id'])) { $this->parseVar($match['id']); }
}
protected function parseVar($id) {
$this->result[] = $this->input->variable((int) $id);
}
protected function parseBlock($contents, $level) {
$nested = clone $this;
$this->result[] = $nested->parse($contents, (int) $level);
}
protected function addOperator() {
$subBlocks = '#' . $this->input->blockSearchRegexp(1) . '#';
$rootLevel = preg_replace($subBlocks, '', $this->input->string());
$rootKeyword = '#' . $this->input->keywordSearchRegexp('AND') . '#';
return $this->result[] = (preg_match($rootKeyword, $rootLevel) === 1);
}
public function __clone() {
$this->result = [];
}
}
Example usage:
$input = '( ( "M" AND ( "(" OR "AND" ) ) AND "T" )';
$tokenized = new TokenizedInput($input);
$parsed = new ParsedInput($tokenized);
$result = $parsed->result();
I removed namespaces/imports/intrefaces, so you might adjust'em as you need. Also didn't want to dig through (possibly invalid now) comments, so removed them as well.
I found a bit of code for stripping a query string and adding a new value to it, but I want to be able to do this with an array of options. Could someone give me a hand in modifying this code to do that?
Current code:
function add_querystring_var($url, $key, $value) {
$url = preg_replace('/(.*)(\?|&)' . $key . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&');
$url = substr($url, 0, -1);
$value = $value ? "=".urlencode($value) : '';
if (strpos($url, '?') === false)
return ($url . '?' . $key . $value);
else
return ($url . '&' . $key . $value);
}
And I want it to do a foreach for each key and value given and then rebuild the new url.
Example: add_querystring_var(curPageURL(), array("order","sort"), array("swn","DESC"))
So I want the following URL http://www.example.com/students when put through the example above would return http://www.example.com/students?order=swn&sort=DESC
Does anyone know how I can do this? I'm new to this area of PHP. :)
UPDATE:
I forgot to mention sometimes the url may have other queries in it, so I want it to replace the ones that I enter into my array.
Example 1: http://www.example.com/students?page=2 would need to turn into http://www.example.com/students?page=2&order=swn&sort=DESC
Example 2: http://www.example.com/students?page=2&order=name&sort=ASC would need to turn into http://www.example.com/students?page=2&order=swn&sort=DESC
function add_querystring_var($url, $additions) {
$parsed = parse_url($url);
if (isset($parsed['query'])) {
parse_str($parsed['query'], $query);
} else {
$query = array();
}
$parsed['query'] = http_build_query(array_merge($query, $additions));
return http_build_url($parsed);
}
Use it this way:
$new_url = add_querystring_var($url, array('order' => 'swn', 'sort' => 'DESC'));
If you're getting errors saying that http_build_url is not defined, see
PHP http_build_url() and PECL Install
You're kind of reinventing the wheel with that function... first off, you'd be better off using urlencode() on your key/value data rather than that regular expression (and I see that you're not encoding your value string at all)
As dpDesignz mentions in his comment - there is a built-in function available: http_build_query()
$querydata = array('foo' => array('bar', 'baz'),
'baz'=>'boom',
'cow'=>'milk',
'php'=>'hypertext processor');
$querystring = http_build_query($data, '', '&');
Or, to use your example:
$querydata = array("order" => "swn", "sort" => "DESC");
$url = curPageURL();
$concat = "?";
if (strpos($url, "?") !== false)) {
$concat = "&"
}
$url .= $concat . http_build_query($data, '', '&');
I am writing a class that handles routing of my PHP webservice but I need to correct the regex, and I want to know what would be the most effecient way to parse the url.
Example urls:
POST /users
GET /users
GET /users&limit=10&offset=0
GET /users/search&keyword=Richard
GET /users/15/posts/38
What I want to create in PHP for class is this:
$router = new Router();
$router->addRoute('POST', '/users', function(){});
$router->addRoute('GET', '/users/:uid/posts/:pid', function($uid, $pid){});
$target = $router->doRouting();
The target variable would now contain an array with:
method
url
callback method
This is what I got so far:
class Router{
use Singleton;
private $routes = [];
private $routeCount = 0;
public function addRoute($method, $url, $callback){
$this->routes[] = ['method' => $method, 'url' => $url, 'callback' => $callback];
$this->routeCount++;
}
public function doRouting(){
$reqUrl = $_SERVER['REQUEST_URI'];
$reqMet = $_SERVER['REQUEST_METHOD'];
for($i = 0; $i < $this->routeCount; $i++){
// Check if the url matches ...
// Parse the arguments of the url ...
}
}
}
So I need a regex that first of all:
/mainAction/:argumentName/secondaryAction/:secondaryActionName
checks if that matches the $reqUrl (see at the for loop above)
Extracts the arguments, so we can use them in our callback function.
What I tried myself:
(code should be in the for loop # doRouting function)
// Extract arguments ...
$this->routing[$i]['url'] = str_replace(':arg', '.+', $this->routing[$i]['url']);
// Does the url matches the routing url?
if(preg_match('#^' . $this->routes[$i]['url'] . '$#', $reqUrl)){
return $this->routes[$i];
}
I really appreciate all help, thanks alot.
this basicly works now.
public function doRouting(){
// I used PATH_INFO instead of REQUEST_URI, because the
// application may not be in the root direcory
// and we dont want stuff like ?var=value
$reqUrl = $_SERVER['PATH_INFO'];
$reqMet = $_SERVER['REQUEST_METHOD'];
foreach($this->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['url'])) . "$#D";
$matches = Array();
// check if the current request matches the expression
if($reqMet == $route['method'] && preg_match($pattern, $reqUrl, $matches)) {
// remove the first match
array_shift($matches);
// call the callback with the matched positions as params
return call_user_func_array($route['callback'], $matches);
}
}
}
PS: You dont need the $routeCount attribute
Great answer #MarcDefiant. Cleanest PHP router I came across. Did a small modification to support regular expression as well. Not sure why you use preq_quote ?
Small todo would be to cast the array to a assoc array. E.g. replace ['0' => 1] with ['id' => 1]
function matchRoute($routes = [], $url = null, $method = 'GET')
{
// I used PATH_INFO instead of REQUEST_URI, because the
// application may not be in the root direcory
// and we dont want stuff like ?var=value
$reqUrl = $url ?? $_SERVER['PATH_INFO'];
$reqMet = $method ?? $_SERVER['REQUEST_METHOD'];
$reqUrl = rtrim($reqUrl,"/");
foreach ($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['url'])) . "$#D";
$pattern = "#^" . preg_replace('/:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', $route['url']) . "$#D";
// echo $pattern."\n";
$params = [];
// check if the current request params the expression
$match = preg_match($pattern, $reqUrl, $params);
if ($reqMet == $route['method'] && $match) {
// remove the first match
array_shift($params);
// call the callback with the matched positions as params
// return call_user_func_array($route['callback'], $params);
return [$route, $params];
}
}
return [];
}
$match = matchRoute([
[
'method' => 'GET',
'url' => '/:id',
'callback' => function($req) {
exit('Hello');
}
],
[
'method' => 'GET',
'url' => '/api/(.*)', // Match all /api/hello/test/...
'callback' => function($req) {
print_r($req);
exit('cool');
}
]
]);
list($route,$params) = $match;
call_user_func_array($route['callback'], [$params]);
I want to add GET parameters to URLs that may and may not contain GET parameters without repeating ? or &.
Example:
If I want to add category=action
$url="http://www.acme.com";
// will add ?category=action at the end
$url="http://www.acme.com/movies?sort=popular";
// will add &category=action at the end
If you notice I'm trying to not repeat the question mark if it's found.
The URL is just a string.
What is a reliable way to append a specific GET parameter?
Basic method
$query = parse_url($url, PHP_URL_QUERY);
// Returns a string if the URL has parameters or NULL if not
if ($query) {
$url .= '&category=1';
} else {
$url .= '?category=1';
}
More advanced
$url = 'http://example.com/search?keyword=test&category=1&tags[]=fun&tags[]=great';
$url_parts = parse_url($url);
// If URL doesn't have a query string.
if (isset($url_parts['query'])) { // Avoid 'Undefined index: query'
parse_str($url_parts['query'], $params);
} else {
$params = array();
}
$params['category'] = 2; // Overwrite if exists
$params['tags'][] = 'cool'; // Allows multiple values
// Note that this will url_encode all values
$url_parts['query'] = http_build_query($params);
// If you have pecl_http
echo http_build_url($url_parts);
// If not
echo $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'] . '?' . $url_parts['query'];
You should put this in a function at least, if not a class.
Here's a shorter version of the accepted answer:
$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?') . 'category=action';
Edit: as discussed in the accepted answer, this is flawed in that it doesn't check to see if category already exists. A better solution would be to treat the $_GET for what it is - an array - and use functions like in_array().
$data = array('foo'=>'bar',
'baz'=>'boom',
'cow'=>'milk',
'php'=>'hypertext processor');
$queryString = http_build_query($data);
//$queryString = foo=bar&baz=boom&cow=milk&php=hypertext+processor
echo 'http://domain.com?'.$queryString;
//output: http://domain.com?foo=bar&baz=boom&cow=milk&php=hypertext+processor
This function overwrites an existing argument
function addToURL( $key, $value, $url) {
$info = parse_url( $url );
parse_str( $info['query'], $query );
return $info['scheme'] . '://' . $info['host'] . $info['path'] . '?' . http_build_query( $query ? array_merge( $query, array($key => $value ) ) : array( $key => $value ) );
}
Example with updating existent parameters.
Also url_encode used, and possibility to don't specify parameter value
<?
/**
* Add parameter to URL
* #param string $url
* #param string $key
* #param string $value
* #return string result URL
*/
function addToUrl($url, $key, $value = null) {
$query = parse_url($url, PHP_URL_QUERY);
if ($query) {
parse_str($query, $queryParams);
$queryParams[$key] = $value;
$url = str_replace("?$query", '?' . http_build_query($queryParams), $url);
} else {
$url .= '?' . urlencode($key) . '=' . urlencode($value);
}
return $url;
}
Use strpos to detect a ?. Since ? can only appear in the URL at the beginning of a query string, you know if its there get params already exist and you need to add params using &
function addGetParamToUrl(&$url, $varName, $value)
{
// is there already an ?
if (strpos($url, "?"))
{
$url .= "&" . $varName . "=" . $value;
}
else
{
$url .= "?" . $varName . "=" . $value;
}
}
<?php
$url1 = '/test?a=4&b=3';
$url2 = 'www.baidu.com/test?a=4&b=3&try_count=1';
$url3 = 'http://www.baidu.com/test?a=4&b=3&try_count=2';
$url4 = '/test';
function add_or_update_params($url,$key,$value){
$a = parse_url($url);
$query = $a['query'] ? $a['query'] : '';
parse_str($query,$params);
$params[$key] = $value;
$query = http_build_query($params);
$result = '';
if($a['scheme']){
$result .= $a['scheme'] . ':';
}
if($a['host']){
$result .= '//' . $a['host'];
}
if($a['path']){
$result .= $a['path'];
}
if($query){
$result .= '?' . $query;
}
return $result;
}
echo add_or_update_params($url1,'try_count',1);
echo "\n";
echo add_or_update_params($url2,'try_count',2);
echo "\n";
echo add_or_update_params($url3,'try_count',3);
echo "\n";
echo add_or_update_params($url4,'try_count',4);
echo "\n";
/**
* #example addParamToUrl('/path/to/?find=1', array('find' => array('search', 2), 'FILTER' => 'STATUS'))
* #example addParamToUrl('//example.com/path/to/?find=1', array('find' => array('search', 2), 'FILTER' => 'STATUS'))
* #example addParamToUrl('https://example.com/path/to/?find=1&FILTER=Y', array('find' => array('search', 2), 'FILTER' => 'STATUS'))
*
* #param $url string url
* #param array $addParams
*
* #return string
*/
function addParamToUrl($url, array $addParams) {
if (!is_array($addParams)) {
return $url;
}
$info = parse_url($url);
$query = array();
if ($info['query']) {
parse_str($info['query'], $query);
}
if (!is_array($query)) {
$query = array();
}
$params = array_merge($query, $addParams);
$result = '';
if ($info['scheme']) {
$result .= $info['scheme'] . ':';
}
if ($info['host']) {
$result .= '//' . $info['host'];
}
if ($info['path']) {
$result .= $info['path'];
}
if ($params) {
$result .= '?' . http_build_query($params);
}
return $result;
}
$parameters = array();
foreach ($get as $key => $value)
{
$parameters[] = $key.'='.$value;
}
$url = 'http://example.com/movies?'.implode('&', $parameters);
One-liner:
$url .= (strpos($url, '?') ? '&' : '?') . http_build_query($additionalParams);
using http_build_query is recommended because it encodes special characters (for example spaces or # in email addresses)
Improved version for 2022
This allows existing parameters to be replaced, and also preserves existing URL fragment (the part after # at the end of an URL)
function addParametersToUrl(string $url, array $newParams): string
{
$url = parse_url($url);
parse_str($url['query'] ?? '', $existingParams);
$newQuery = array_merge($existingParams, $newParams);
$newUrl = $url['scheme'] . '://' . $url['host'] . ($url['path'] ?? '');
if ($newQuery) {
$newUrl .= '?' . http_build_query($newQuery);
}
if (isset($url['fragment'])) {
$newUrl .= '#' . $url['fragment'];
}
return $newUrl;
}
Testing:
$newParams = [
'newKey' => 'newValue',
'existingKey' => 'new',
];
echo addParametersToUrl('https://www.example.com', $newParams);
// https://www.example.com?newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/', $newParams);
// https://www.example.com/dir/?newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/dir/', $newParams);
// https://www.example.com/dir/?newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar', $newParams);
// https://www.example.com/dir/file?foo=bar&newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar&existingKey=old', $newParams);
// https://www.example.com/dir/file?foo=bar&existingKey=new&newKey=newValue
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar&existingKey=old#hash', $newParams);
// https://www.example.com/dir/file?foo=bar&existingKey=new&newKey=newValue#hash
echo addParametersToUrl('https://www.example.com/dir/file#hash', $newParams);
// https://www.example.com/dir/file?newKey=newValue&existingKey=new#hash
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar#hash', $newParams);
// https://www.example.com/dir/file?foo=bar&newKey=newValue&existingKey=new#hash
I think you should do it something like this.
class myURL {
protected $baseURL, $requestParameters;
public function __construct ($newURL) {
$this->baseurl = $newURL;
$this->requestParameters = array();
}
public function addParameter ($parameter) {
$this->requestParameters[] = $parameter;
}
public function __toString () {
return $this->baseurl.
( count($this->requestParameters) ?
'?'.implode('&', $this->requestParameters) :
''
);
}
}
$url1 = new myURL ('http://www.acme.com');
$url2 = new myURL ('http://www.acme.com');
$url2->addParameter('sort=popular');
$url2->addParameter('category=action');
$url1->addParameter('category=action');
echo $url1."\n".$url2;
After searching for many resources/answers on this topic, I decided to code my own. Based on #TaylorOtwell's answer here, this is how I process incoming $_GET request and modify/manipulate each element.
Assuming the url is: http://domain.com/category/page.php?a=b&x=y
And I want only one parameter for sorting: either ?desc=column_name or ?asc=column_name. This way, single url parameter is enough to sort and order simultaneously. So the URL will be http://domain.com/category/page.php?a=b&x=y&desc=column_name on first click of the associated table header row.
Then I have table row headings that I want to sort DESC on my first click, and ASC on the second click of the same heading. (Each first click should "ORDER BY column DESC" first) And if there is no sorting, it will sort by "date then id" by default.
You may improve it further, like you may add cleaning/filtering functions to each $_GET component but the below structure lays the foundation.
foreach ($_GET AS $KEY => $VALUE){
if ($KEY == 'desc'){
$SORT = $VALUE;
$ORDER = "ORDER BY $VALUE DESC";
$URL_ORDER = $URL_ORDER . "&asc=$VALUE";
} elseif ($KEY == 'asc'){
$SORT = $VALUE;
$ORDER = "ORDER BY $VALUE ASC";
$URL_ORDER = $URL_ORDER . "&desc=$VALUE";
} else {
$URL_ORDER .= "&$KEY=$VALUE";
$URL .= "&$KEY=$VALUE";
}
}
if (!$ORDER){$ORDER = 'ORDER BY date DESC, id DESC';}
if ($URL_ORDER){$URL_ORDER = $_SERVER[SCRIPT_URL] . '?' . trim($URL_ORDER, '&');}
if ($URL){$URL = $_SERVER[SCRIPT_URL] . '?' . trim($URL, '&');}
(You may use $_SERVER[SCRIPT_URI] for full URL beginning with http://domain.com)
Then I use resulting $ORDER I get above, in the MySQL query:
"SELECT * FROM table WHERE limiter = 'any' $ORDER";
Now the function to look at the URL if there is a previous sorting and add sorting (and ordering) parameter to URL with "?" or "&" according to the sequence:
function sort_order ($_SORT){
global $SORT, $URL_ORDER, $URL;
if ($SORT == $_SORT){
return $URL_ORDER;
} else {
if (strpos($URL, '?') !== false){
return "$URL&desc=$_SORT";
} else {
return "$URL?desc=$_SORT";
}
}
}
Finally, the table row header to use the function:
echo "<th><a href='".sort_order('id')."'>ID</a></th>";
Summary: this will read the URL, modify each of the $_GET components and make the final URL with parameters of your choice with the correct form of usage of "?" and "&"
public function addGetParamToUrl($url, $params)
{
foreach ($params as $param) {
if (strpos($url, "?"))
{
$url .= "&" .http_build_query($param);
}
else
{
$url .= "?" .http_build_query($param);
}
}
return $url;
}
another improved function version. Mix of existing answers with small improvements (port support) and bugfixes (checking keys properly).
/**
* #param string $url original url to modify - can be relative, partial etc
* #param array $paramsOverride associative array, can be empty
* #return string modified url
*/
protected function overrideUrlQueryParams($url, $paramsOverride){
if (!is_array($paramsOverride)){
return $url;
}
$url_parts = parse_url($url);
if (isset($url_parts['query'])) {
parse_str($url_parts['query'], $params);
} else {
$params = [];
}
$params = array_merge($params, $paramsOverride);
$res = '';
if(isset($url_parts['scheme'])) {
$res .= $url_parts['scheme'] . ':';
}
if(isset($url_parts['host'])) {
$res .= '//' . $url_parts['host'];
}
if(isset($url_parts['port'])) {
$res .= ':' . $url_parts['port'];
}
if (isset($url_parts['path'])) {
$res .= $url_parts['path'];
}
if (count($params) > 0) {
$res .= '?' . http_build_query($params);
}
return $res;
}
Try this function to add URL parameters.
Then you can disable the link when parameter is set so there is no url parameter duplicate.
<?php
function addQueryString($a)
{
if (empty($_SERVER['QUERY_STRING']))
return '?' . $a;
else if (!empty($_SERVER['QUERY_STRING']))
return '?' . $_SERVER['QUERY_STRING'] . '&' . $a;
}
?>
test
sat
In case you are using WordPress you can simply use
add_query_args(['sort' => 'asc'], 'http:/example.com/?search=news')
Docs https://developer.wordpress.org/reference/functions/add_query_arg/