Where to check for any results - php

Ok so I have 2 classes. Photo class and PhotoMapper class.
Photo class contains all the values and outputs them.
PhotoMapper sets the values to the Photo class, by a assign class, like this:
$query = "SELECT * FROM users";
$query = $this->_pdo->prepare($query);
$query->bindValue(":id", $photo->id());
$query->execute();
$data = $query->fetch();
$photo->assign($data);
And the assign:
public function assign($data){
if (is_array($data)) {
foreach ($data as $name => $value)
{
$this->{'_' . $name} = $value;
}
}
}
Now where would i check for if $query->rowCount() > 0 ?
Should i inside is_array after the foreach, make a $this->rowCount = .. ?
What would be best to perform this check? I would like to check for the rowCount outside of both classes..
$photo = new Photo($albumID, $photoID, $view, $userID);
$photoMapper->select($photo, $view);
Is how it looks outside the classes. How can i check and output error if select(which is the query above) didnt find any rows?
I would need to have 2 queries? One to check, and one to select them? or?..

Well, if you're expecting data from the fetch operation but there isn't any (you could simply find out by checking if $data has any contents), you should throw an Exception. The following code snippet would typically be placed before the fetch:
// ...
$query->execute();
if ( !( $data = $query->fetch() ) ) {
throw new Exception('photo could not be loaded');
}
$photo->assign($data);
// ...
However, if you want the code to continue regardless, you can reverse the if condition, and put the assign call inside the statement;
$query->execute();
if ( ( $data = $query->fetch() ) ) {
$photo->assign($data);
}

Related

How to give meaningful names to functions in PHP?

This might seem like a stupid and trivial question. I am having problem naming functions in PHP. I have two functions that retrieves all the information of a student given its id or name and email.
Since PHP doesn't have function overloading in the same sense as JAVA, I am having difficulty naming the functions.
Here is what I have done. These are the names that I have given them.
get_students_with_id($id) and get_students_with_name_and_email($name, $email)
But the parameters are gonna increase. I need a better and simple solution to name these functions or methods. BTW, they all belong to the same class. So what am I gonna do? Thanks in advance.
In PHP there doesn't exist the concept of method overriding like in JAVA, for example, but you can send default parameters, for example:
get_students($id, $name = null, $email = null)
This means that you don't need to call the function with the three parameters. You can do it by calling it just with one and it will assume it is the id. For example, if you want to have a function working for your example above, you could do something like:
function get_students($id, $name = null, $email) {
if (!empty($id)) {
// Get students by their ids
} else if (!empty($name) && !empty($email)) {
// Get students by their names and emails
}
}
And you can call the function above:
get_students(1); //Will retrieve studen with id 1
get_students(null, "Name", "email#email.com"); //Will retrieve students with name "Name" and email "email#email.com"
A search method could look something like this:
class Student {
public static $columns = ['id', 'name', 'email', 'password', /* ... */];
// Imagine that this method is called with the following array:
// ['name' => 'Joe', 'password' => 'Pa55w0rD']
public static function search(array $queries) {
// We will be appending WHERE clauses to this SQL query
$sql = 'SELECT * FROM students WHERE ';
// Get the column names
$parameters = array_keys($queries);
// Create a parameterized WHERE clause for each column
foreach ($parameters as & $param) {
if ( ! in_array($param, self::$columns)) {
throw "Invalid column";
}
$param = "{$param} = :{$param}";
}
// Squish parameterized WHERE clauses into one
// and append it to the SQL query
$sql .= implode(' AND ', $parameters);
// The query will now look something like this:
// SELECT * FROM students WHERE name = :name AND password = :password
// Prepare the SQL query
$stmt = DB::instance()->prepare($sql);
// Go over the queries and bind the values to the columns
foreach ($queries as $col => $val) {
$stmt->bindValue(":" . $col, $val);
// Internally the query will look something like this:
// SELECT * FROM students WHERE name = 'Joe' AND password = 'Pa55w0rD'
}
// Execute
$result = $stmt->execute();
// ...
}
}
To use the method you would do something like this:
$student = Student::search([
'name' => 'Joe',
'password' => 'Pa55w0rD',
]);
You would want to handle the data in a safer way (making sure the password is hashed, for instance), but the general idea is there.
Why not use get_students($id=0, $name='', $email='') and so on for your other parameters, then have the function do whatever is necessary based on the passed parameters?
If that gets to be too much, pass an array check for keys. So if array('id' => 1) is passed then if (array_key_exists('id', $input)) {...} would catch it and proceed with actual function work, but if other keys/values are passed then a subsequent appropriate elseif would catch it.
Update: I think a format like this might be able to handle most of your use cases, based on some of the comments I read in the question. Not sure what your DB is, so this was done with MySQL in mind.
function get_students($input) {
$where = array();
$allowed_columns = array('id', 'name', 'email');
foreach ($allowed_columns as $key) {
if (!array_key_exists($key, $input)) continue;
$where[] = "`$key` = '" . mysqli_escape_string($input[$key]) . "'";
}
if ($where) {
$query = 'SELECT ... FROM `...` WHERE ' . join(' AND ', $where);
// etc...
} else {
return false;
}
}
I would use a class instead of multiple functions
class Student
{
public static function byName($name)
{
// ...
}
public static function byId($id)
{
// ...
}
}
$student = Student::byName('joe');
This would allow it to be much cleaner and more extendible, as you can put common logic in protected static methods in the class.
If you want to do multiples you can do some chaining which is a little more complicated.
I've mocked up a quick ideone which you can reverse engineer:
http://ideone.com/duafK4

