Yii sql query bindings, with params, do not work properly - php

I'm new to Yii and everything seems good, but the problem is, when I`m using the binding params, like (DAO stile):
$command = $this->conn->createCommand($sql);
$command->bindColumn("title", "test_title");
$result = $command->query();
or (Active Record):
$row = Movies::model()->find("m_id=:m_id", array(":m_id"=>27));
or
$row = Movies::model()->findByPk(24);
I've tried everything:
1) added a config param to mysql config. in main.php - 'enableParamLogging' => true
2) changed strings from ' to "
3) added another param just in case of mysql versions - 'emulatePrepare'=>true
Nothing works for me.
I thought that the problem is in params, but bindColumn method doesn't use it, so my presumption is that some module of Yii hasn't been include in config file or something like that.
My model looks like this (created in /models dir):
class Movies extends CActiveRecord {
public static function model($className = __CLASS__) {
parent::model($className);
}
}
Just for everybody to avoid unnecessary questions: the database is configured properly in main.php conf file, there is a table movies, there is a PKs 24 and 27 also.
All native SQL works fine, except using in DAO special methods to bind some params and if in AR using findByPk or find. I hope that this is clear, guys don't bother me with obvious simple technical possibilities, that I can (as U presume) did wrong.
PS Another helpful info - when calling
$command->bindColumn("title", "test_title");
FW's throwing an Exception - CDbCommand and its behaviors do not have a method or closure named "bindColumn". So, as mentioned above, I think that Yii don't see those special methods and this is certain. How can I repair it?
Ok, why this code don't work either? There is no Exceptions, just blank page.
$sql = "SELECT title, year_made FROM movies WHERE year_made=':ym'";
$command = $this->conn->createCommand($sql);
$command->bindParam(":ym", "2012", PDO::PARAM_STR);
$result = $command->query();

The DAO binding isn't working because there is no bindColumn. You only have bindParam, that binds a variable to a column, or bindValue, that binds a value.
I don't know what's wrong with the AR query though.
Edit: Your DAO code should look like this:
$sql = "SELECT * FROM sometable WHERE title = :title";
$command = $this->conn->createCommand($sql);
$command->bindParam(":title", $tile_var);
$result = $command->query();

Have you generated movie model by Gii generator or you wrote it by yourself?
Edit:
As I know you have to add tableName
public function tableName() {
return 'movies';
}

