I'm sing the Data Mapper Pattern in Zend Framework. This works well so far, but now I got to a point where I need your help/opinion. So let's start with the Code:
We got a table with several Persons:
CREATE TABLE `persons` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`age` int(3) NOT NULL,
`haircolor` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id``),
);
Now I try to select all people, that have brown hair. I use the following method in the ServiceLayer
public function getPeopleByHaircolor($hair) {
return $this->getMapper()->fetch('haircolor = ?', $hair);
}
The method in the Mapper looks like this:
public function fetch($condition, $value) {
$resultSet = $this->getTable()->fetchAll($this->getTable()->select()->where($cond, $value));
$entries = array();
foreach($resultSet as $row) {
$entry = new Default_Model_Person();
$entry->id = $row->id;
$entry->name = $row->name;
[...]
}
return $entries;
}
I think I follow the Data Mapper Pattern with this methods...
Now the Problem:
I want to select Persons, that have brown hair AND that are younger than 20 yrs.
So how can I do that? My try:
public function getTeens($hair) {
$rows = $this->getMapper()->fetch('haircolor = ?', $hair);
$return = array();
foreach($rows as $row) {
if((int)$row->age < 20) $return[] = $row;
}
return $return;
}
But if you get more variables, like "people with brown hair, younger than 20yrs and with the name 'Foo Bar'", I need more and more methods and/or foreach loops.
My Question:
How would you do this in the Data Mapper Pattern? If I do a native SQL query like $serviceLayer->mapper->table->qry('SELECT ...'), is this breaking the Data Mapper Pattern?
I don't like those additional foreach loops and it feels like I'm doing something wrong, so I wrote this question.
Indeed, for two conditions, it inadvisable to query on one condition and then filter on your other while you iterate. It leaves you no clear approach for more than two conditions. And implementing pagination adapters becomes pretty messy.
Seems to me that the issue is that your mapper fetch() method supports permits only a single condition. So:
Modify the signature to support an array of conditions.
Alternatively, you could create a separate mapper method for each enhanced fetch method, ex: fetchByHairAndAge($hair, $age), etc.
In my limited experience with data mappers I have found the following approach to work quite well for the scenarios I have encountered so far:
public function getPeopleByHaircolorAndAge($haircolor, $age, $limit=null, $offset=null)
{
$people = new PersonCollection;
$people->filterByHaircolor($haircolor);
$people->filterByAge($age);
$people->setLimit($limit);
$people->setOffset($offset);
$personCollectionMapper = new PersonCollectionMapper;
$personCollectionMapper->fetch($people);
return $people;
}
By instantiating the domain object first I have the option to set filters and other variables that the data mapper can read from the object when it is injected into the mapper.
To me this has been the superior approach so far compared to having multiple mapper methods that returns a domain object.
Related
In the past I've worked with framework as Slim or CodeIgniter, both provide method such as getWhere(), this method return true or false if the array content passed to the getWhere was found on database table.
Actually I've created my own layer class that extends PDO functionality, my goal is create a method that take care to look for a specific database content based on the supplied parameters, currently I created this:
public function findRecordById($table, $where = null, $param = null)
{
$results = $this->select("SELECT * FROM $table WHERE $where",$param);
if(count($results) == 0)
{
return false;
}
return true;
}
for search a content I simply do:
if(!$this->db->findRecordById("table_name", "code = :key AND param2 = :code",
array("key" => $arr['key'], "code" => $arr['code']))){
echo "content not found";
}
now all working pretty well but I think that the call on the condition is a bit 'too long and impractical, I would like to optimize everything maybe going all the content into an array or something, but until now I have a precise idea. Some help?
I don't quite understand your question, but for the code provided I could tell that such a method should never belong to a DB wrapper, but to a CRUD class, which is a completely different story.
If you want to use such a method, it should be a method of a Model class, and used like this
$article = Article::find($id);
While for a database wrapper I would strongly suggest you to keep with raw SQL
$sql = "SELECT * FROM table_name WHERE code = :key AND param2 = :code";
$data = $this->db->query($sql, $arr)->fetchAll();
is the clean, tidy, and readable code which anyone will be able to read and understand.
As a bonus, you will be able to order the query results using ODER BY operator.
I'm requesting the community's wisdom because I want to avoid bad coding practices and/or mistakes.
I'm having a php class wich is an objects manager. It does all the work with the database: inserting new data, updating it, getting it and deleting it (I've read it's called CRUD...). So it has a function that gets an element by id.
What I want to write is a function that gets a list of objects from the table.
I will then use a mysql query that goes something like
SELECT * FROM mytable WHERE column1='foo'
And then some order by and limit/offset.
However, in my application there are different cases in which I will need different lists from this table. The WHERE clause will then be different.
Should I write different functions, one per type of list?
Or should I write one generic function to which I will send arguments that then dynamically creates the query? If so, do you have any advice on how to do this properly?
EDIT:
Thanks for all your answers! I should tell that I'm not using any framework (maybe wasn't the best idea...), so I didn't know about query builders. I'll investigate that (either finding a standalone uery builder or migrating to a framework or writing my own, I don't know yet). That will be useful any time I need to execute a mysql query :-)
Although I'm still confused:
Let's say I need several lists of clients (objects), for example all clients, clients over 18, clients currently online...
What approach would be best to retrieve those lists? I can either have 3 functions in my clients manager
allClients() {//execute a specific query and return list of objects}
allClientsOver18() {//execute specific query and return list of objects}
allClientsOnline() {//execute specific query and return list of objects}
or I can have one function tht builds the query based on parameters
listClients($some, $parameters)
{
//Build the query based on the parameters (definitely need a query builder!)
//Execute the query
//return list of objects
}
Which approach would be best (I guess it depends on circumstances) and mostly, why?
Thanks in advance!
Rouli
Thanks for all the info on query builders, I didn't even know it existed! :-) However I'm still confused as to wether I should write one specific function for each case (that function can still use the query builder to write its specific query), or write one generic function that builds dynamically the query based onf parameters. Which would be better in which case? I've added an example in my question, hope it makes it clearer!
This depends on how often you use each of these isolated queries, how complex the conditions are and how often you my need to combine the conditions with other queries. For eaxample if each the "online" and "over18" are just simple conditions then you could just use the normal findBy logic from my example:
$table = new MyTable($db);
$onlineOnly = $table->findBy(array('is_online' => true), null, null);
$over18Only = $table->findBy(array('is_over_18' => true), null, null);
$onlineOver18 = $table->findBy(array('is_over_18' => true, 'is_online' => true), null, null);
If the query is more complex - for example to get over 18 clients you have to do:
select client.*, (YEAR(CURDATE()) - YEAR(client.birthdate)) as age
FROM client
WHERE age >= 18
Then its probably better to make this into a separate method or create methods to work on Query objects directly to add complex conditions for example - especially if you will need this condition in a few different queries in the app:
$table = new MyTable($db);
// creates a basic query defaulted to SELECT * FROM table_name
$query = $table->createQuery();
// adds the complex condition for over 18 resulting in
// SELECT table_name.*, (YEAR(CURDATE()) - YEAR(table_name.birthdate)) as age WHERE age >= 18
$over18 = $table->applyOver18Query($query)->execute();
This way you can apply your over 18 condition easily to any query with out manually manipulating the builder ensure that your over 18 condition is consistent. But for simplicity you could also have a convenience method like the following:
public function findOver18By(array $criteria, $limit = null, $offest = null) {
$query = $this->findBy($criteria, $limit, $offset);
$this->applyOver18Query($query);
return $query->execute();
}
Normally you would use some kind of query builder at the lower level like:
$query = $db->createQuery()
->select($fields)
->from($tableName)
->where($fieldName, $value);
$results = $query->execute();
Then you might have a class that makes use of this like:
class MyTable
{
protected $tableName = 'my_table';
protected $db;
public function __construct($db) {
$this->db = $db;
}
public function findBy(array $criteria, $limit = null, $offset = null) {
$query = $this->db->createQuery();
$query->select('*')->from($this->tableName);
foreach ($criteria as $col => $value) {
// andWhere would determine internally whether or not
// this is the initial WHERE clause or an AND clause
// something similar would happen with an orWhere method
$query->andWhere($col, $value);
}
if (null !== $limit) {
$query->limit($limit);
}
if (null !== $offset) {
$query->offset($offset);
}
return $query->execute();
}
}
Usage would look like:
$table = new MyTable($db);
$result = $table->findBy(array('column1' => 'foo'), null, null);
This is a lot to implement on your own. Most people use an ORM or a DBAL to provide these features and those are often included with a framework like Eloquent with Laravel, or Doctrine with Symfony.
I guess at start you should need some main data like
$main = [
'from' = '`from_table`',
]
Then you should add selects if had
$selects = ['fields1','field2'];
$where = ['some condition', 'other condition'];
Then you could
$query = "SELECT ".implode(',', $selects ." FROM ".$main['from']."
WHERE ".implode('AND ', $where .";";
That's some approaches for simple one table query.
If you need Joins, then $selects better would be make with aliasos, so no field will be lost if they are not different, like
select temp.id as temp_id , temp2.id temp2_id from temp
left join temp2 on temp2.temp_id = temp.id
Feel free to ask some questions, maybe i haven't told , but you should also check bound parameters with some functions to avoid sql injections
I suggest using a CLASS for your database which holds all your database accessing functions as it makes your code cleaner making it more easier to look through for errors or modifications.
class Database
{
public function connect() { }
public function disconnect() { }
public function select() { }
public function insert() { }
public function delete() { }
public function update() { }
}
sample connect function for connecting to a selected database.
private db_host = ‘’;
private db_user = ‘’;
private db_pass = ‘’;
private db_name = ‘’;
public function connect()
{
if(!$this->con)
{
$myconn = mysqli_connect($this->db_host,$this->db_user,$this->db_pass);
if($myconn)
{
$seldb = mysqli_select_db($this->db_name,$myconn);
if($seldb)
{
$this->con = true;
return true;
} else
{
return false;
}
} else
{
return false;
}
} else
{
return true;
}
}
with this approach will make creating CRUD functions easier. Heres a sample insert function.
public function insert($table,$values,$rows = null)
{
if($this->tableExists($table))
{
$insert = 'INSERT INTO '.$table;
if($rows != null)
{
$insert .= ' ('.$rows.')';
}
for($i = 0; $i < count($values); $i++)
{
if(is_string($values[$i]))
$values[$i] = '"'.$values[$i].'"';
}
$values = implode(',',$values);
$insert .= ' VALUES ('.$values.')';
$ins = #mysql_query($insert);
if($ins)
{
return true;
}
else
{
return false;
}
}
}
heres a quick view on using this.
;<?php;
$db->insert('myDataBase',array(3,"Name 4","this#wasinsert.ed")); //this takes 3 paramteres
$result = $db->getResult(); //Assuming you already have getResult() function.
print_r($result);
?>
EDIT
there are more purist approach to handling database operations. I highly suggest it because handling information is very delicate and should be fronted with many safety measures But it requires deeper php knowledge. Try PDO for php and this article by matt bango on prepared statements and its significance.
Could you help to extend a little bit about the Zend Quickstart: In the tutorial, we use the Mapper to update a single Guestbook. What if I want to update more than one Guestbook? And based on some conditions?
For example, I have an action to delete all Guestbooks that were created before 2012-12-21. What should I update to achieve that?
Does my approach make sense?
// application/models/GuestbookMapper.php
class Application_Model_GuestbookMapper
{
public function deleteByCreatedBefore($date)
{
$this->getDbTable()->deleteByCreatedBefore($date);
}
}
// application/models/DbTable/Guestbook.php
class Application_Model_DbTable_Guestbook extends Zend_Db_Table_Abstract
{
public function deleteByCreatedBefore($date) {
$where = $this->getAdapter()->quoteInto('created < ?', $date);
$this->delete($where);
}
}
Thanks,
If you are using the quickstart model/mapper and want to stay true to that data mapper paradigm you wouldn't have anything in your Application_Model_DbTable_Guestbook except for properties ('name', 'primary'...). The DbTable model would exist as the database adapter for that single table.
Your delete function would be placed in the mapper.
class Application_Model_GuestbookMapper
{
public function deleteByCreatedBefore($date)
{
$where = $this->getDbTable()->quoteInto('created < ?', $date);
//delete() returns num of rows deleted
$this->getDbTable()->delete($where);
}
}
This will work but may not be the best/safest way to achieve the required functionality.
This particular example of the Data Mapper is very simple and might be somewhat misleading to some people. The Guestbook example of the Mapper is really not a good representation of the mapper as the database row and the domain model (Application_Model_Guestbook) map 1 to 1 (one database column to one model property).
Where the Data Mapper starts to shine is when you need to map several database tables to a single Domain Model. With the understanding that your Domain Model (Application_Model_Guestbook) may have to effect more then one database table each time delete() is called, the structure for the delete() function is important.
What should you do to accomplish a delete with the mapper?
First: update Application_Model_GuestbookMapper::fetchAll() to accept a $where parameter, I usually setup this type of function to accept an array that sets the column and the value.
//accepted parameters: Zend_Db_Table::fetchAll($where = null, $order = null, $count = null, $offset = null)
//accepts array (column => value )
public function fetchAll(array $where = null)
{
$select = $this->getDbTable()->select();
if (!is_null($where) && is_array($where)) {
//using a column that is not an index may effect database performance
$select->where($where['column'] = ?, $where['value']);
}
$resultSet = $this->getDbTable()->fetchAll($select);
$entries = array();
foreach ($resultSet as $row) {
$entry = new Application_Model_Guestbook();
$entry->setId($row->id)
->setEmail($row->email)
->setComment($row->comment)
->setCreated($row->created);
$entries[] = $entry;
}
return $entries;
}
Second: Refactor your Application_Model_GuestbookMapper::deleteByCreatedBefore() to accept the output from fetchAll() (actually it would be simpler to just build a delete() function that accepts the output: array of Guestbook objects)
//accepts an array of guestbook objects or a single guestbook object
public function deleteGuestbook($guest)
{
if (is_array($guest) {
foreach ($guest as $book) {
if ($book instanceof Application_Model_Guest){
$where = $this->getDbTable()->quoteInto('id = ?', $book->id);
$this->getDbTable()->delete($where);
}
}
} elseif ($guest instanceof Application_Model_Guest) {
$where = $this->getDbTable()->quoteInto('id = ?', $guest->id);
$this->getDbTable()->delete($where);
} else {
throw new Exception;
}
}
Deleting a domain object as an object will become more important as you have to consider how deleting an object will affect other objects or persistence (database) paradigms. You will at some point encounter a situation where you don't want a delete to succeed if other objects still exist.
This is only an opinion but I hope it helps.
Imagine a "Games" class used to track games between opponents. Is it better OOP to have 1 method to retrieve games based on user input parameters or is it better to have multiple methods specific to the retrieval goals?
class Games {
function get_games($game_id = NULL, $stadium_id = NULL, $start_date = NULL,
$end_date = NULL, $count = 999); {}
}
VS
class Games {
function get_all_games($count = 999); {}
function get_game_by_id($game_id = 1); {}
function get_games_by_stadium($stadium_id = 1); {}
function get_games_by_dates($start_date = NULL; $end_date = NULL) {}
}
Explanation of benefits and any coding / snytax tips would be appreciated. Thanks.
The more I practice OOP the more I find myself following a rule about passing parameters to methods. Kind of like having many levels of nested if statements, I find that if I have more than two I might be doing something wrong.
Keep your code simple. You're writing a method that does something, not a block of procedural code that does everything. If you want to get a game, then get a game. If you want to get a list for a date range, then do that.
However I would point out that you don't really need get_all_games() - You can just allow for get_games_by_dates() to be passed with no parameters. If it doesn't get any then it would get the games for every date since forever (all the games)
I would always err on the side of OOP code. the reason being is that it makes you code much easier to maintain and read. The more functions you have the easier it is to follow code later on down the road
I would go for separate methods since you are using lots of paramaters with default values.
If you want to get all games you would have to do:
$games->get_games(NULL, NULL, NULL, NULL, 999);
Assuming that your get_....() functions are returning all game data, I would write a single function to return this data, based on an id passed in, and write a series of find_...() functions to return an array of found ids. This will have the added benefit of making it easier to override the data retrieval code in decendant classes.
class Games {
public function get_game($game_id) {
// Return game details (array/object) for $game_id, or FALSE if not found.
}
public function find_all_games() {
// Return array of ids for all games.
}
public function find_games_by_dates($start_date = NULL, $end_date = NULL) {
// Return array of ids between $start_date and $end_date unless NULL.
}
}
You can then call:
$oGames = new Games() ;
$aGames = $oGames->find_all_games() ;
foreach($aGames as $id) {
$aGame = $oGames->get_game($id) ;
if($aGame !== FALSE) { // This check might be skipped if you trust the array of ids from find_all_games().
// Assuming an array is returned.
echo "Game Found: ".$aGame['name']."\n" ;
}
}
The benefit of "multiple methods specific to the retrieval goals" is that you can add/remove goals. The problem with using one monolithic function with a bunch of parameters is that, should you decide to add/remove a way to get games, you'd have to change the interface. Which would break any code that uses it.
Each method should be as concise as possible, performing only one function.
I am working on my first module for magento version 1.3.2.3.
I have created a simple table (not EAV, just a primary key and 2 columns) and some classes to access it, following Alan Storm's articles which helped me a lot, but I can't figure out how to make a simple select: Alan explains how to load with the primary key, but not selecting rows that match some value.
In normal MySQL I'd write:
SELECT *
FROM my_table
WHERE some_field = '" . $someValue . "'
I've found a snippet which gives me the result I want:
$resource = new Mage_Core_Model_Resource();
$read = $resource->getConnection('core_read');
$select = $read->select()
->from('my_table')
->where('some_field = ?', $someValue);
return $read->fetchAll($select);
But there have to be an easier/prettier solution, using the model class I've created. The result will be a single row, not a collection.
I've tried everything I could think of, like:
return Mage::getModel('modulename/classname')->select()->where('some_field = ?', $comeValue);
return Mage::getModel('modulename/classname')->load()->where('some_field = ?', $comeValue);
return Mage::getModel('modulename/classname')->load(array('some_field = ?', $comeValue));
and more stuff, but no luck so far: what am I missing??
You probably want to use your model's Collection for that.
$collection = Mage::getModel('mygroup/mymodel')->getCollection();
$collection->addFieldToFilter('some_field',$some_value);
foreach($collection as $item)
{
var_dump($item);
}
var_dump($collection->getFirstItem());
var_dump($collection->getLastItem());
Here's an example of how this is achieved in the CoreUrlRewrite Model class:
public function loadByIdPath($path)
{
$this->setId(null)->load($path, 'id_path');
return $this;
}
You can create similar methods in your model classes. You can also use the alternative form of the load method anywhere in your code:
$model = Mage::getModel('modulename/classname')->load($someValue, 'some_field');