MySQL PDO won't work with integers

I Have a basic function that looks like this:
public function query($query, $params = []) {
$statement = $this->db->prepare($query);
// Bind parameters based on value's type
foreach ($params as $key => $value) {
if(is_int($value)) {
$statement->bindParam($key + 1, $value, PDO::PARAM_INT);
} else {
$statement->bindParam($key + 1, $value, PDO::PARAM_STR);
}
}
$statement->execute();
return $statement;
}
For whatever reason, when I run something like this:
public static function photosByTag($tag, $user = null) {
$db = new DBConnection();
$query = "SELECT * FROM photos JOIN tags ON tags.photo = photos.pid WHERE tag LIKE ? AND owner = ?";
$params = [$tag, $user];
$result = $db->query($query, $params);
return $result->fetchAll();
}
photosByTag('city', 1)
It doesn't work. If I replace the AND owner = ? with AND owner = 1 it works fine. Something is wrong when binding integers as params, but I don't know what or why.
The problem isn't the bind, it is the loop. If you look at the manual, the second parameter for bindParam (&$variable) requires a reference. Your loop destroys that reference once it reassigns $value. The solution would be to use $params[$key] instead of $value in the bindParam()
Seems kind of redundant to do it this way when you can just use the execute() statement to bind the parameters.
$statement->execute($params);
Just let PDO handle how it assigns the variables. All your doing is checking what is submitted and then choosing the type, you're not enforcing a type, so it is probably similar to what PDO::execute does as is.

How make a Dynamic bindValue()?

Okay I have a function called sendQuery which sends your query.
I know how to do it with BindParams, but I can't really think of a way to make it work with bind values inside a execute.
This is the code:
public function sendQuery($query, array $value, $do_fetch)
{
$query_process = $this->db->prepare($query);
if(!$query_process->execute($binds))
{
throw new excpetion ("An error has occured!");
}
$this->insert = $this->db->lastInsertId();
$this->row_count = $query_process->rowCount();
if($fetch == true)
{
return $query_process->fetchAll();
}
}
As you see, it executes with $binds,
Works like (WHERE user = ?), but I want to send queries like this:
(WHERE user = :user) instead of a ' ? ', and multiple of them.
How do I do so?
You have to do exactly the same.
Just get rid of useless code and use consistent variable naming
public function sendQuery($query, array $binds, $do_fetch)
{
$stm = $this->db->prepare($query);
$stm->execute($binds);
$this->insert = $this->db->lastInsertId();
$this->row_count = $stm->rowCount();
if($do_fetch)
{
return $stm->fetchAll();
}
}
$sql = "SELECT * FROM t WHERE c1=:name AND c2=:age";
$param = array ("name" => $name,"age" => $age);
$data = $db->sendQuery($sql, $data, 1);
However, instead of just single function I would create a set of them:
query() to run non-select queries
getOne() preforms select and returns scalar value
getRow() returns a row
getAll returns all rows
it could be extremely handy