You may have two separate issues. At least your DAO code (if that is what you actually have in your code) is binding wrong.
Binding needs to occur before the query is done (and it's done on the command object), not on the result object. See more info here: http://www.yiiframework.com/doc/guide/1.1/en/database.dao#binding-parameters
As far as your AR queries go, those could also be problematic. You can use findByAttributes instead of this line (although your line should work):
$row = Movies::model()->find("m_id=:m_id", array(":m_id"=>27));
The below should work if you have a standard id key. If you don't (and are using m_id, have you set your model's primaryKey() function up?
$row = Movies::model()->findByPk(24);
Also, you'll be getting a model object back, not a row if that changes how you are handling the results ...

Related

OOPHP PDO querying inside object & functions, regarding binding

Okay, so I have this code, works great for saving a video object that's passed through it. I put this together because someone said I shouldn't be doing queries directly within the Video class functions, I should have an external class devoted to interacting with the database for it.
class VideoHandler {
private $myObject;
function __construct($object) {
$bind = array();
$bind[':src'] = $object->src;
$bind[':title'] = $object->title;
$bind[':desc'] = $object->desc;
$bind[':cat_id'] = $object->cat_id;
$bind[':game_id'] = $object->game_id;
$this->myObject = $object;
}
function save($conn) {
$sql = "INSERT INTO videos (`src`, `title`, `desc`, `cat_id`, `game_id`) VALUES (:src, :title, :desc, cat_id, :game_id)";
$query = $conn->prepare($sql);
$query->execute($this->myObject);
return "Video Saved!";
}
}
So this is all fine and dandy for saving, but I want to be able to get related videos, get top videos, update the views on a video, etc.
So let's say hypothetically I want to add views to the video with something like this:
function updateViews($conn) {
$sql = "UPDATE videos SET views=(:views+1) WHERE id=:id";
$query = $conn->prepare($sql);
$query->execute($this->myObject);
}
It won't work because there's too many binded variables (I know I don't have views or id bounded, but that's irrelevant anyway).
Is there a better way to construct the VideoHandler to be able to do these multiple functions without repeating a lot of code? The two solutions I thought of seem wrong to me. I was thinking I could just have multiple bind arrays in the constructor for each function, and multiple attributes to store the arrays.
So $bind_save, $bind_views, $bind_related, etc. Where I only put in the parts of the object I'd use for the different queries in each array.
Either that or pass the object into the function itself and just bind everything inside there, but that doesn't seem right either.
It seems strange that I can't just add $bind[':id'] and $bind[':views'] into the array in the constructor and just use the code I posted above. Maybe I'm missing something, but why does every piece of the array have to be used in the PDO execute? How come I can't just have the full array and only use the ones that are being used in the $sql query?
Anyway, is there a better way to do it than the ways I mentioned? I feel like there must be.
Thanks

Escape SQL queries in PHP PostgreSQL

I have a website with lots of PHP files (really a lot...), which use the pg_query and pg_exec functions which do not
escape the apostrophe in Postgre SQL queries.
However, for security reasons and the ability to store names with
apostrophe in my database I want to add an escaping mechanism for my database input. A possible solution is to go
through every PHP file and change the pg_query and pg_exec to use pg_query_params but it is both time consuming
and error prone. A good idea would be to somehow override the pg_query and pg_exec to wrapper functions that would
do the escaping without having to change any PHP file but in this case I guess I will have to change PHP function
definitions and recompile it which is not very ideal.
So, the question is open and any ideas that would
allow to do what I want with minimum time consumption are very welcome.
You post no code but I guess you have this:
$name = "O'Brian";
$result = pg_query($conn, "SELECT id FROM customer WHERE name='{$name}'");
... and you'd need to have this:
$name = "O'Brian";
$result = pg_query_params($conn, 'SELECT id FROM customer WHERE name=$1', array($name));
... but you think the task will consume an unreasonable amount of time.
While it's certainly complex, what alternatives do you have? You cannot override pg_query() but it'd be extremely simple to search and replace it for my_pg_query(). And now what? Your custom function will just see strings:
SELECT id FROM customer WHERE name='O'Brian'
SELECT id FROM customer WHERE name='foo' OR '1'='1'
Even if you manage to implement a bug-free SQL parser:
It won't work reliably with invalid SQL.
It won't be able to determine whether the query is the product of intentional SQL injection.
Just take it easy and fix queries one by one. It'll take time but possibly not as much as you think. Your app will be increasingly better as you progress.
This is a perfect example of when a database layer and associated API will save you loads of time. A good solution would be to make a DB class as a singleton, which you can instantiate from anywhere in your app. A simple set of wrapper functions will allow you to make all queries to the DB go through one point, so you can then alter the way they work very easily. You can also change from one DB to another, or from one DB vendor to another without touching the rest of the app.
The problem you are having with escaping is properly solved by using the PDO interface, instead of functions like pg_query(), which makes escaping unnecessary. Seeing as you'll have to alter everywhere in your app that uses the DB, you may as well refactor to use this pattern at the same time as it'll be the same amount of work.
class db_wrapper {
// Singleton stuff
private $instance;
private function __construct() {
// Connect to DB and store connection somewhere
}
public static function get_db() {
if (isset($instance)) {
return $instance;
}
return $instance = new db_wrapper();
}
// Public API
public function query($sql, array $vars) {
// Use PDO to connect to database and execute query
}
}
// Other parts of your app look like this:
function do_something() {
$db = db_wrapper::get_db();
$sql = "SELECT * FROM table1 WHERE column = :name";
$params = array('name' => 'valuename');
$result = $db->query($sql, $params);
// Use $result for something.
}

Doctrine - how to get the SQL INSERT query, in the postSave() event?

I would like to get the exact SQL INSERT query that Doctrine generates when an object's save() method is called.
Preferably, I would like to get it in the postSave() event of the model and log it in a txt file.
For instance:
<?php
$user = new User(); // A Doctrine Model with timestampable behavior enabled
$user->first_name = 'Manny';
$user->last_name = 'Calavera';
$user->save();
?>
I want to get/log the following SQL query:
INSERT INTO user (first_name, last_name, created_at, updated_at) VALUES ('Manny', 'Calavera', '2010-08-03 12:00:00', '2010-08-03 12:00:00');
The background for needing this, is that I wish to mass-import later the data by parsing the txt file.
I don't think there is any easy way to do this since the behaviour of save() varies depending on a few different things (if you're inserting/updating).
If you had created a doctrine query object, then you can call the getSqlQuery() method like this:
$q = Doctrine_Query::create()
->select('u.id')
->from('User u');
echo $q->getSqlQuery();
but the save() is a method, not an object so this won't work. I think you'll have to hack in some code to detect whether you're inserting or updating and build a query to log on the fly and then use save().
I know this suggestion is not ideal because it is not logging 'exactly' what save() is doing but for the purposes you stated it should still work just as well.
Why don't you try to see how the symfony developers do it? Check their WebDebug Toolbar for Doctrine here. The WebDebug Toolbar outputs all query that you do on a page.
There's a hook on DoctrineEvent, and I think you can modify the code to do what you want. Check the getDoctrineEvents and getSqlLogs method.
Hope this helps. If you need further explanation, please write it in comment, I'll try my best to explain.
Take a look here: http://www.phpandstuff.com/articles/codeigniter-doctrine-scratch-day-8-hooks-profiling-dql and go to the section headed Profiling with Doctrine and Creating a Profiler Hook. Altough this is for the use with the CodeIgniter framework, it can be easy adopted to your own environment since the code has no dependencies to the framework.
You basically want to set up a Connection Profiler and let it write all queries to a file. I suggest appending all queries to the file to have a better "log"-like feeling. Don't get confused by many framework talk inside the articles. The examples work very well (with a little understanding and copy&pasting) in other scenarios.
you can use the profiler from the sfDoctrineDatabase class. Use the getQueryExecutionEvents to grab all queries.
$databaseManager = sfContext::getInstance()->getDatabaseManager();
if ($databaseManager) {
foreach ($databaseManager->getNames() as $name) {
$database = $databaseManager->getDatabase($name);
if ($database instanceof sfDoctrineDatabase && $profiler = $database->getProfiler()) {
foreach ($profiler->getQueryExecutionEvents() as $event) {
$conn = $event->getInvoker() instanceof Doctrine_Connection ? $event->getInvoker() : $event->getInvoker()->getConnection();
$params = sfDoctrineConnectionProfiler::fixParams($event->getParams());
$query = $event->getQuery() ;
foreach ($params as $param) {
$param = htmlspecialchars($param, ENT_QUOTES, sfConfig::get('sf_charset'));
$query = join(var_export(is_scalar($param) ? $param : (string) $param, true), explode('?', $query, 2));
}
// log the $query here, or use the symfony's logger
// sfContext::getInstance()->getLogger()->debug(sprintf('Query Run !! %s ', $query));
}
}
}
}
dont forget to join the query with the parameters (so it will replace ? with the values )
:D
You can convert you DQL query to SQL by this function: http://tokarchuk.ru/2010/12/dql-query-to-raw-sql/

