Let's say i'm making a web application for a online story sharing site.
We have tables: genre,tags,content_warning with only 2 columns as id and name. for example: genre = 1 - romance, 2 - action, etc.
I use the above tables to control/define each genre/tags/contentwarnings. so whenever a user publishes/uploads their own story they just cant input random tags/genre/contentwarnings.
So i'll have these functions in my story_model:
public function get_genre(){
$genre = array();
$query = $this->db->get('genre');
foreach($query->result() as $row){
$genre[$row->genre_id] = $row->genre_name;
}
return $genre;
}
public function get_tags(){
$tags = array();
$query = $this->db->get('tag');
foreach($query->result() as $row){
$tags[$row->tag_id] = $row->tag_name;
}
return $tags;
}
public function get_content_warnings(){
$content_warning = array();
$query = $this->db->get('content_warning');
foreach($query->result() as $row){
$content_warnings[$row->content_warning_id] = $row->content_warning_name;
}
return $content_warning;
}
Am i correct to say that i am repeating myself in the above 3 functions? so i would write a single code like:
public function sample_get($table){
$data = array();
$query = $this->db->get($table);
foreach($query->result_array() as $row){
$data[$row[$table.'_id']] = $row[$table.'_name'];
}
return $data;
}
to access the above function in my controller i would pass 'genre' or 'tag' or 'content_warning' as the parameter.
How would i name my above function then? the 3 separate functions are easy to name as they are very direct to the point and you know what the function does by reading its name.
combining all 3 function into one makes it harder to name the function in a direct way. I like to name my function as direct as possible so its easy for me to follow. as in they're more readable.
Account_model:
public function get_userid($username){
$this->db->select('user_id');
$this->db->from('user');
$this->db->where('username', $username);
$query = $this->db->get();
foreach($query->result() as $row){
$user_id = $row->user_id;
}
return $user_id;
}
story_model:
public function get_stories($user_id){
$stories = array();
$this->db->select('story_id, story_name');
$this->db->from('story');
$this->db->where('user_id', $user_id);//author
$query = $this->db->get();
foreach($query->result() as $row){
$stories[$row->story_id] = $row->story_name;
}
return $stories;
}
would the above two function warrant a dry-ification?
Or let's change the second function to only get the story_id which would coincide with the account_model function to get user_id. would they then be needed to dry?
but i get confused as to when do i decide to dry my functions? since i'll use alot of get function to retrieve data should i then just opt for a single get function?
If you want to use your common, abstract function, but keep well-named public functions, you can take the following approach:
/**
* Use the common function internally, but do not expose it publicly
*/
private function sample_get($table){
$data = array();
$query = $this->db->get($table);
foreach($query->result_array() as $row){
$data[$row[$table.'_id']] = $row[$table.'_name'];
}
return $data;
}
/**
* Use well-named functions that are exposed to be used publicly
*/
public function get_content_warnings(){
return $this->sample_get('content_warning');
}
This makes it possible to structure your class internally (through using the keyword private), but maintain a good interface to be used by other classes (through using public).
Related
This has been bothering me for quite some time now.
Controller, on my take, is where I do validation, model calls, and displaying of data. And on the Model, which has only one purpose, is where I make my SQL queries.
But what is the best approach on Model. Do I have to make a lot of functions, with different conditions/approaches, or do I have to do it all in a single function, dedicated to a single table in my database. Take the code below for example:
Multiple Functions:
class Sword_model extends CI_Model {
public function __construct()
{
$this->load->database();
$this->load->library('session');
$this->load->helper('url_helper');
}
public function getsword($swordid = NULL)
{
if($swordid === NULL){
$query = $this->db->query("SELECT * FROM sword_tb");
return $query->result_array();
}
$query = $this->db->query("SELECT * FROM sword_tb WHERE sword_id = ?", array($swordid));
return $query->row_array();
}
public function getstrongswords($strong = NULL)
{
if($strong === NULL){
return false;
}
$query = $this->db->query("SELECT * FROM sword_tb WHERE strong = ?", array($strong));
return $query->result_array();
}
/*** AND MORE SUCCEEDING FUNCTIONS BELOW FOR DIFFERENT COLUMNS/CONDITIONS ***/
}
Pros: This is straight through and easier to understand (and I think is faster)
Cons: You have to manually create functions for different conditions/columns
Single Function:
class Sword_model extends CI_Model {
public function __construct()
{
$this->load->database();
$this->load->library('session');
$this->load->helper('url_helper');
}
public function getsword($column = NULL, $value = NULL, $condition = 'AND')
{
$query = 'SELECT * FROM sword_tb';
$count = count($column);
for($x = 0; $x < $count; $x++){
if($x > 0){
$query .= ' '.$condition;
} else {
$query .= ' WHERE (';
}
$query .= ' '.$column[$x].' = ?';
}
$query .= ' ORDER BY sword_name';
$query = $this->db->query($query, $value);
return $query->result_array();
}
}
with this single function approach, you can call this function by putting arrays as parameters like this:
$this->Sword_model->getsword(array('sword', 'strong'), array(3, 1), 'OR');
And the query will look like this:
SELECT * FROM sword WHERE (sword = ? OR strong = ?) ORDER BY sword_name
And if you left it behind blank, the query will look like this:
SELECT * FROM sword ORDER BY sword_name
Pros: Flexible
Cons: Slower (?) than the first approach
What is more ideal between the two? Or is there any other more ideal way?
I prefer to have each table to have it's own model with different preferable methods as you do in "Multiple Model". This way it is more maintainable and easier to approach & understand the codes. The second one is though it's harder to approach and it may not be useful on all scenario.
I have been using Codeigniter MVC & HMVC for more than 2 years. First one is my choice for so far and it helps me to check and maintain my codes and also helps in my future updates/codes.
You can try ORM ( Object-relational mapping )
Datamapper is an ORM library .It is designed to map your Database tables into easy to work with objects .
After successful installation of Datamapper
If you have a table called users
Create a model with name user
<?php
class User extends DataMapper {
function __construct($id = NULL)
{
parent::__construct($id);
}
}
In your controller, you can simply access data by
$u = new user(); // Singular of model name is required
$u->get();
foreach ($u as $user => $value)
{
echo $user->name;
}
By this method you have to create each models for your tables and access data through your controller
I prefer one generic model using which I can execute all mysql queries (which are not so complex, otherwise you need to write a query string and get it executed in few cases if there is a need of some too much complex query.) Otherwise you can call the models' generic functions from your controller by passing data array and table name and rest will be managed by the model.
Here is the generic model I use:
<?php
/*
All user module related databse functions
Author : Himanshu Upadhyay (himanshuvarun#gmail.com)
*/
if (!defined('BASEPATH'))
exit('No direct script access allowed');
class User_Model extends MY_Model {
public function __construct() {
parent::__construct();
}
public function get_rows($filters = array(), $table = TBL_USER) {
return parent::get_rows($filters, $table);
}
public function get_columns($table = TBL_USER) {
return parent::get_columns($table);
}
public function update_table($data, $where, $table = TBL_USER, $set = '') {
return parent::update_table($data, $where, $table, $set = '');
}
public function get_count($filters = array(), $table = TBL_USER) {
return parent::get_count($filters, $table);
}
public function insert($data, $table = TBL_USER) {
return parent::insert($data, $table);
}
public function delete($where, $table = TBL_USER) {
return parent::delete($where, $table);
}
/* End of file user_model.php */
/* Location: ./application/models/user_model.php */
?>
And in your controller, you can call the model function like :
$user_data['first_name'] = 'ABC';
$user_data['last_name'] = 'XYZ';
$this->user_model->insert($user_data, 'tbl_users'); // This is calling `insert` function of user model with first array argument with the data with column names as keys of the array and 2nd argument is the table name.
Conclusion: So by this approach, you can load user_model in all the controllers and you can use all its generic functions in all of the controllers. So this approach avoids redundant model functions to fetch, insert and update the data as well as it saves us by defining different models for each tables.
Most of the time multiple functions are easier to debug, comprehend and maintain.
That said, you could make your multi-method model code a lot less repetitive.
Consider the following. (__construct() not shown cause yours is fine.)
class Sword_model extends CI_Model
{
protected $get_all_sql = 'SELECT * FROM sword_tb';
protected $get_where_sql = "SELECT * FROM sword_tb WHERE sword_id = ?";
public function getsword($swordid = NULL)
{
$sql = isset($swordid) ? $this->get_where_sql : $this->get_all_sql;
$bind = isset($swordid) ? $swordid : FALSE;
return $this->do_Query($sql, $bind);
}
public function getstrongswords($strong = NULL)
{
if(isset($strong))
{
return $this->do_Query($this->get_where_sql, $strong);
}
//Returning an empty array means the controller can skip doing a conditional
//check for a model return === FALSE.
//foreach() won't choke on an empty array, but it will choke if passed FALSE
return array();
}
protected function do_Query($sql, $binds = FALSE)
{
$query = $this->db->query($sql, $binds);
return $query->result_array();
}
}
However, the "flexible" approach can be useful is certain circumstances.
The speed difference between "singles" vs "flexible" is negligible and not a consideration. What does need to be considered is that "flexible" quickly becomes unwieldy as you try to respond to more "conditions".
There is something that will make writing "flexible" model methods easier - easier to write, comprehend, debug and maintain. Instead of manually constructing query strings and passing them to $this->db->query() use Query Builder. It is designed exactly for situations where you need to conditionally build a query statement.
Your "flexible" version of getsword() has at least one limitation and that is that you cannot SELECT columns that are not part of the WHERE clause.
Using Query Builder here is one way you could implement a flexible method that builds queries for both AND WHERE and OR WHERE clauses. Hopefully the DocBlock before the method will provide some insight.
class Sword_model extends CI_Model
{
protected $get_all_sql = 'SELECT * FROM sword_tb';
protected $get_where_sql = "SELECT * FROM sword_tb WHERE sword_id = ?";
/**
*
* #param mixed $columns The columns to retrieve. Can be either a string
* e.g. 'title, content, date',
* or it can be an array e.g. array('title', 'content', 'date')
*
* #param array $where If provided, must be an associative array where
* the key => value is 'field_name' => value_to_match, e.g.
* array('title' => "Kill Bill")
* $where requires a different structure when the $condition argument is
* "OR". In this case the value part should provide multiple values.
* These values can be provided either in an array
* or a comma separated string list. For instance:
* As an array, $where = array('field_name' => array('val1', 'val2'));
* As a string, $where = array('field_name' => 'val1, val2'));
*
* #param string $condition For this example can be either 'AND' (default) or 'OR'
*/
public function getsword($columns = NULL, $where = NULL, $condition = 'AND')
{
if(!empty($columns)) //No $columns means SELECT *
{
$this->db->select($columns);
}
$condition = strtoupper($condition); //Don't assume
if(!empty($where))
{
if($condition === 'OR')
{
foreach($where as $key => $values)
{
if(is_string($values))
{
$values = explode(', ', $values);
}
if(is_array($values))
{
foreach($values as $matching)
{
$match = [$key => $matching];
$this->db->or_where($match);
}
}
else
{
$this->db->or_where($key, $values);
}
}
}
else
{
$this->db->where($where);
}
}
return $this->db
->order_by('sword_name')
->get("sword_tb")
->result_array();
}
}
You might look at the block
foreach($values as $matching)
{
$match = [$key => $matching];
$this->db->or_where($match);
}
and question the use of or_where() before calling where(). Query Builder is smart enough to know if any other WHEREs have been added and won't put "OR" in front of "WHERE" if it's not needed.
Let's look at some usage examples.
$this->sword_model->getsword();
Produces the query statement
SELECT * FROM sword_tb ORDER BY sword_name
$this->sword_model->getsword('model, sword_name, cost', array('model' => 'Broad'));
produces
SELECT model, sword_name, cost FROM sword_tb WHERE model = Broad ORDER BY sword_name
$this->sword_model->getsword(NULL, array('model' => 'Broad', 'cost <' => 100));
SELECT * FROM sword_tb WHERE model = Broad AND cost < 100 ORDER BY sword_name
$this->sword_model->getsword('model, sword_name, cost',
array('model' => 'Broad, Samurai', 'sword_name' => 'Excalibur'), 'or');
SELECT model, sword_name, cost FROM sword_tb WHERE model = Broad OR model = Samurai OR sword_name = Excalibur ORDER BY sword_name
$this->sword_model->getsword(NULL,
array('model' => 'Broad, Samurai', 'sword_name' => ['Excalibur', 'Fred']), 'or');
SELECT * FROM sword_tb WHERE model = Broad OR model = Samurai OR sword_name = Excalibur OR sword_name = Fred ORDER BY sword_name
NOTE: I removed the back-tics from the generated queries string because here on SO it looked weird. Rest assured, everything is properly escaped.
If you're worried about speed, I benchmarked the above. The longest time required to build a query statement was 0.0007 second. To build all five queries required a total of 0.0022 seconds - an average of 0.00044 each.
Note, this is time to build the statement. The time to retrieve data is not included because, well... I don't have the data.
I'm building a small and simple PHP content management system and have chosen to adopt an MVC design pattern.
I'm struggling to grasp how my models should work in conjunction with the database.
I'd like to separate the database queries themselves, so that if we choose to change our database engine in the future, it is easy to do so.
As a basic concept, would the below proposed solution work, is there a better way of doing things what are the pitfalls with such an approach?
First, I'd have a database class to handle all MySQL specific pieces of code:
class Database
{
protected $table_name;
protected $primary_key;
private $db;
public function __construct()
{
$this->db = DatabaseFactory::getFactory()->getConnection();
}
public function query($sql)
{
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll();
}
public function loadSingle($id)
{
$sql = "SELECT * FROM $this->table_name WHERE $this->primary_key = $id";
return $this->query($sql);
}
public function loadAll()
{
$sql = "SELECT * FROM $this->table_name";
return $this->query($sql);
}
}
Second, I'd have a model, in this case to hold all my menu items:
class MenuItemModel
{
public $menu_name;
public $menu_url;
private $data;
public function __construct($data)
{
$this->data = $data;
$this->menu_name = $data['menu_name'];
$this->menu_url = $data['menu_url'];
}
}
Finally, I'd have a 'factory' to pull the two together:
class MenuItemModelFactory extends Database
{
public function __construct() {
$this->table_name = 'menus';
$this->primary_key = 'menu_id';
parent::__construct();
}
public function loadById($id)
{
$data = parent::loadSingle($this->table_name, $this->primary_key, $id);
return new MenuItemModel($data);
}
public function loadAll()
{
$list = array();
$data = parent::loadAll();
foreach ($data as $row) {
$list[] = new MenuItemModel($row);
}
return $list;
}
}
Your solution will work of course, but there are some flaws.
Class Database uses inside it's constructor class DatabaseFactory - it is not good. DatabaseFactory must create Database object by itself. However it okay here, because if we will look at class Database, we will see that is not a database, it is some kind of QueryObject pattern (see link for more details). So we can solve the problem here by just renaming class Database to a more suitable name.
Class MenuItemModelFactory is extending class Database - it is not good. Because we decided already, that Database is just a query object. So it must hold only methods for general querying database. And here you mixing knowledge of creating model with general database querying. Don't use inheritance. Just use instance of Database (query object) inside MenuItemModelFactory to query database. So now, you can change only instance of "Database", if you will decide to migrate to another database and will change SQL syntax. And class MenuItemModelFactory won't change because of migrating to a new relational database.
MenuItemModelFactory is not suitable naming, because factory purpose in DDD (domain-driven design) is to hide complexity of creating entities or aggregates, when they need many parameters or other objects. But here you are not hiding complexity of creating object. You don't even "creating" object, you are "loading" object from some collection.
So if we take into account all the shortcomings and correct them, we will come to this design:
class Query
{
protected $table_name;
protected $primary_key;
private $db;
public function __construct()
{
$this->db = DatabaseFactory::getFactory()->getConnection();
}
public function query($sql)
{
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll();
}
public function loadSingle($id)
{
$sql = "SELECT * FROM $this->table_name WHERE $this->primary_key = $id";
return $this->query($sql);
}
public function loadAll()
{
$sql = "SELECT * FROM $this->table_name";
return $this->query($sql);
}
}
class MenuItemModel
{
public $menu_name;
public $menu_url;
private $data;
public function __construct($data)
{
$this->data = $data;
$this->menu_name = $data['menu_name'];
$this->menu_url = $data['menu_url'];
}
}
class MenuItemModelDataMapper
{
public function __construct() {
$this->table_name = 'menus';
$this->primary_key = 'menu_id';
$this->query = new Query();
}
public function loadById($id)
{
$data = $this->query->loadSingle($this->table_name, $this->primary_key, $id);
return new MenuItemModel($data);
}
public function loadAll()
{
$list = array();
$data = $this->query->loadAll();
foreach ($data as $row) {
$list[] = new MenuItemModel($row);
}
return $list;
}
}
Also consider reading this:
DataMapper pattern
Repository pattern
DDD
I'm trying to write getFunction() in my Zend Model
Only works:
public function getFunction($id){
$Wiadomosc = new Application_Model_DbTable_Wiadomosc();
$nieodczytane = $Wiadomosc->select();
$nieodczytane->from(etc.....)
}
But i don't want to create object model in model!
I would like to do it in that way:
public function getFunction($id){
$nieodczytane = $this->select();
$nieodczytane->from(etc.....)
}
but..
Method "select" does not exist and was not trapped in __call()
how to do it?
As I know select() function is defined in Zend Model Structure already, which your model inherited from, so make it like this:
public function getFunction($id){
$nieodczytane = parent::select();
$nieodczytane->from(etc.....)
}
Although you question is well written and not easy to understand what you want to achieve by this. let me try to answer it.
If you are using the Zend DbTable. Let me try to explain how it works.
For Example you have the following model
//this class represents the table'Wiadomosc' in your Database scheme
class Wiadomosc{
protected $_name = 'Wiadomosc';
protected $_primary = 'id';
}
If you want write a get function regarding the model, normally you query by id and you
fetch the row you want get,
you do as it in the Zend Framework way.
/*** Get list.
* #param Integer $id
* #return Array
* #throws Exception if id could not be found.
*/
public function getFunction($id)
{
$id = (int)$id; //typcast
$row = $this->fetchRow('id = ' .$id);
if(!$row)
{
throw new Exception("Could not find $id");
}
return $row->toArray();
}
If that is not the case and your querying from another modal you could try the following.
public function getFunction($id)
{
$select = $this->select();
$select->from(array('w' => 'Wiadomosc'), array('column1','column2', 'column3'));
$select->setIntegrityCheck(false);
if($rows = $this->fetchAll($select)){
return $rows;
}elseif (empty($rows)){
$rows = $this->_name->select();
return $rows;
}
}
Edited:
You can do one of the following options:
public function getFunction($id){
$nieodczytane = $this->select();
$nieodczytane->from(array('t' => 'YourTableName'),array('*'));
if($rows = $this->fetchAll($nieodczytane)){
return $rows;
}
}
public function getFunction($id){
$nieodczytane = $this->select();
if($rows = $this->fetchAll($nieodczytane)){
return $rows;
}
}
Can I do 2 separate queries in a model in joomla 2.5 ?
If it is possible, how do you do this?
This is my model:
<?php
defined('_JEXEC') or die();
jimport( 'joomla.application.component.modellist' );
class AnagraficheModelServiziassociatiaggiuntivi extends JModelList
{
public function __construct($config = array())
{
if (empty($config['filter_fields']))
{
$config['filter_fields'] = array('id_servizi_aggiuntivi', 'id_persona', 'id_servizio', 'nome_servizio', 'cod_servizio', 'id_tipologia_socio', 'nome_servizio');
}
parent::__construct($config);
}
function getListQuery()
{
$db = JFactory::getDBO();
$query = $db->getQuery(true);
$query->select('id_servizi_aggiuntivi, id_persona , id_servizio , nome_servizio');
$query->from('#__servizi_aggiuntivi as serviziaggiuntivi, #__elenco_servizi');
$query->where('cod_servizio=id_servizio');
$result1 = $db->loadObjectList();
//$db->setQuery($query);
$query = $db->getQuery(true);
$query->select('id_tipologia_socio, id_servizio as cod_servizio, nome_servizio');
$query->from('#__associazione_servizi as serviziassociati, #__elenco_servizi');
$query->where('cod_servizio=id_servizio');
$result1 = $db->loadObjectList();
return $query;
}
protected function populateState($ordering = null, $direction = null)
{
// Load the filter state.
$search = $this->getUserStateFromRequest($this->context.'.filter.search', 'filter_search');
$this->setState('filter.search', $search);
// List state information.
parent::populateState($ordering, $direction);
}
}
?>
This doesn't work, but if I delete the second query it works great!
In my opinion, the problem is in the function getListQuery(). In this function I want insert 2 queries not 1 query! Is it possible?
The short answer is no. getListQuery should return a query. You can't get it to return two queries.
Instead, you likely want to override the getItems function to process the items after the first query runs. You can either adjust or add items using something like the following:
public function getItems() {
// load items from the parent getItems function, which will call getListQuery to handle your first query.
//It only adds items if the first query works.
if ($items = parent::getItems()) {
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select('id_tipologia_socio, id_servizio as cod_servizio, nome_servizio');
$query->from('#__associazione_servizi as serviziassociati, #__elenco_servizi');
$query->where('cod_servizio=id_servizio');
$result1 = $db->setQuery($query)->loadObjectList();
// this is a simple way to merge the objects. You could do other processing (loops, etc) to meld the information together
$items = (object) array_merge((array) $items, (array) $restult1);
}
return $items;
}
I have several doubts about OOP PHP code I'm creating. It is (so far) for retrieving a title and several chapters in different languages stored online. But first I will show the code as my doubts refer to this. This is the class I'm currently working with:
<?php
// Requires PHP 5.4+
class Subject
{
private $DB;
private $Language;
private $Keyword;
public function __construct($DB, $Keyword, $Language)
{
$this->DB=$DB;
$this->Keyword=$Keyword;
$this->Language=$Language;
}
private function query($query, $arg)
{
$STH = $this->DB->prepare($query);
$STH->execute(array_merge((array)$this->Keyword, (array)$arg));
return $STH->fetch()[$this->Language]; // PHP 5.4+
}
public function retrieveTitle ()
{
return $this->query("SELECT * FROM subject WHERE keyword = ? ORDER BY date DESC LIMIT 1");
}
public function retrieveChapter ($arg)
{
return $this->query("SELECT * FROM chapters WHERE subject_keyword = ? AND type = ? ORDER BY date DESC LIMIT 1", $arg);
}
?>
Then I do something similar to this to display the page:
if (isset($_GET['a']))
{
$Subject=new Subject($DB, $_GET['a'], $User->get('language'));
if ($Subject->retrieveTitle())
{
echo '<h1 id="Title">'.$Subject->retrieveTitle().'</h1>';
// Index
if ($Subject->retrieveTitle())
// ... code for the index
// Introduction
if ($Subject->retrieveChapter('Introduction'))
echo '<h2 id="Introduction">' . $_('Introduction') . '</h2>' . $Subject->retrieveChapter('Introduction');
// ... more non-relevant code.
}
}
else
// ... whatever
First concern. I'm not sure if this is the proper way to handle this kind of data. I tried to separate the methods and make them as small as possible, trying also not to repeat much code. And this is the way that feels right. But I cannot see why this other code, similar to the previous one, is less desirable. Note: This class has SURELY some typos and has not been tested, it's only here to ilustrate the difference, so please don't use it (at least not literaly):
<?php
// Requires PHP 5.4+
class Subject
{
public $Title;
public $Chapters = array ();
public function __construct($DB, $Keyword, $Language)
{
// Retrieve all
$STH = $DB->prepare("SELECT * FROM subject WHERE keyword = ? ORDER BY date DESC LIMIT 1");
$STH->execute(array($Keyword));
$this->Title = $STH->fetch()[$Language]; // PHP 5.4+
// Retrieve chapters
$ToForeach = ('Introduction','History','1');
$STH = $DB->prepare("SELECT * FROM chapters WHERE subject_keyword = ? AND type = ? ORDER BY date DESC LIMIT 1");
foreach ($ToForeach as $part)
{
$STH->execute(array($Keyword, $part));
$this->Chapters = $STH->fetch()[$Language];
}
}
}
?>
And then access the properties directly (or even build some get() in the middle, but you get the idea).
So, is there any difference? What are the benefits and pitfalls of the first method vs the second to code the class? Memory usage should be slightly smaller in the first one, but I think that won't be a deal breaker in comparison to readability in this case.
EDIT: Just writing the question in a way for others to understand has lead me to think about it in other ways. The first way also looks easier to test.
Second concern. If I want to make a/some methods for saving data, should I put it in the same class or in a different one? Because if I put it in one it bundles all the subject related methods in one pretty independent class, if I separate it I have more specialized classes with separated roles.
Any further advice, specifically about coding best practices [that I might not be following], is also welcome!
This is not easy to answer in a good way. So I can only focus on some minor parts of it. For not repeating code, I'd say the first example you give has quite some repeated code:
class Subject
{
/**
* #var ParametrizedQueryFetchQueryFactory
*/
private $queryFactory;
public function __construct($DB, $Keyword, $Language) {
$this->queryFactory = new ParametrizedQueryFetchQueryFactory($DB, $Language, [$Keyword]);
}
private function query($query, array $args = array()) {
return $this->queryFactory->query($query, $args);
}
public function retrieveTitle() {
return $this->query("SELECT * FROM subject WHERE keyword = ? ORDER BY DATE DESC LIMIT 1");
}
public function retrieveChapter($part) {
return $this->query(
"SELECT * FROM chapters WHERE subject_keyword = ? AND TYPE = ? ORDER BY DATE DESC LIMIT 1",
[$part]
);
}
}
class ParametrizedQueryFetchQueryFactory
{
private $db, $returnIndex, $defaultArgs;
public function __construct($db, $returnIndex, array $defaultArgs = array()) {
$this->db = $db;
$this->returnIndex = $returnIndex;
$this->defaultArgs = $defaultArgs;
}
public function query($query, array $args = array()) {
$fetcher = new ParametrizedQueryFetch($this->db,$query, $this->returnIndex, $this->defaultArgs);
return $fetcher->execute($args);
}
}
class ParametrizedQueryFetch
{
private $db, $query, $returnIndex, $defaultArgs;
public function __construct($db, $query, $returnIndex, array $defaultArgs = array()) {
$this->db = $db;
$this->query = $query;
$this->returnIndex = $returnIndex;
$this->defaultArgs = $defaultArgs;
}
public function execute(array $args) {
$args = array_merge($this->defaultArgs, $args);
$stmt = $this->db->prepare($this->query);
$stmt->excute($args);
return $stmt->fetch()[$this->returnIndex];
}
}
And btw, to make this PHP 5.3 compatible, you would only need to change a single line here.