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
Related
The problem is not exactly passing arrays to objects per se, but distributing the initial values acquired from the database correctly in their related objects. Here's the situation. I have a forum member class and I need to pass only the values relevant to that class and also make it so it would assume certain array keys are one and the same, specifically the user ID key.
Example of a member class:
class Member {
private $id;
private $username;
public function __construct($row) {
foreach($row as $key => $value) {
$this->$key = $value;
}
public function getUserURL() {
return '' . $this->username . '';
}
}
Now imagine there is a posts table too and I need to get both post and user/author data from that:
$res = $db->query("SELECT posts.id, posts.author_id, users.username FROM posts JOIN users ON author_id = users.id");
$row = $res->fetch_assoc();
And now I need to inject the user data into the member class for instantiation:
$author = new Member($row);
And there goes the first problem. I'm not really injecting only the author data but also post data into it. This could be solved in a number of dubious ways. One of them is injecting the relevant values separately instead of entire array like this:
$author = new Member($row["author_id"], $row["username"], ...);
But this isn't very convenient as there could be more to author values than just these two, plus they can vary in number and position, which means I'd have to remember their positions and sometimes fill in the gaps with empty values or NULLs.
Of course, I could just ignore the fact I'm passing foreign data along, memory is cheap, but you may have noticed that there is still one more problem on top of it, which is the user ID key (author_id!= id). Depending on a page/table it can be "id", "uid", "author_id", "from_id", etc. Using an alias for user/author ID in the queries isn't an option either, because sometimes I need those foreign keys to be exactly what they are as they provide more leverage to the code logic.
A potential solution to both issues could be the following:
$author = new Member($row["author_id"]);
$author->getUserURL($row["username"]);
This way I don't have to worry about either foreign data or varying user/author ID keys as all are strictly defined and just once. However, the problem with this scenario is that it makes the member class hardly useful now that most of it has to be done manually. Passing values to each method for one thing. And if previously it would use existing object properties to concoct the needed result, now they mostly just return what's already in the array. What I really want is just inject an array and do something as simple as this:
$author->getUserURL();
$author->getUserAvatar();
$author->getUserHomepage();
...etc. But it seems unachievable unless two conditions are met: 1) Fixed user ID key, 2) Strictly user data. It obviously works on profile and members pages (no foreign data), but not anywhere else. Of course, I could ignore all that foreign data in object and just use array_key_exists() to check for all possible variants of author/user ID like this:
public function __construct($row) {
if(array_key_exists("author_id", $row)) {
$this->id = $row["author_id"];
}
if(array_key_exists("from_id", $row)) {
$this->id = $row["from_id"];
}
...
}
...but this is just not elegant enough. I need a more refined approach and I can't seem to come up with one. It appears that to get it right I have to at least partially break the class or somehow split the array into posts and user data, but I don't see how this could be done without too much work. It's at times like this that I just want to run two DB queries to get two separate arrays of data out of the box, which could solve everything, but everyone knows that that, too, is a bad practice.
So, has anyone got better ideas? I do realize that this isn't the best of questions as it's mostly about efficiency and not "how", but it's small things like this that could lead to bad coding in the long run and I'm still fresh to OOP.
I've been working on a PHP project for about 6 month where I'm using a multitier architecture (Data Access Layer , Business Logic Layer, ...).
One of the page of my website has a really long loading time, so I investigated and I realised it was the function from a BLL file that took a longer time to process.
Here's a example of a function in my DAL (here called DAL_Releve) files :
public static function getReleveByAffectationID($id){
$conn = MgtConnexion::getConnexion();
$sql = "SELECT *"
. "FROM releve "
. "WHERE Affectation_ID = :id "
. "AND Date_Releve >= :date ;";
$req = $conn->prepare($sql);
$req->bindValue(":id", $id);
$req->bindValue(":date", $_SESSION["year"]-1 ."-01-01");
$req->execute();
MgtConnexion::closeConnexion($conn);
return $req;
}
Here's a example of a function in my BLL (here called MgtReleve) files :
public static function getReleveByAffectationID($id){
$req = DAL_Releve::getReleveByAffectationID($id);
$releveArray = array();
while($row = $req->fetch(PDO::FETCH_ASSOC)){
$releveArray[] = new releve($row);
}
return $releveArray;
}
As you can see, I return the PDOStatement object $req from the DAL and then fetch it in the BLL.
My problem is here : In my interface file, I'm looping through a array of Affectation and each of them have Releves (so each Releves have an attribute Affectation_ID), like this:
$lesAff = MgtAffectation::getAllAffectation();
//This doesn't return an array of objects but just an Array of arrays like : $array[<number>]["key"] = value;
foreach ($lesAff as $affect){
$lesReleves = MgtReleve::getReleveByAffectationID($affect["Affectation_ID"]);
foreach ($lesReleves as $rel){
//DO STUFF HERE
}
}
FYI : The page loads in 6.622 seconds with the code above and in 0.014 seconds if I remove everything inside the first foreach (except for html code which is not showed here).
Now I'm starting to question my understanding of the multitiers architecture :
If I decide not to fetch $req in my BLL file but in my interface (to loop only once in my queries results instead of twice), what's the purpose of the BLL then if only to return the PDOStatement object? Is there another way to return the data with more efficiency ?
There are many areas of optimization:
Technological optimization: instead of connecting to the database every time you are running a query, you should connect only once and then use this connection through the entire application. I wrote an article on the common errors of Database wrappers, you may find it interesting to read, and especially the part on the database connection.
Architectural optimization. It seems that you could benefit from lazy loading when instead of selecting database rows one by one you could do it in batches. Check this answer on how to run a PDO query with multiple ids at once. So it will take you to run only 68 queries instead of 1200
Database optimization. Make sure that all queries involved are running fast.
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.
}
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 ...
The thing is that you have classes and then you have the database data. When you create an object how do you set the objects properties to contain the data in the database ?
I saw something like this and I'm wondering if this is really the best way to do it. I'm sure this is a fairly common issue, but I don't know what are the most accepted solutions on how to handle it.
In this example when the object is created you pass an id as a parameter and then you run a query to the database with the id and you assing the returned values to the object properties. I don't have much PHP experience and haven't seen this used much.
Is this an acceptable way to achieve this purpose ? Is there a better or more accepted way ?
public function __construct($id = null){
if($id != null){
$sql = "SELECT *
FROM users
WHERE user_id = $id";
$res = Db::returnRow($sql);
// $res contains an associative array with database columns and values
if($res){
$this->user_id = $res['user_id'];
$this->user_name = $res['user_name'];
//and so on...
}
}
}
Could somebody provide some sample code or pseudocode to illustrate what is the correct way to do this ?
It could be an acceptable way for a homework maybe. But architecturaly it is not.
Your class that is representing your business data (a user in your example) must be loosely coupled with your database access logic. In the end the PHP class acting as a user should not be aware that the data come from a database, a file or any other resource. Following that you will be able to reuse your user php class in other projects without having to change anything to it! If you have your data access logic inside it you are stuck.
Conclusion: I would suggest to read some resources on Design Pattern (in your situation take a look at DAO pattern) ;) Hint: the one from Head First series is extremely accessible and enjoyable.
You could create a function to do this for you automatically, by looping over the associative array's key/value pairs. Or you could look into using an ORM library.
Yes, you can semi-automate this by having a parent class all objects inherit from. On load, it queries, "SHOW FIELDS FROM [my tablename]" and populates an associative array with the names. If an id has been passed in, it looks for a valid object in that table with that id and assigns the values to the array.
Side note: don't pass your id directly into your query like that. Parametize the sql and wrap a function around any user input to sanitize it.
If it's mysql, you can just do:
$obj = mysql_fetch_object($query);
PDO the ability to use arbitrary classes as the target for a fetch, but beware that they assign the variable data before running the constructor:
$pdo->query($stmt, PDO::FETCH_CLASS, "MyClass", array('foo'=>'bar'));
...where the final parameter contains arguments for your class constructor.