PHP: how would I use this simple class?

I was looking around for more "correct" login/logout portions of code, and found this one:
http://snipplr.com/view/1079/auth/
I got two questions though that stop me from using it.
1: How would I instantiate the class, and use it in my script?(I know PHP but am just befuddled for some reason)
2: there's the following lines:
global $db;
$db->query("sql here...");
How on earth does that make a database object? I think maybe I should create an object like $db = mysql_connect(...) outside the script, and global is calling it from outside the class?
If I know how to call this class, others will seem like a breeze, this is really helpful to me!
That code is dangerous, you should not use it in it's current state.
It blindly trusts $_COOKIE and creates SQL queries using string concatenation, without properly escaping input. Malicious users can easily perform SQL injection on any application using that class.
The presence of the var keyword, the presence of a constructor using the same name as the class and the lack of access control keywords in front of the methods tells me that the code was written for PHP4, not PHP5. It will still run, though
Both of these are fixable problems. First, let's address your questions.
The code written by #Delan Azabani is a good example of how to use the class.
The code is confusing. At first, it looks like the code is using a real database object instead of the old, low-level functions provided by the mysql extension. But then it goes and calls functions from the mysql extension! It's probably someone's custom-written wrapper. The code isn't actually creating a database object, it's simply referencing the value of $db in the global scope, as you suspect.
You've mentioned that you're using the mysql extension. I urge you to reconsider and use PDO or mysqli instead. Not only will this class work with either (though with some changes to mitigate the glaring security hole), but your own code will be better off using the more modern, safer techniques used in PDO and mysqli.
Let's fix the login method, using PDO.
public function login($username, $password) {
global $db;
$sth = $db->prepare("SELECT user_id FROM users WHERE username = ? AND password = ?");
$sth->execute(array($username, $password));
if($sth->rowCount() == 1) {
$this->user_id = $sth->fetchColumn();
...
Let's go over what changed. First, we added query placeholders, those are the question marks. The prepare method returns a statement handle, which you're already probably familiar with. Next, we tell the prepared statement to run itself using the execute method, passing an array of variables. The variables will be automatically escaped for you and then inserted into the query in place of the placeholders.
After the query has run, we use rowCount to pull back the number of matching rows. We want exactly one. Because we're pulling back the first column of the first row, we then use fetchColumn to grab just that bit of data.
The rest of the login method is just fine.
The check method needs similar fixing up.
public function check($username, $password) {
global $db;
$sth = $db->prepare("SELECT user_id, password FROM users WHERE username = ?");
$sth->execute(array($username));
if($sth->rowCount() == 1) {
list($db_user_id, $db_password) = $sth->fetch(PDO::FETCH_NUM);
if(md5($db_password . $this->salt) == $password) {
$this->user_id = $db_user_id;
...
Once again, we prepare a query with placeholders, then execute with the actual variables. Again we want only one row, but this time we want everything in the row. We'll use PDO::FETCH_NUM to tell fetch that we want a numerically indexed array, then we'll take the two resulting entries in the array and stuff them in $db_user_id and $db_password, using them where the old code called mysql_result. PDO doesn't let you pick and choose different columns from the same row using fetchColumn.
If you wanted to use the mysqli extension instead, there's a bit more work to be done. I hope I've convinced you to use PDO instead, though!
The rest of the class is, eh, adequate. Make sure to adjust the $domain class variable at the top to match your actual domain name, it's used in the cookies.
global $db just allows the global database pointer $db to be accessed from inside the class method. You need to create the database by running mysql_connect and mysql_select_db. The entire class is in class Auth, so instantiate an Auth object before doing anything else (well, after connecting to the database, of course).
You can then call the methods inside your Auth object. For example, if a login form submits to login-post.php, you can have this code inside:
$m = new Auth();
if ($m -> login($_POST['u'], $_POST['p'])) {
header('Location: /');
exit;
} else {
header('Location: login?wrong=1');
exit;
}
Looks like the code you posted is just assuming you have a variable defined in your global scope called $db that looks like a wrapper class to the native PHP MySQL resource. So yes, you should create $db outside in the global scope (i.e. not inside a class), but it's expecting a custom class.
I did a little searching, and it looks like he's using THIS class: http://snipplr.com/view/27966/class-db/
It asks you to create an ini file called "crunksauce.ini" (I kid you not) in the same directory as the executing script, and contains configuration variables. The config file should like this:
database = <your database name>
host = <your db host>
user = <your db user>
pass = <your db password>
After creating the config file, you can create the db object like this:
$db = new Db();

Code coverage with phpunit; can't get to one place

In the xdebug code coverage, it shows the line "return false;" (below "!$r") as not covered by my tests. But, the $sql is basically hard-coded. How do I get coverage on that? Do I overwrite "$table" somehow? Or kill the database server for this part of the test?
I guess this is probably telling me I'm not writing my model very well, right? Because I can't test it well. How can I write this better?
Since this one line is not covered, the whole method is not covered and the reports are off.
I'm fairly new to phpunit. Thanks.
public function retrieve_all()
{
$table = $this->tablename();
$sql = "SELECT t.* FROM `{$table}` as t";
$r = dbq ( $sql, 'read' );
if(!$r)
{
return false;
}
$ret = array ();
while ( $rs = mysql_fetch_array ( $r, MYSQL_ASSOC ) )
{
$ret[] = $rs;
}
return $ret;
}
In theory, you should be better separating the model and all the database related code.
In exemple, in Zend Framework, the quickstart guide advise you to have :
your model classes
your data mappers, whose role is to "translate" the model into the database model.
your DAOs (or table data gateway) that do the direct access to the tables
This is a really interesting model, you should have a look at it, it enables you to really separate the model from the data, and thus to perform tests only onto the model part (and don't care about any database problem/question)
But in your code, I suggest you to perform a test where you have the dbq() function to return false (maybe having the db connexion to be impossible "on purpose"), in order to have full code coverage.
I often have these kind of situations, where testing all the "error cases" takes you too much time, so I give up having 100% code coverage.
I guess the function dbq() performs a database query. Just unplug your database connection and re-run the test.
The reason you have trouble testing is that you are using a global resource: your database connection. You would normally avoid this problem by providing your connection object to the test method (or class).

Categories