I create a sub object of "shortcut" methods such as return_all and other methods to run with PDO as a database object.
In short, all of my queries (be it insert, update, delete or select) goes through 2 different methods, the last one I call execute which looks like this:
/*
* execute_prepared_sql() will prepare SQL based on the users parameters:
*
*/
private function execute_prepared_sql() {
if ($this->prepare_sql() == true) {
try {
$this->stmt = $this->pdo->prepare($this->sanitized_sql);
$this->stmt->execute($this->bound_parameters);
} catch (PDOException $e) {
set_debug($e->getMessage(), true);
return false;
}
return true;
}
return false;
}
I then have methods which looks like this:
public function return_active_partners() {
if ($this->select(array("query_tables" => $this->table_name, "conditions" => array("AND" => array("partners.status" => 1))))) {
$this->stmt->setFetchMode(PDO::FETCH_ASSOC);
return $this->stmt->fetchAll();
}
}
The above method will then effectively return the results ($this->select() calls the execute_prepared_sql() method.
This all works perfectly fine, but I have this extremely annoying issue where at times (and really at random) I get a "SQLSTATE[HY093]: Invalid parameter number: no parameters were bound" error. It appears to happen a lot more when I do 2 queries after eachother (say I delete a row and then return a result set of the remaining rows)
What appears to happen is the bound parameters don't get removed during the execute_prepared_sql() call. I was under the impression that when execute() is called all bound parameters would be "reset" or cleared, but this appears to not be the case.
My question is, how can I clear any bound parameters a PDO statement may have stored to ensure I don't use the same parameters twice? Or is there something else you can see which I'm doing wrong which may be the issue?
Related
I have a function like this:
public function myfunc ($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8) {
// do something
}
See? My function gets 8 arguments. Yes it works as well, but you know, it's ugly kinda ..! Isn't there any better idea? For example passing just one array contains all argument. Is it possible? or even something similar.
I do it this way..
$params = [];
put things in params..
$params[] = $a;
$params[] = $b;
pass the array to function
myFunction($params);
The function accept array as arg like, definition:
public function myFunction($params = []){}
Pass something in, and var_dump to check for yourself...
OK, now I know that it's an SQL operation you're doing then the best approach would be an associative array (assuming PDO and prepared statements).
public function myFunc (array $data)
{
// Using 3 values for example!
$stmt = $this -> pdo -> prepare ("INSERT INTO TABLE thisTable (
col1,
col2,
col3
) VALUES (
:val1,
:val2,
:val3
)");
if (false !== $stmt execute ($data))
{
return $stmt -> rowCount ();
} else {
return 0;
}
}
You'd call it with an array with the correct parameters in it:
$success = (bool) $obj -> myFunc (["val1" => "First value to insert",
"val2" => "Second value to insert",
"val3" => "Third value to insert"]);
It depends whether myfunc belongs to an exposed api or not (i.e.: public)
If it is public, the signature must break when you update the underlying model (your insert query), otherwise errors will be committed on the client-side.
With an array, you're losely mapping your Model to your Application, and you'd just expect programmers to send you the right values. With a tight/restricting mapping, this kind of error cannot happen.
I think that if you're saving an item in the database, you actually need all these fields. It is unelegant indeed, but it's not an anti-pattern as none of them are optional. And even if one or two were, it would not be a concern.
What you could do to improve your api is either:
If there are situation where you need to pass only a few of the parameters (i.e. most are optional or depend on a particular scenario) then you could specialize the method into separate functions. But PHP not accepting function polymorphism is quite a pain in the neck for this kind of things; you'd have to name the methods differently.
public function myfunctosavedatainaparticularcase ($arg1, $arg2, $arg3, $arg4)
// do something
}
public function myfunctosavedatainanotherparticularcase ($arg5, $arg6, $arg7, $arg8)
// do something
}
Use an object model mapper. For example, suppose you're saving a User data. You'd just pass a User object to the method:
public function myfunc (User $user)
// map fields to the User signature.
}
This would be acceptable if you're in control of the User class since you'd have to change it to reflect model changes.
Use an ORM to handle this for you. You'd only have to update the xml specifications of the schema after you decide to change the database model, all necessary changes would be propagated to the application automatically. Of course the objects definition would change but that is inevitable.
So, I was trying to implement this answer for my other question on the same subject... and it keeps givin me the exceeded time error. Any clues?
This is on my product model. It inherits from Eloquent.
public function newQuery($excludeDeleted = true)
{
$user_permission = Auth::user()->permissions;
if( $user_permission->master )
return parent::newQuery();
else if( $user_permission->web_service )
{
$allowed_ids = array();
foreach( $user_permission->allowed_products()->get() as $allowed)
$allowed_ids[] = $allowed->id;
return parent::newQuery()->whereIn('id', $allowed_ids);
}
return parent::newQuery();
}
If the user is master there is no need to query scope on the request. But, if it isn't then I need to filter by the logged user's permissions.
UPDATE:
I tried the following code in a controller and it works alright:
$user_permission = Auth::user()->permissions;
echo "<PRE>"; print_r($user_permission->allowed_products()->get()); exit;
UPDATE 2:
Guys, I just found out that the problem was in this peace of code:
$allowed = Auth::user()->permissions()->first()->allowed_products()->get()->list('id');
It somehow give me an Maximum execution time of 30 seconds exceeded. If I put the exact same code in a controller, works like a charm, though! I also tried to put it in a scope, also worked. This it's really grinding my gears!
Elloquent has a function called newQuery. Controller does not. When you implement this function in a Model you are overriding the one in Elloquent. If you then invoke Elloquent methods that need a new query for your model before they can return, like ->allowed_products()->get(). Then you are calling your own newQuery() method recursively. Since the user permissions have not changed, this results in infinite recursion. The only outcome can be a timeout because it will keep on trying to determine a filtered product list which causes your newQuery() method to be called, which tries to determine the filtered product list before returning the query, and so on.
When you put the method into a Controller, it is not overriding the Elloquent newQuery method so there is no infinite recursion when trying to get the allowed_product list.
It would be more efficient to apply the filter to the product query based on whether the id is in the user's allowed_products() list using ->whereExists() and build up the same query as allowed_products() except now add condition that id from the query you are filtering is equal to the product id in the allowed products query. That way the filtering is done in the database instead of PHP and all is done in the same query so there is no recursion.
I don't see how your update code works. Illuminate\Database\Eloquent\Collection does not have any magic methods to call the relation functions, you should get an undefined method error trying to do that.
Can you try something like
public function newQuery($excludeDeleted = true)
{
// Returns `Illuminate\Database\Eloquent\Collection`
$user_permission = Auth::user()->permissions;
if ($user_permission->master)
{
return parent::newQuery();
}
else if ($user_permission->web_service)
{
// If here you was to call $user_permission->allowed_products()->get() not much is going to happen, besides probably getting an undefined method error.
$allowed_ids = Auth::user()->permissions()->allowed_products()->get()->lists('id');
return parent::newQuery()->whereIn('id', $allowed_ids);
}
return parent::newQuery();
}
Update: as per comments below I believe the problem is due to newQuery() being called multiple times as the code works just fine when called once in a controller. When this is applied to every query there is no need to collect all the IDs over and over again (assuming they're not going to change each time you call for them). Something such as the below will allow you to store these and only process them once per request rather than every time a query is run.
private $allowed_ids_cache = null;
public function newQuery($excludeDeleted = true)
{
$user_permission = Auth::user()->permissions;
if ($user_permission->master)
{
return parent::newQuery();
}
else if ($user_permission->web_service)
{
if ($this->allowed_ids_cache === null)
{
$this->allowed_ids_cache = Auth::user()->permissions()->allowed_products()->get()->lists('id');
}
return parent::newQuery()->whereIn('id', $this->allowed_ids_cache);
}
return parent::newQuery();
}
Sometimes depending on which user type if viewing my page, I need to add in a JOIN, or even just limit the results. Is there a cleaner way of going about it? Should I have separate statements for each type of request instead? What is more "proper"?
Here is what my code ends up looking like:
// Prepare statement
$stmt = $this->db->prepare('
SELECT *
FROM Documents
LEFT JOIN Notes ON ID = D_ID
'.($user_id ? "INNER JOIN Users ON UID = ID AND UID = :userid" : '')."
". ($limit ? 'LIMIT :offset, :limit' : '')
);
// Bind optional paramaters
if ($user_id) $stmt->bindParam(':userid', $user_id, DB::PARAM_INT);
if ($limit)
{
$stmt->bindParam(':offset', $limit[0], DB::PARAM_INT);
$stmt->bindParam(':limit', $limit[1], DB::PARAM_INT);
}
Maybe just wrap the insert strings into their own methods for clarity, like getUserInsertString($user_id), and try to make your quote use more consistent.
Also, are you testing whether $user_id and $limit are defined just by going if ($user_id)? If so, if you had error reporting turned to all, you would get a bunch of undefined variable warnings. You may want to consider using if (isset($user_id)) instead.
I'd create separate (protected) functions, those return a prepared statement that only needs to be executed.
/**
* #returns PDOStatement
*/
protected function prepareStatementForCase1(PDO $dbObject,Object $dataToBind){...}
/**
* #returns PDOStatement
*/
protected function prepareStatementForCase2(PDO $dbObject,Object $dataToBind){...}
Then, I would decide outside, which one has to be called.
You can rebuild, maintain and read the code more easily.
Example:
class Document{
protected $dbObject;
public function __construct(PDO $dbObject){
$this->dbObject=$dbObject;
}
public function doQuery($paramOne,$paramTwo,...){
$logicalFormulaOne=...; // logical expression here with parameters
$logicalFormulaTwo=...; // logical expression here with parameters
if($logicalForumlaOne){
$dbStatement=$this->prepareStatementForCase1($dataToBind);
}else if($logicalFormuleTwo){
$dbStatement=$this->prepareStatementForCase2($dataToBind);
}
$dbResult=$dbStatement->execute();
}
protected function prepareStatementForCase1(Object $dataToBind){
$dbStatement=$this->dbObject->prepare("query string");
$dbStatement->bindParam(...);
return $dbStatement;
}
}
But I would not advice this, when your PDOResult object represents different type of database tuples, or when you return more rows in one of the cases.
What I usually do is that I create a class which represents (in your example) a Document. Only one. I can insert, delete, select, modify by its fields, and handle one item. When I need to (for example) fetch more of them, I create a new class, e.g. DocumentList, which handles a collection of documents. This class would give me an array of Document objects when it fetches more of them.
I've come across a difficult to track bug, but I'm not sure what is causing this bug. I have a class Property and I want to fetch one entry form the table property with a method named loadProperty(). This method is part from a singleton class (Registry).
public function loadProperty() {
$this->load('model', 'property');
$sth = $this->dbh->prepare("SELECT * FROM property WHERE subdomain = :subdomain LIMIT 1");
$sth->setFetchMode(PDO::FETCH_CLASS, 'property');
$data = array('subdomain' => $this->router->subdomain);
try {
$sth->execute($data);
if ($sth->rowCount() == 1) {
$this->property = $sth->fetch();
} else {
$this->property = null;
}
} catch (PDOException $exception) {
// HANDLING EXCEPTION
}
}
The first line of the method loads the model. It just looks for the class file and requires it with require_once.
All this works fine when I use PDO::FETCH_BOTH instead of PDO::FETCH_CLASS. My guess is that PDO is doing some things behind the scenes that I am not aware of, but that cause my loadProperty method to be called an infinite number of times.
What am I overlooking here?
The infinite loop turned out to be caused by an error of my own - who'd've thought. By setting PDO's fetch mode to PDO::FETCH_CLASS, PDO attempts to instantiate an instance of Property, which one might expect. However, the model creates a reference to the Registry class in its constructor method, causing the constructor of the Registry class to be invoked which includes the loadProperty method shown above. The result is an infinite loop.
im using codeigniter 2.0.2 and this is from its userguide
$data = array(
'title' => $title,
'name' => $name,
'date' => $date
);
$this->db->where('id', $id);
$this->db->update('mytable', $data);
my question is once this executed how do you find its executed correctly or not?
The update function returns a value:
$result = $this->db->update('mytable', $data);
Check that value for either being TRUE (success) or FALSE (failure). update runs query internally and then returns the return value of query (Ref):
The query() function returns a database result object when "read" type queries are run, which you can use to show your results. When "write" type queries are run it simply returns TRUE or FALSE depending on success or failure.
Use
$this->db->affected_rows()
to see how many rows have been affected on write type queries (update, insert, etc...)
http://codeigniter.com/user_guide/database/helpers.html
Both answers was valid. You just have to use each one depending on the case. If you are just checking if the query was executed use this method:
$result = $this->db->update('mytable', $data);
if you really want the number of rows affected the second method is better:
$this->db->affected_rows()
however I always use the second method. The update query is a good example why. A query can be successful and still there was nothing updated on the database because the value that you were trying to update was actually equal to the value you are sending in the query.
This would be a false positive. And the affected rows would be 0.
I hope it helped =)
When developing CodeIgniter model methods, I find that I consistently return desirable values depending on the type of database write that is executed. It is often important to differentiate between a query that has run successfully versus a query that has actually changed a record.
For an update or delete query, I'll return the number of affected rows -- this will be most helpful to controller methods that call it. If you are performing logging (to keep track of change history), then ONLY log something if there is a change to the row; otherwise you are unnecessarily bloating your change history logs.
public function update(int $id, array $newData) :int
{
$oldData = $this->db->get_where('mytable', ['id' => $id])->row_array();
if ($this->db->update('mytable', $newData, ['id' => $id])) {
$affectedRows = $this->db->affected_rows();
if ($affectedRows) {
$this->Log->mytableUpdate($id, $newData, $oldData);
}
return $affectedRows;
}
return 0;
}
For insert queries, I always return the auto-incremented id of the newly inserted row via insert_id().
If using the PDO driver with PostgreSQL, or using the Interbase driver, this function requires a $name parameter, which specifies the appropriate sequence to check for the insert id.
public function insert(array $newData) :int
{
if ($this->db->insert('mytable', $newData)) {
$newId = $this->db->insert_id(); // or insert_id('mytable')
$this->Log->mytableInsert($newId, $newData);
return $newId;
}
return 0;
}
Having consistent return types in your model methods will make your project easier to develop and maintain. The script that calls these model methods will be able to quickly assess the outcome by making a "falsey" check.