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.
Related
I'm working on an application written in PHP. I decided to follow the MVC architecture.
However, as the code gets bigger and bigger, I realized that some code gets duplicated in some cases. Also, I'm still confused whether I should use static functions when quering the database or not.
Let's take an example on how I do it :
class User {
private id;
private name;
private age;
}
Now, inside this class I will write methods that operate on a single user instance (CRUD operations). On the other hand, I added general static functions to deal with multiple users like :
public static function getUsers()
The main problem that I'm facing is that I have to access fields through the results when I need to loop through users in my views. for example :
$users = User::getUsers();
// View
foreach($users as $user) {
echo $user['firstname'];
echo $user['lastname'];
}
I decided to do this because I didn't feel it's necessary to create a single user instance for all the users just to do some simple data processing like displaying their informations. But, what if I change the table fields names ? I have to go through all the code and change those fields, and this is what bothers me.
So my question is, how do you deal with database queries like that, and is it fine to use static functions when querying the database. And finally, where is it logical to store those "displaying" functions like the one I talked about ?
Your approach seems fine, howerver I would still use caching like memcached to cache values and then you can remove static.
public function getUsers() {
$users = $cacheObj->get('all_users');
if ($users === false) {
//use your query to grab users and set it to cache
$users = "FROM QUERY";
$cacheObj->set('all_users', $users);
}
return $users;
}
(M)odel (V)iew (C)ontroller is a great choice choice, but my advice is look at using a framework. The con is they can have a step learning curve, pro is it does a lot of heavy lifting. But if you want to proceed on your own fair play, it can be tough to do it yourself.
Location wise you have a choice because the model is not clearly define:
You'll hear the term "business logic" used, basically Model has everything baring views and the controllers. The controllers should be lean only moving data then returning it to the view.
You model houses DB interaction, data conversions, timezone changes, general day to day functions.
Moudle
/User
/Model
/DB or (Entities and Mapper)
/Utils
I use Zend and it uses table gateways for standard CRUD to avoid repetition.
Where you have the getUsers() method you just pass a array to it, and it becomes really reusable and you'd just have different arrays in various controller actions and it builds the queries for you from the array info.
Example:
$data = array ('id' => 26)
$userMapper->getUsers($data);
to get user 26
enter code here
$data = array ('active' => 1, 'name' => 'Darren')
$userMapper->getUsers($data);`
to get active users named Darren
I hope this help.
I'm querying big chunks of data with cachephp's find. I use recursive 2. (I really need that much recursion sadly.) I want to cache the result from associations, but I don't know where to return them. For example I have a Card table and card belongs to Artist. When I query something from Card, the find method runs in the Card table, but not in the Artist table, but I get the Artist value for the Card's artist_id field and I see a query in the query log like this:
`Artist`.`id`, `Artist`.`name` FROM `swords`.`artists` AS `Artist` WHERE `Artist`.`id` = 93
My question is how can I cache this type of queries?
Thanks!
1. Where does Cake "do" this?
CakePHP does this really cool but - as you have discovered yourself - sometimes expensive operation in its different DataSource::read() Method implementations. For example in the Dbo Datasources its here. As you can see, you have no direct 'hooks' (= callbacks) at the point where Cake determines the value of the $recursive option and may decides to query your associations. BUT we have before and after callbacks.
2. Where to Cache the associated Data?
Such an operation is in my opinion best suited in the beforeFind and afterFind callback method of your Model classes OR equivalent with Model.beforeFind and Model.afterFind event listeners attached to the models event manager.
The general idea is to check your Cache in the beforeFind method. If you have some data cached, change the $recursive option to a lower value (e.g. -1, 0 or 1) and do the normal query. In the afterFind method, you merge your cached data with the newly fetched data from your database.
Note that beforeFind is only called on the Model from which you are actually fetching the data, whereas afterFind is also called on every associated Model, thus the $primary parameter.
3. An Example?
// We are in a Model.
protected $cacheKey;
public function beforeFind($query) {
if (isset($query["recursive"]) && $query["recursive"] == 2) {
$this->cacheKey = genereate_my_unique_query_cache_key($query); // Todo
if (Cache::read($this->cacheKey) !== false) {
$query["recursive"] = 0; // 1, -1, ...
return $query;
}
}
return parent::beforeFind($query);
}
public function afterFind($results, $primary = false) {
if ($primary && $this->cacheKey) {
if (($cachedData = Cache::read($this->cacheKey)) !== false) {
$results = array_merge($results, $cachedData);
// Maybe use Hash::merge() instead of array_merge
// or something completely different.
} else {
$data = ...;
// Extract your data from $results here,
// Hash::extract() is your friend!
// But use debug($results) if you have no clue :)
Cache::write($this->cacheKey, $data);
}
$this->cacheKey = null;
}
return parent::afterFind($results, $primary);
}
4. What else?
If you are having trouble with deep / high values of $recursion, have a look into Cake's Containable Behavior. This allows you to filter even the deepest recursions.
As another tip: sometimes such deep recursions can be a sign of a general bad or suboptimal design (Database Schema, general Software Architecture, Process and Functional flow of the Appliaction, and so on). Maybe there is an easier way to achieve your desired result?
The easiest way to do this is to install the CakePHP Autocache Plugin.
I've been using this (with several custom modifications) for the last 6 months, and it works extremely well. It will not only cache the recursive data as you want, but also any other model query. It can bring the number of queries per request to zero, and still be able to invalidate its cache when the data changes. It's the holy grail of caching... ad-hoc solutions aren't anywhere near as good as this plugin.
Write query result like following
Cache::write("cache_name",$result);
When you want to retrieve data from cache then write like
$results = Cache::read("cache_name");
I am writing a small framework where my goal is to make it easy for my developers to set up a redirect to the details page of a resource after adding/inserting one.
Ordinarily this would be as easy as PDO::lastInsertID(), but I am using more than just the ID to identify the resource, I am also using a formatted version of the resource's name/title.
For example a new tournament might be called "Test Tournament" and therefore it's resource URI will be domain.com/tournament/328-test-tournament.
So when I redirect after inserting, I need to redirect to '328-test-tournament' and not just '328'. This is by design for URL integrity purposes. I don't want people accessing individual resources with mis-matched IDs and titles.
So that said, after I insert, I would like to be able to automatically return not just the ID, but the entire data set for what I entered, so that I can then format the title and redirect.
I could do this in the controller:
$this->TournamentModel->insert();
$id = PDO::lastInsertID();
$title = my_title_formatting_function($_POST['title']);
#header("Location: domain.com/tournament/{$id}-{$title}");
But I want a solution that's slightly more elegant like this:
$id = $this->TournamentModel->lastInsert();
Where lastInsert is actually a public method in the core model by retrieving not just the id of the last insert, but the entire row. I would then handle the title formatting and id concatenation right there.
Does something like this exist? Or at the very least is there a PDO method that returns the table that was inserted into so that I could construct a query using the table name and the id?
Check Doctrine or Propel.
They are two very famous Object Relational Mapper's, which have as base PDO.
ORM's can do what you asked, and have lots of other features you will enjoy, check it up.
Does a 'title' not belong to a Tournament object? i.e.
class TournamenentModel {
private $id;
private $title;
public function setTitle($title) {
$this->title = $title;
}
public function getTitleFormatted() {
return my_title_formatting_function($this->title);
}
}
in which case, your insert method might look something like
PDO::save(array('title' => $this->title));
and your workflow would be along the lines of:
$tournament = new TournamentModel();
$tournament->setTitle($_POST['title']);
$tournament->insert();
$title = $tournament->getTitleFormatted();
#header("Location: domain.com/tournament/{$id}-{$title}");
The title is persisted in memory until you release the object, there is no need to save and then retrieve it from a database?
In the end, I got this function. I don't know whether it's normal or not.
function user_registration($user_name, $user_email, $user_pass, $address,
$city, $postalcode, $country, $phone, $mobilephone)
How and why can I improve this?
You could either pass an array with all variables packed nicely together, or just make a "User" class and add all properties via setters and do the validation in the end with a dedicated method:
class User {
public function setName($name) {
$this->name = $name;
}
[...]
public function register() {
//Validate input
if (empty($this->name))
$this->errors[] = "ERROR, Username must not be emtpy";
//Add the user to the database
//Your SQL query
return empty($this->errors);
}
}
$user = new User();
$user->setName("Peter");
$success = $user->register();
if (!$success)
echo "ERRORS OCCURED: ".print_r($user->errors, true);
A solution would be to only have one parameter, that can contain several pieces of data -- like an array.
Your function could be defined this way :
function user_registration(array $data) {
// work with $data['name']
// and $data['email']
// ...
}
And you'd call it like this :
user_registration(array(
'name' => 'blah',
'email' => 'test#example.com',
'pass' => '123456',
// and so on
));
Nice things are :
You can add / remove "parameters" easily
The "parameters" can be passed in any order you want
Not so bad things are :
No hint while typing in your IDE
No documentation (like phpDoc)
I personally don't think it has too many parameters. From looking at the functions definition it is clear what you require as input which wouldn't be so obvious if called with an array.
"If it aint broke don't fix it!"
When you look at your argument names, you cannot but notice that they can be grouped into three different groups:
User Data: $user_name, $user_pass
Address Data: $address, $city, $postalcode, $country
Contact Data: $user_email, $phone, $mobilephone
Consequently, you could apply Introduce Parameter Object:
Often you see a particular group of parameters that tend to be passed together. Several methods may use this group, either on one class or in several classes. Such a group of classes is a data clump and can be replaced with an object that carries all of this data. It is worthwhile to turn these parameters into objects just to group the data together. This refactoring is useful because it reduces the size of the parameter lists, and long parameter lists are hard to understand. The defined accessors on the new object also make the code more consistent, which again makes it easier to understand and modify.
If you dont want to do OOP you could group the arguments into arrays as well but you'll lose all the type benefits then. I will just assume you don't mind using objects. So, after applying the Refactoring you'll end up with
function user_registration(User $user, Address $address, Contact $contact)
Looking at that parameter list should make you notice that Address and Contact likely belong to User in the first place, so you could consider changing the function signature to just
function user_registration(User $user)
and then call it like this:
$user = new User('johndoe', 'secretsauce');
$user->setAddress(new Address('Doe Street', 'Doe Town', 12345, 'Neverland'));
$user->setContact('jdoe#example.com', '+123 12345', '+123 54321');
user_registration($user);
We could probably make username and password into a Credentials object as well and then just do
user_registration(new User($credentials, $address, $contact));
By requiring the data in the ctor we make sure newly registered users do have all these information. We could argue whether we need Address and Contact for registering users though, so Setter injection might be good enough here:
$user = new User(new Credentials('johndoe', 'secretsauce'));
$user->setAddress(new Address('Doe Street', 'Doe Town', 12345, 'Neverland'));
$user->setContact(new Contact('jdoe#example.com', '+123 12345', '+123 54321'));
user_registration($user);
However, user_registration as a separate function in global scope is misplaced. By GRASP's Information Expert principle, methods should be on the objects that have the most information to fulfill the responsibility. This improves Cohesion and reduces Coupling. In other words:
$user = new User($credentials);
$user->setAddress($address);
$user->setContact($contact);
$user->register();
One problem with the User class now is that it contains the password. The password is only ever needed to authenticate the user against the authentication service. We could argue about the username, but the password definitely should not be part of the User object at all. So you should do something like
$user = new User;
$user->setAddress($address);
$user->setContact($contact);
$user->register($credentials);
and when register() is called, it will only use the credentials to delegate insertion of a new user into the user storage. But it will not retain them in the actual User instance.
Finally, you might want to add a Simple Factory or Builder pattern to encapsulate the creation of the User to simplify the aggregation of the various instances. Or you might want to introduce a Repository pattern and move the register() method there. This is beyond scope for this question though.
As a general rule of thumb (not as a steadfast rule), anytime you have to ask "Does this function have too many parameters?" -- the answer is yes. Your intuition is telling you something that your brain just hasn't yet been able to work out.
In this particular case, the first thing that comes to mind is that your user cred.s should be checked first (does the username already exist? is the pw complex enough) and your user details should be added separately, probably using an object or array.
One way is to pass an array as parameter to this function and put all info in that array:
function user_registration(array $user_info)
{
// process $user_info;
}
Function (method) without any parameters is best. Function with one paremeter is better than function with 2 parameters. Function with 2 parameters is better than function with 3 parameters and so on.
#Florianh has given a perfect solution how your code can be improved. With this comment, I would like to elaborate on the "why" part from a system design perspective.
From an Object Oriented perspective, other objects should be able to manipulate attributes. That is why attributes should never be defined as "public". They should be "private" e.g.:
private var $name;
The reason for this is when other objects would manipulate this variable, the correct working of the object is at risk. Methods, on the other hand, can be defined publicly:
public function register()
Accordingly, the manipulation of the attributes will happen through the appropriate methods. A method can be used to also evaluate the correctness of operations on the attributes.
There are two operations that can happen: reading the current value of an attribute by using Get methods and saving a new value of an attribute by using Set methods.
A good practice would be to implement a get method for each class attribute. Each attribute that can be modified, should have an appropriate set method as well.
Sometimes it is better to not implement a get/set method (e.g.: showData()) after all. This is because the usage of getters and setters within a particular class may possibly lead to a reduced performance. However, this means that when changing or implementing the class, one must be careful when trying to save false information and as a result putting the integrity of the class at risk.
Now, consider the fact that you decide to use only one phone number instead of both a phone- and mobile number. When the mobile number becomes deprecated, your main program still remains the same. All you have to do is change/remove one method. The advantage of using getters and setters is an increase in adaptability and maintainability.
I make array of keys like
$fields = array('field1', 'field2');
function register (array $values, array $keys)
{
$data = array();
foreach ($keys as $one)
{
if (isset($values[$one])) $data[$one] = $values[$one];
}
// or you can use array functions like array_flip and after - array intersect
}
I'd make it this way
fields=explode(",","name,surname,lastname,street,city,region,zip,country");
user_registration($fields);
Because I am sure these variables coming from $_POST
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.