I've been learning PHP for about a week, and I need some guidance as to whether or not I should be using arrays to sort my data based on multiple variables. My table contains information about operas, the composers, their nationalities, and includes a link to a relevant site.
Without being overly verbose, I need to let a user select data based on Nationality, Century it was written, and whether or not it contains a relevant link. I'd like to let them be even more specific, but my code is quickly becoming unmanageable. For 3 variables, I have eight separate queries covering each case. If I want to go to 4 variables, I will need 16 queries. I know there is a better way to do this, but I've read multiple times that arrays are more appropriate for smaller quantities, not necessarily a table that contains around 40,000 rows.
$req=$_REQUEST['nat'];
$req2=$_REQUEST['cent'];
$req3=$_REQUEST['imslp'];
if ($req!="any" && $req2!="any" && $req3=="yes") $query="SELECT * FROM operadatabase WHERE nationality='$req' AND century=$req2 AND imslplink = imslplink";
else if ($req!="any" && $req2!="any" && $req3=="no") $query="SELECT * FROM operadatabase WHERE nationality='$req' AND century=$req2";
else if ($req!="any" && $req2=="any" && $req3=="yes") $query="SELECT * FROM operadatabase WHERE nationality='$req' AND imslplink = imslplink";
else if ($req!="any" && $req2=="any" && $req3=="no") $query="SELECT * FROM operadatabase WHERE nationality='$req'";
else if($req=="any" && $req2!="any" && $req3=="yes") $query="SELECT * FROM operadatabase WHERE century=$req2 AND imslplink = imslplink";
else if($req=="any" && $req2!="any" && $req3=="no") $query="SELECT * FROM operadatabase WHERE century=$req2";
else if($req=="any" && $req2=="any" && $req3=="yes") $query="SELECT * FROM operadatabase WHERE imslplink = imslplink";
else if($req=="any" && $req2=="any" && $req3=="no") $query="SELECT * FROM operadatabase";
$result=mysql_query($query);
$num=mysql_numrows($result);
mysql_query($result);
mysql_close();
$i=0;
for ($i; $i < $num; $i++){
$f12=mysql_result($result,$i,"fullname");
$f13=mysql_result($result,$i,"operatitle");
$f14=mysql_result($result,$i,"nationality");
$f15=mysql_result($result,$i,"century");
$f16=mysql_result($result,$i,"imslplink");
echo "<tr>";
echo "<td>".$f12."</td><td>".$f13."</td>";
if(strlen($f14) > 2) {
echo '<td><img src="/icons/'.$f14.'.png" width="25" height="25"></td>';
}
echo "<td>".$f15."</td>";
if(substr($f16, 0, 4) == "http") {
echo "<td>IMSLP</td>";
}
The best way I can word my questionis this: Within a query, how can I select a set of data based on one variable, then select from that set based on a second variable, then a third, etc.? Alternatively, can I nest queries?
You can create a simple class that includes returned methods for chaining. You would have to assign some if/else here and probably have to add extra variables into the class, but you get the idea.
class Combiner
{
public $base;
public function __construct($base = 'SELECT * FROM operadatabase')
{
// This saves the front of your select statement and makes it available
// for the rest of the methods. Uses in final Write() method
$this->base = $base;
}
public $vars;
public function FetchNat($value = '')
{
// This just checks your $_REQUEST value here
if(!empty($value) && $value !== 'any')
// You'll want to sanitize this value
$this->vars[] = "nationality = '".mysql_real_escape_string($value)."'";
// Returning this allows you to use this method/function
// in your chain. That is why you are returning $this
// on all your methods/functions
return $this;
}
public function FetchCent($value = '')
{
// This just checks your $_REQUEST value here
if(!empty($value) && $value !== 'any')
// You'll want to sanitize this value
$this->vars[] = "century = '".mysql_real_escape_string($value)."'";
return $this;
}
public function FetchIMSLP($value = '')
{
// This just checks your $_REQUEST value here
if(!empty($value) && $value == 'yes')
$this->vars[] = "imslplink = 'imslplink'";
return $this;
}
public $sql;
public function Write()
{
// This compiles all the variables down the line and returns a final string
$statement = (isset($this->vars) && !empty($this->vars))? " where ".implode(" and ",$this->vars):"";
$this->sql = $this->base.$statement;
return $this;
}
}
// Create new instance of your auto-fetcher
$write = new Combiner();
// This is a method chain that will return an auto-compiling string as it compiles further down the line
$test = $write->FetchCent('')->FetchNat('test')->FetchIMSLP('yes')->Write()->sql;
print_r($test);
This would write:
SELECT * FROM operadatabase where nationality = 'test' and imslplink = 'imslplink';
Related
My application is executing user-defined SQL statements that contain query parameters. To detect the parameter names that should be passed to oci_bind_by_name I use a simple reg-ex pattern like /:\w+/ but this fails for string literals and comments contained in the SQL statement.
BEGIN
/* some unused :param here */
SELECT 'some other :param there' FROM foo;
END;
Handling string literal detection and comment by more reg-ex patterns seems like a bad idea when thinking about even more nasty examples like:
BEGIN
SELECT '/* some comment :literals --' FROM foo;
-- some more comment :literals */
END;
Is there some way to get the required query parameter names for binding using OCI8 functions? What other possibilities do exist without falling back to manually parsing SQL in user code?
My code below is not a great way to solve this problem. Before you use that code, keep looking for a more official solution.
It appears that OCI does have functionality to dynamically retrieve bind names, through the function OCIStmtGetBindInfo. However, it also looks like that function is not available in the default PHP functions. Maybe there are other, more advanced ways of connecting PHP to Oracle that supply the necessary function, but I don't know enough about OCI or PHP to find them.
If you're ready for a not-so-great solution, you can use my open source program plsql_lexer to find the bind variable names. The lexer breaks SQL statements into small tokens, and handles difficult syntax issues like comments and strings. The results should be much more accurate than using a few regular expressions.
The downside is that the program is not a full parser, and you have to deal with the primitive tokens. In this case, it's relatively easy to find 99.9999% of the bind variables with a single SQL statement. After installing the program, put your SQL into the middle of the following SELECT statement:
--Find bind variables.
--(Words or numerics that were immediately preceded (excluding whitespace) by a colon.)
select to_char(value) bind_variable_name
from
(
--Get previous token.
select type, value, first_char_position,
lag(to_char(type)) over (order by first_char_position) previous_type
from
(
--Convert to tokens, ignore whitespace.
select type, value, first_char_position
from table(plsql_lexer.lex(
q'[
--Here's the actual SQL statement you care about.
--/*:fake_bind1*/
select 1 a
from dual
where 1 = : real_bind_1 and :real_bind_2 = ':fake_bind_2'
]'))
where type not in ('whitespace')
order by first_char_position
)
)
where type in ('numeric', 'word')
and previous_type = ':'
order by first_char_position;
BIND_VARIABLE_NAME
------------------
real_bind_1
real_bind_2
There may still be some weird cases this code doesn't handle. For example, a bind variable can be a quoted identifier, you may need to handle the double quotes. And the above code doesn't handle indicators. On the other hand, I have literally never seen either of those features used, so it may not matter to you. Test thoroughly.
Finally I wrote some small state machine for parsing SQL statement bind parameters and put it into a helper class to not conflict with other globals:
class SqlBindNames {
private static function isLineBreak($ch) {
return (($ch === "\r") || ($ch === "\n"));
}
private static function isIdentChar($ch) {
return (($ch >= 'a') && ($ch <= 'z')) ||
(($ch >= 'A') && ($ch <= 'Z')) ||
(($ch >= '0') && ($ch <= '9')) ||
($ch === '_');
}
private const QUOTE_SINGLE_CHR = '\'';
private const QUOTE_DOUBLE_CHR = '"';
private const COMMENT_LINE_STR = "--";
private const COMMENT_BEGIN_STR = "/*";
private const COMMENT_END_STR = "*/";
private const BIND_START_CHR = ':';
private const MODE_NORMAL = 0;
private const MODE_QUOTE_SINGLE = 1;
private const MODE_QUOTE_DOUBLE = 2;
private const MODE_COMMENT_LINE = 3;
private const MODE_COMMENT_MULTI = 4;
private const MODE_BIND_VARNAME = 5;
public static function getSqlBindNames(string $sql, bool $unique = true) {
$mode = self::MODE_NORMAL;
$names = array();
$namesIndex = array();
$len = strlen($sql);
$i = 0;
while ($i < $len) {
$curr = $sql[$i];
if ($i < $len - 1) {
$next = $sql[$i + 1];
} else {
$next = "\0";
}
$nextMode = $mode;
if ($mode === self::MODE_NORMAL) {
if ($curr === self::QUOTE_SINGLE_CHR) {
$nextMode = self::MODE_QUOTE_SINGLE;
} else if ($curr === self::QUOTE_DOUBLE_CHR) {
$nextMode = self::MODE_QUOTE_DOUBLE;
} else if (($curr === self::COMMENT_LINE_STR[0]) && ($next === self::COMMENT_LINE_STR[1])) {
$i += 1;
$nextMode = self::MODE_COMMENT_LINE;
} else if (($curr === self::COMMENT_BEGIN_STR[0]) && ($next === self::COMMENT_BEGIN_STR[1])) {
$i += 1;
$nextMode = self::MODE_COMMENT_MULTI;
} else if (($curr === self::BIND_START_CHR) && self::isIdentChar($next)) {
$bindName = "";
$nextMode = self::MODE_BIND_VARNAME;
}
} else if (($mode === self::MODE_QUOTE_SINGLE) && ($curr === self::QUOTE_SINGLE_CHR)) {
$nextMode = self::MODE_NORMAL;
} else if (($mode === self::MODE_QUOTE_DOUBLE) && ($curr === self::QUOTE_DOUBLE_CHR)) {
$nextMode = self::MODE_NORMAL;
} else if (($mode === self::MODE_COMMENT_LINE) && self::isLineBreak($curr)) {
$nextMode = self::MODE_NORMAL;
} else if (($mode === self::MODE_COMMENT_MULTI) && ($curr === self::COMMENT_END_STR[0]) && ($next === self::COMMENT_END_STR[1])) {
$i += 1;
$nextMode = self::MODE_NORMAL;
} else if ($mode === self::MODE_BIND_VARNAME) {
if (self::isIdentChar($curr)) {
$bindName = $bindName . $curr;
}
if (!self::isIdentChar($next)) {
/* found new bind param */
if (!$unique || !in_array(strtolower($bindName), $namesIndex)) {
array_push($namesIndex, strtolower($bindName));
array_push($names, $bindName);
}
$nextMode = self::MODE_NORMAL;
}
}
$i += 1;
$mode = $nextMode;
}
return $names;
}
}
It seems to work, improvements are welcome!
Before I begin, I want to point out that I can solve my problem. I've rehearsed enough in PHP to be able to get a workaround to what I'm trying to do. However I want to make it modular; without going too much into detail to further confuse my problem, I will simplify what I am trying to do so that way it does not detract from the purpose of what I'm doing. Keep that in mind.
I am developing a simple CMS to manage a user database and edit their information. It features pagination (which works), and a button to the left that you click to open up a form to edit their information and submit it to the database (which also works).
What does not work is displaying each row from MySQL in a table using a very basic script which I won't get into too much detail on how it works. But it basically does a database query with this:
SELECT * FROM users OFFSET (insert offset here) LIMIT (insert limit here)
Essentially, with pagination, it tells what number to offset, and the limit is how many users to display per page. These are set, defined, and tested to be accurate and they do work. However, I am not too familiar how to handle these results.
Here is an example query on page 2 for this CMS:
SELECT * FROM users OFFSET 10 LIMIT 10
This should return 10 rows, 10 users down in the database. And it does, when I try this command in command prompt, it gives me what I need:
But when I try to handle this data in PHP like this:
<?php
while ($row = $db->query($pagination->get_content(), "row")) {
print_r($row);
}
?>
$db->query method is:
public function query($sql, $type = "assoc") {
$this->last_query = $sql;
$result = mysql_query($sql, $this->connection);
$this->confirm_query($result);
if ($type == "row") {
return mysql_fetch_row($result);
} elseif ($type == "assoc" || true) {
return mysql_fetch_assoc($result);
} elseif ($type == "array") {
return mysql_fetch_array($result);
} elseif ($type == false) {
return $result;
}
}
$pagination->get_content method is:
public function get_content() {
global $db;
$query = $this->base_sql;
if (!isset($_GET["page"])) {
$query .= " LIMIT {$this->default_limit}";
return $query;
} elseif (isset($_GET["page"]) && $_GET["page"] == 1) {
$query .= " LIMIT {$this->default_limit}";
return $query;
} elseif (isset($_GET["page"])) {
$query .= " LIMIT {$this->default_limit}";
$query .= " OFFSET " . (($_GET["page"] * $this->default_limit) - 10);
return $query;
}
}
And my results from the while loop (which should print out each row of the database, no?) gives me the same row everytime, continuously until PHP hits the memory limit/timeout limit.
Forgive me if its something simple. I rarely ever handle database data in this manner. What I want it to do is show the 10 users I requested. Feel free to ask any questions.
AFTER SOME COMMENTS, I'VE DECIDED TO SWITCH TO MYSQLI FUNCTIONS AND IT WORKS
// performs a query, does a number of actions dependant on $type
public function query($sql, $type = false) {
$sql = $this->escape($sql);
if ($result = $this->db->query($sql)) {
if ($type == false) {
return $result;
} elseif ($type == true || "assoc") {
if ($result->num_rows >= 2) {
$array;
$i = 1;
while ($row = $result->fetch_assoc()) {
$array[$i] = $row;
$i++;
}
return $array;
} elseif ($result->num_rows == 1) {
return $result->fetch_assoc();
}
} elseif ($type == "array") {
if ($result->num_rows >= 2) {
$array;
$i = 1;
while ($row = $result->fetch_array()) {
$array[$i] = $row;
$i++;
}
return $array;
} elseif ($result->num_rows == 1) {
return $result->fetch_array();
}
}
} else {
die("There was an error running the query, throwing error: " . $this->db->error);
}
}
Basically, in short, I took my entire database, deleted it, and remade another one based on the OOD mysqli (using the class mysqli) and reformatted it into a class that extends mysqli. A better look at the full script can be found here:
http://pastebin.com/Bc00hESn
And yes, it does what I want it to. It queries multiple rows, and I can handle them however I wish using the very same methods I planned to do them in. Thank you for the help.
I think you should be using mysql_fetch_assoc():
<?php
while ($row = $db->query($pagination->get_content())) {
print_r($row);
}
?>
I am trying to get an if statement to dynamically code itself based on user input. So the if statement code is being inserted into a variable ($if_statement_variable), like this:
$if_statement_variable = "if (";
$price = trim($_GET["Price"]);
if (!empty($price)) {
$if_statement_variable .= " $Product->price < $price ";
}
$product_name = trim($_GET["Product_name"]);
if (!empty($product_name)) {
$if_statement_variable .= " && $Product->$product_name == 'product_name_string' ";
}
// plus many more if GET requests
$if_statement_variable .= ") ";
Then results from an XML file will be displayed based on user values submitted and the $if_statement_variable.
$XMLproducts = simplexml_load_file("products.xml");
foreach($XMLproducts->product as $Product) {
echo $if_statement_variable; // Here is where the problem is
{ // opening bracket for $variable_if_statement
echo $Product->$product_name; // products displayed based on if statement code in $if_statement_variable
} //closing bracket for $variable_if_statement
}
The echo $if_statement_variable above correctly displays $price from this variable string, but does NOT display $Product->price. Assuming $price had a value of 1000, the output is if ( == 1000 ). How can I get $Product->price to correctly insert itself into the $if_statement_variable so that it displays the $Product->price values from the XML file?
If you're trying to generate a boolean value dynamically, based on some complicated logic, just assign the true/false value to a variable, (say, $booleanValue) and then do if($booleanValue){}
Something like:
$price = trim($_GET['price']);
$product_name = trim($_GET['Product_name']);
if(!empty($price)){
$booleanValue = ($Product->price < $price);
}
if(!empty($productName)){
$booleanValue = ($booleanValue && $Product->$product_name == 'product_name_string')
}
if($booleanValue){
echo $Product->$product_name;
}
In other words, create a variable to hold the actual boolean value, not a string to hold an expression that will evaluate to a boolean value.
Do not build PHP source as a string. In this case callables are a better solution. A callable is a function inside a variable. In PHP this might be an function name, and array with an object and a method name, an anonymous function or an object implementing invoke.
Here is an example for anonymous functions:
function getCondition($parameters) {
$conditions = [];
if (isset($parameters['Price']) && trim($parameters['Price']) != '') {
$price = trim($parameters['price']);
$conditions[] = function($product) use ($price) {
return $product->price < $price;
}
}
if (isset($parameters['Product_name']) && trim($parameters['Product_name']) != '') {
$productName = trim($parameters['Product_name']);
$conditions[] = function($product) use ($productName) {
return $product->product_name == $productName;
}
}
return function($product) use ($conditions) {
foreach ($conditions as $condition) {
if (!$condition($product)) {
return FALSE;
}
}
return TRUE;
}
}
$condition = getConditon($_GET);
if ($condition($product)) {
...
}
It is important that each function can be called the same way. So if you call the condition function you not need to know, which condition it is. In the example above you can imagine that the getCondition() function can get really complex really fast if you add additional conditions.
If you encapsulate the conditions into classes, the usage becomes more readable:
$condition = new \YourCompany\Product\Conditions\Group(
new \YourCompany\Product\Conditions\PriceMaximum($_GET, 'Price'),
new \YourCompany\Product\Conditions\Name($_GET, 'Product_name')
);
if ($condition($product)) {
...
}
This way you separate the actual condition logic from the from the use. The source of all classes is some more then the anonymous function variant. But you you can put each class in it's own file and use them in any combination you need.
The classes need to implement __invoke().
class Group {
private $_conditions = array();
public function __construct() {
$this->_conditions = func_get_args();
}
public function __invoke($product) {
foreach ($this->_conditions as $condition) {
if (!$condition($product)) {
return FALSE;
}
}
return TRUE;
}
}
class Name {
private $_productName = NULL;
public function __construct($parameters, $name) {
if (isset($parameters[$name]) && trim($parameters[$name]) > 0) {
$this->_productName = trim($parameters[$name]);
}
}
public function __invoke($product) {
return (
NULL === $this->_productName ||
$product->product_name == $this->_productName
);
}
}
class PriceMaximum {
private $_maximum = NULL;
public function __construct($parameters, $name) {
if (isset($parameters[$name]) && trim($parameters[$name]) > 0) {
$this->_maximum = trim($parameters[$name]);
}
}
public function __invoke($product) {
return (
NULL === $this->_maximum ||
$product->price < $this->_maximum
);
}
}
This concept can even be used together with an anonymous function:
$condition = new \YourCompany\Product\Conditions\Group(
new \YourCompany\Product\Conditions\PriceMaximum($_GET, 'Price'),
new \YourCompany\Product\Conditions\Name($_GET, 'Product_name'),
function ($product) {
return $product->category == 'food';
}
);
i have complex query and say it $complexQuery,
then i need to get all data row number from that without need the data result.
I researched that count_all_results() i better than num_rows()
now say my code :
$complexQuery = 'Some sql query';
$q = $this->db->query($complexQuery);
$total1 = $q->num_rows();
now i confuse to get all total data from that query,
any suggestion for using $this->db->count_all_results() with that query ?
== SOLVED BY EDITING DB_active_rec.php ==
i do this (leave as it is if tablename contained 'select') :
public function from($from)
{
foreach ((array)$from as $val)
{
if (strpos($val, ',') !== FALSE)
{
foreach (explode(',', $val) as $v)
{
$v = trim($v);
$this->_track_aliases($v);
$v = $this->ar_from[] = $this->_protect_identifiers($v, TRUE, NULL, FALSE);
if ($this->ar_caching === TRUE)
{
$this->ar_cache_from[] = $v;
$this->ar_cache_exists[] = 'from';
}
}
}
else
{
$val = trim($val);
// Added to bypass from arr if $val contained 'select' for complex query
// $this->db->count_all_rows("select * from tableName")
// will be select count(1) from (select * from tableName)
if(FALSE !== strpos(strtolower($val),'select')){
$this->ar_from[] = "($val)";
}else{
// Extract any aliases that might exist. We use this information
// in the _protect_identifiers to know whether to add a table prefix
$this->_track_aliases($val);
$this->ar_from[] = $val = $this->_protect_identifiers($val, TRUE, NULL, FALSE);
}
if ($this->ar_caching === TRUE)
{
$this->ar_cache_from[] = $val;
$this->ar_cache_exists[] = 'from';
}
}
}
return $this;
}
that should be like:
$this->db->where($complexQuery);
$this->db->from('your_table_name');
echo $this->db->count_all_results();
See: Codeigniter count_all_results()
another minutes, but the same code
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class SomeMyModel extends CI_Model {
private static $db;
function __construct()
{
parent::__construct();
self::$db = &get_instance()->db;
}
static function countTableResults(){
self::$db->where($coplexQuery);
return self::$db->count_all_results('#TABLENAME#');
}
I am generating unique id for my small application but I am facing some variable scope problem. my code-
function create_id()
{
global $myusername;
$part1 = substr($myusername, 0, -4);
$part2 = rand (99,99999);
$part3 = date("s");
return $part1.$part2.$part3;
}
$id;
$count=0;
while($count == 1)
{
$id;
$id=create_id();
$sqlcheck = "Select * FROM ruser WHERE userId='$id';";
$count =mysql_query($sqlcheck,$link)or die(mysql_error());
}
echo $id;
I dont know which variable I have to declare as global
That doesn't look like a variable scope problem, it looks like a simple variable assign problem:
$count=0;
while($count == 1)
{
This block will clearly never execute.
Further, please use a boolean with a good name when doing boolean checks. It reads so much cleaner. i.e.:
function isUniqueUserID($userIDToCheck)
{
$sqlcheck = "Select * FROM user WHERE userId='$userIDToCheck';";
$resource = mysql_query($sqlcheck)or die(mysql_error());
$count = mysql_fetch_assoc($resource);
if( count($count) > 0)
{return false;}
return true;
}
$userIDVerifiedUnique = false;
while(! $userIDVerifiedUnique )
{
$userIDToCheck = create_id();
$userIDVerifiedUnique = isUniqueUserID($userIDToCheck );
}
Note that mysql_query will use the last used connection if you don't specify a link:
http://us2.php.net/mysql_query
No need to make it global.
in adition to Zak's answer i'd pass the username into the function instead of using globals
function create_id($username)
{
$part1 = substr($username, 0, -4);
$part2 = rand (99,99999);
$part3 = date("s");
return $part1.$part2.$part3;
}
also
//$id; no need for this
$count=1; // this bit
while($count == 1) // not sure what's going on
{
//$id; again same thing no need for this
$id=create_id($myusername);
edit: now that i think of it: how do you expect to find "Select * FROM ruser WHERE userId='$id';"? A Select query is used to find something specific, your username is so random, i think the likely hood of actually successfully getting a record is 1 in a bajillion.
edit2 whoops, i see the whole point is to get a unique username... O_O
In addition to the others:
$count =mysql_query($sqlcheck,$link)or die(mysql_error());
mysql_query doesn't return a record count but, rather, a resource.
mysql_query