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.
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.
Since the DataMapper is supposed to be for the exchange/transfer of data between objects and the relational database I would get a user like this
$user = $this->entityFactory->build('User');
$userMapper = $this->mapperFactory->build('User');
$user->setId(43);
$userMapper->fetch($user);
That works fine because I can create the User object outside of the mapper and pass it in but what do I do when I am getting a collection/list of objects?
Creating the empty objects outside of the mapper first just does not seem correct and would surely cause some problems so what is the best way to do it?
Thanks.
I don't know if this question is still in your mind, however let me give you an answer to this. In principle the first step is the same
$userCollection = $this->entityFactory->build('UserCollection');
$userCollectionMapper = $this->mapperFactory->build('UserCollection');
$user = $this->entityFactory->build('User');
$user->setId(43);
$userCollection->add($user);
$userCollectionMapper->fetch($userCollection);
So the userObject would function here as an searchObject for the collectionMapper (like teresko proposed in an older thread). Your CollectionMapper would retrieve the data from the database and i prefer to use something like
//returns new User object
$newUser = $userCollection->newUser();
//populate user object with DB entry
$userCollectionMapper->populate($newUser,$DBresponse);
//add to collection
$userCollection->add($newUser);
Of course there would be a loop before that looping through the found lines in the database and you would have to clear the list of user objects before adding the results.
So this is the way i would deal with the problem. Hope it helps.
In my DB layer class I have a method that takes a select sql query as param:
public function select($sqlQuery) {
$stmt = $this->pdo->prepare($sqlQuery);
$stmt->execute();
$ret = $stmt->fetch(PDO::FETCH_ASSOC);
return $ret;
}
I want to be able to return the fetched data to my model, and then loop through it in my view class, without using any PDO in the view.
How should this be done the right way?
I want to be able to reach the table rows as $row['testCol'] etc.
Call fetchAll() instead of fetch(), which will return a full multidimensional array.
public function select($sqlQuery) {
$stmt = $this->pdo->prepare($sqlQuery);
$stmt->execute();
$ret = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $ret;
}
$results = $your_object->select("SELECT * FROM yourtable");
// It's a 2D array!
var_dump($results);
Model is a layer. Not a class or object.
And, if you write your code by adhering to SRP, the model layer would contain several different types of structures. As minimum you would have separate classes for domain business logic and storage abstraction.
What you have here is complete mess. Not only you lumping both domain logic and storage mechanism in same class (and pretend that it is "model"), but you are also exposing SQL directly to presentation layer.
If you were trying to build a proper model layer, the current view would request information from service instance (which would be the type of structures, that one would use to separate domain business logic from views and controller) and acquire the data it need.
The service would instantiate the proper domain object(s) and, based on state, previously set by controller, retrieve information into them using some form of storage abstraction (either data mapper, repository, unit of work, dao or some similar structure). Then, after performing required data manipulations on the filled domain object(s), it would return data to the view.
View, based on received information and previous signals from controller, would decide what sort of response should be generated. If the indicated for of response is HTML, it would use one or (usually) multiple templates to compose the output. Thought it might as well send just a HTTP location header, to trigger a redirect.
For further reading I would recommend this answer.
The point is: you currently have simplified the concepts of MVC to the point where even separation between presentation and model layer has been broken.
You could easily return $stmt. It allows you to iterate over it and is accessible like PDO::FETCH_ASSOC:
$rows = $model->select($sqlQuery);
...
foreach($rows as $row)
{
echo $row['testCol'], "\n";
}
Inside your function it's like:
public function select($sqlQuery) {
$stmt = $this->pdo->prepare($sqlQuery);
$stmt->execute();
return $stmt;
}
Keep it simple, that will help you in the long run. You only want something in your view that is accessible like an array and PDO offers you that out of the box. You can easily replace it later with - let's say - an array if you need to. Maybe not the best example with the array, but I'm sure you get the idea.
And just a note in the margin: Don't call it model, just call it database or similar. It better names the thingy which is always helpful.
Is it even possible to do something like this in codeigniter?Is it considered a good practice in general?
Yes it is possible.
I do it all of the time, if the data is not from post I can send it to the model.
$this->load->model('some_model');
$this->some_model->some_function($var);
What data do you want to send to the model? This will help in deciding whether it is bad practice.
Ofcourse it is possible! Like Kieran showed, you can just write functions in your model class that accepts parameters.
Just keep in mind that your controller should first parse/validate these parameters, no extra validation should be included in your model. Your model should be strictly used to write to and read from the DB.
So, as a good practice, you should only pass arguments that contain actual data to be stored or used in your queries.
I usually write different functions for different WHERE cases. For example:
select($id){}
select_by_name($name){}
...
This keeps your model comprehensible. The only exception I make is in the case of limiting your result. In all my models, I have on function to select all records, with an option to pass two more variables for pagination purposes:
select_all($start=null,$limit=null){
$qry = "SELECT * FROM ...";
if($start != null) $qry .= " LIMIT ?,?";
return $this->db->query($qry,func_get_args());
}
A submitted form on my site returns an array of request data that is accessible with
$data = $this->getRequest();
This happens in the controller which gathers the data and then passes this array to my model for placing/updating into the database.
Here is the problem. What if I want to manipulate one of the values in that array? Previously I would extract each value, assigning them to a new array, so I could work on the one I needed. Like so:
$data = $this->getRequest();
$foo['site_id'] = $data->getParam('site_id');
$foo['story'] = htmlentities($data->getParam('story'));
and then I would pass the $foo array to the model for placing/updating into the database.
All I am doing is manipulating that one value (the 'story' param) so it seems like a waste to extract each one and reassign it just so I can do this. Additionally it is less flexible as I have to explicitly access each value by name. It's nicer to just pass the whole request to the model and then go through getting rid of anything not needed for the database.
How would you do this?
Edit again: Looking some more at your question what I am talking about here all goes on in the controller. Where your form`s action will land.
Well you have a couple of options.
First of all $_GET is still there in ZF so you could just access it.
Second there is:
$myArray = $this->_request->getParams();
or
$myArray = $this->getRequest()->getParams();
Wich would return all the params in an array instead of one by one.
Thirdly if the form is posted you have:
$myArray = $this->_request()->getPost();
Wich works with $this->_request->isPost() wich returns true if some form was posted.
About accessing all that in your view you could always just in controller:
$this->view->myArray = $this->_request->getParams();
edit: right I taught you meant the view not the model. I guess I do not understand that part of the question.
If you want to deal with the post data inside your model just:
$MyModel = new Model_Mymodels();
$data = $this->_request->getParams();
$data['story'] = htmlentities($data['story']);
$myModels->SetItAll($data);
And then inside your model you create the SetItAll() function (with a better name) and deal with it there.
Edit: oh wait! I get it. You hate sanytising your input one by one with your technique. Well then what I showed you about how to access that data should simplify your life a lot.
edit:
There is always the Zend_Form route if the parameters are really coming from a form. You could create code to interface it with your model and abstract all this from the controller. But at the end of the day if you need to do something special to one of your inputs then you have to code it somewhere.