returning an array in Mysqli prepared statement

I have function in my database class that returns the id of the people that are from the a specific country such as spain. But for some reason I only get one value, but there are many people with the same country. here is the function:
Class DbAb {
private $db;
public function sameCountry($country) {
$query = "SELECT id FROM users WHERE country = ? ";
$stmt = $this->db->prepare($query);
$stmt->bind_param("s", $country);
if ($stmt->execute()) {
$stmt->bind_result($sameCountry);
$stmt->fetch();
return $sameCountry;
}
return false;
}
}
$sameC = new DbAb();
$samePeople = $sameC->samecountry("spain");
print_r($samePeople);
Does anyone know how to return an array of results? I have tried to define the variable as an array but still doesn't work...
The bind_result($var) + fetch() inserts a single row into the $var variable.
If you want to return an array of ids from your method, you need to first create an empty array, then for each row, insert into it.
eg. replace this:
$stmt->bind_result($sameCountry);
$stmt->fetch();
return $sameCountry;
with this:
$arr = array();
$stmt->bind_result($id);
while ( $stmt->fetch() ) {
$arr[] = $id;
}
return $arr;

Codeigniter issue with escaping values when passing array to query with "in" in the where clause

I have the function below in my model for a codeigniter project, and the variable $id is an array and for example, contains (1,2,3). Now that i'm revisiting it, I think that i'm not actually escaping my array $id. I think I would have to change the line
$this->db->escape($id)
to
$id = $this->db->escape($id)
If I do that, then it puts single quotes around every element in the array and treats it as one long string like this: '(1,2,3)'.
Can someone confirm that I am not actually escaping my variable and either suggest a solution or let me know if this is a bug within the codeigniter framework?
function get_ratings($id)
{
$this->db->escape($id); // had to manually escape the variable since it's being used in an "in" in the where clause.
$sql = "select * from toys t
where t.toy_id in ($id)";
$query = $this->db->query($sql, $id);
if($query->num_rows() > 0)
{
return $query->result_array();
}
else
{
return false;
}
}
You may be interested in using the CI Active Record class:
Beyond simplicity, a major benefit to using the Active Record features is that it allows you to create database independent applications, since the query syntax is generated by each database adapter. It also allows for safer queries, since the values are escaped automatically by the system.
Your rewritten query would look like this (assuming $id is an array):
$this->db->where_in('toy_id', $id)->get('toys');
Aside: I will admit I am a bit confused, as it looks like $ids would be a more appropriate variable name, and the way you are using it in the query, I would assume it is a string...
If active record is not your thing, you may also find Query Bindings to be useful:
The secondary benefit of using binds is that the values are automatically escaped, producing safer queries. You don't have to remember to manually escape data; the engine does it automatically for you.
EDIT: Looking back on this later, it looks like this is what you're trying to do. In that case, try replacing:
$sql = "select * from toys t where t.toy_id in ($id)";
With:
$sql = "select * from toys t where t.toy_id in (?)";
And pass $id as the second argument to query(), but as a comma separated string (implode(',', $id) if $id is indeed an array).
Otherwise you may want to use $this->db->escape_str().
$this->db->escape_str() This function escapes the data passed to it, regardless of type.
Here is an excerpt from the source code of the mysql driver to maybe put your mind at ease.
function escape_str($str, $like = FALSE)
{
if (is_array($str))
{
foreach ($str as $key => $val)
{
$str[$key] = $this->escape_str($val, $like);
}
return $str;
}
// continued...
It loops through arrays and escapes their values.
It does indeed seem that $this->db->escape is not going to work for arrays.
$this->db->escape() This function determines the data type so that it can escape only string data.
Here is the source:
function escape($str)
{
if (is_string($str))
{
$str = "'".$this->escape_str($str)."'";
}
elseif (is_bool($str))
{
$str = ($str === FALSE) ? 0 : 1;
}
elseif (is_null($str))
{
$str = 'NULL';
}
return $str;
}
Looks like it ignores arrays.
Anyways, hope you find a solution that works for you. My vote is for Active Record.
What you want to do is escape the individual values in the array. So you can use array_map on the array first.
$id = array_map('some_escape_function', $id);
See: http://php.net/manual/en/function.array-map.php
Then you can do:
$in = join(",",$id);
Your SQL would then be:
WHERE t.toy_id in ($in)
Which gives you:
WHERE t.toy_id in ('1','2','3')
You could try something like this:
$sql = 'select * from toys t where t.toy_id in ('.
join(',',array_map(function($i) {
return $this->db->escape($i);
}, $id)).');';
*Disclaimer: I'm not where I can access my PHP/MySQL server right now, so I haven't validated this. Some modification and/or tweakage may be necessary.
Here's the solution I'm using for this, with CI 2.1.2:
1) Copy /system/database/DB.php to application/database/DB.php, and around line 123, make it look like:
...
if ( ! isset($active_record) OR $active_record == TRUE)
{
require_once(BASEPATH.'database/DB_active_rec.php');
require_once(APPPATH.'database/MY_DB_active_rec' . EXT);
if ( ! class_exists('CI_DB'))
{
eval('class CI_DB extends MY_DB_active_record { }');
}
}
...
2) Create MY_Loader.php in application/core:
class MY_Loader extends CI_Loader
{
function __construct()
{
parent::__construct();
log_message('debug', 'MY Loader Class Initialized');
}
public function database($params = '', $return = FALSE, $active_record = NULL) {
// Grab the super object
$CI = & get_instance();
// Do we even need to load the database class?
if (class_exists('CI_DB') AND $return == FALSE AND $active_record == NULL AND isset($CI->db) AND is_object($CI->db)) {
return FALSE;
}
//require_once(BASEPATH . 'database/DB.php');
require_once(APPPATH . 'database/DB' . EXT);
if ($return === TRUE) {
return DB($params, $active_record);
}
// Initialize the db variable. Needed to prevent
// reference errors with some configurations
$CI->db = '';
// Load the DB class
$CI->db = & DB($params, $active_record);
}
}
3) Create application/database/MY_DB_active_rec.php:
class MY_DB_active_record extends CI_DB_active_record {
public function __construct($params)
{
parent::__construct($params);
log_message('debug', 'MY Active Record Database Driver Class Initialized');
}
private function _array_escape(&$str)
{
$str = "'" . $this->escape_str($str) . "'";
}
function escape($str)
{
if (is_array($str))
{
array_walk($str, array($this, '_array_escape'));
return implode(',', $str);
}
elseif (is_string($str))
{
$this->_array_escape($str);
}
elseif (is_bool($str))
{
$str = ($str === FALSE) ? 0 : 1;
}
elseif (is_null($str))
{
$str = 'NULL';
}
return $str;
}
}
Then you just pass in an array of values:
$in_data = array(1, 2, 3);
$this->db->query('SELECT * FROM table WHERE id IN(?)', array($in_data));
It's not pretty, but it seems to do the trick!
Code Igniter v3 now automaticly escapes array values :
http://www.codeigniter.com/userguide3/database/queries.html
Query Bindings
Bindings enable you to simplify your query syntax by letting the system put the queries together for you. Consider the following example:
$sql = "SELECT * FROM some_table WHERE id = ? AND status = ? AND author = ?";
$this->db->query($sql, array(3, 'live', 'Rick'));
The question marks in the query are automatically replaced with the >values in the array in the second parameter of the query function.
Binding also work with arrays, which will be transformed to IN sets:
$sql = "SELECT * FROM some_table WHERE id IN ? AND status = ? AND author = ?";
$this->db->query($sql, array(array(3, 6), 'live', 'Rick'));
The resulting query will be:
SELECT * FROM some_table WHERE id IN (3,6) AND status = 'live' AND author = 'Rick'
The secondary benefit of using binds is that the values are automatically escaped, producing safer queries. You don’t have to remember to manually escape data; the engine does it automatically for you.
To bind them you can do the following:
$queryParams = [];
// to add the appropriate amount of bindings ?
$idBindings = str_replace(' ', ',', trim(str_repeat("(?) ", count($ids))));
// add each id with int validation
foreach ($ids as $id) {
if(is_int(intVal($id)) == TRUE){
array_push($queryParams, intVal($id));
}
}
// the other option commented out below is to merge without checking -
// note: sometimes values look like numeric values but are actually strings
//queryParams = array_merge($queryParams, $ids);
$sql = "select * from toys t where t.toy_id in ('. $idBindings .')";
$query = $this->db->query($sql, $queryParams );

Categories