Learning OOP - distributing logic and dividing responsibility - php

Trying to move from global procedural coding to a more OOP-oriented style, I'm refactoring a website for a magazine.
One of the conditions to check on the server is whether an article id or a combination of year/issue has been passed in. In the first case the user should be served a specific article, in the second case the table of contents and the image cover for the selected issue. I also need to retrieve the year and issue for display purposes in the second case.
My first instinct was to put all the the logic inside the constructor:
class Magazine {
public $year;
public $issue;
public function __construct($year, $issue, $articleId = 0) {
if($articleId > 0) {
// Get article from database
$this->year = // get year from search results;
$this->issue = // get number from search results;
} else {
// Get table of contents and cover image
}
}
}
// Create an instance either like this:
$magazine = new Magazine(1985, 3);
// Or like this:
$magazine = new Magazine(0, 0, 172);
But then I realized that it's probably not a good idea to "overload" the constructor in this way (to compensate for not being able to overload it in the usual sense?).
Ok, so I create a method for retrieving the article, like so:
class Magazine {
public $year;
public $issue;
private $articleId;
public function __construct($year, $issue, $articleId = 0) {
$this->year = $year;
$this->issue = $issue;
$this->articleId = $articleId;
}
public function getArticle() {
// Use $this->articleId to retrieve data from database
// Set $this->year and $this->issue to appropriate values
}
}
Or without a constructor:
class Magazine {
public $year;
public $issue;
public function getArticle($articleId) {
// Use $articleId to retrieve data from database
// Set $this->year and $this->issue to appropriate values
}
}
No, assigning values to the year and issue properties is not part of getting an article. So I create another method for that:
public function getYearAndIssue($articleId) {
// Use $articleId to retrieve data from database
}
But now I need to connect to the database twice. Not good. It seems like I have to collect all the data I need when connecting to the db the first time. And where do I do that? In the constructor? No, that will just take me back to square one. Or minus one.
So where do I go from here? Two methods like getArticleAndEverythingElseINeedForThat() { // ... } and getCoverAndEverythingElseINeedForThat() { ... }?
EDIT: Thanks to Daan’s comment I've experimented some more and come up with the following:
class Magazine {
private $db;
public function __construct($db) {
$this->db = $db;
}
public function getArticle($articleId) {
$sql = "SELECT article FROM table WHERE id = :id";
$ph = array(':id' => $articleId);
$ps = $this->db -> prepare($sql);
$ps -> execute($ph);
// Do some more stuff
return $article;
}
public function getYearAndIssue($articleId) {
$sql = "SELECT year, issue FROM table WHERE id = :id";
$ph = array(':id' => $articleId);
...
return array($year, $issue);
}
}
class Database {
public static function getConnection() {
$db = new PDO(...);
$db->setAttribute(...);
...
return $db;
}
}
$db = Database::getConnetion();
$magazine = new Magazine($db);
$article = $magazine->getArticle(59);
list($year, $issue) = $magazine->getYearAndIssue(59);
New questions:
Do I seem close to understand injection?
In some descriptions I've seen the PDO object is created both as a private variable in the constructor and passed as an argument to the helper functions, i.e. as if I'd written my code like this:
class Magazine {
private $db;
public function __construct($db) {
$this->db = $db;
}
public function getArticle($db, $articleId) {
// ...
}
}
$db = Database::getConnetion();
$magazine = new Magazine($db);
$article = $magazine->getArticle($db, 59);
Should I do that too? If so, why?
Even if I use the same db connection twice (or more times in the real scenario), I still have to get stuff from the db several times. Wouldn't it be better/faster to get everything I need from the db at once (if it would be possible without messing up the logic again/any further)? Like this:
$sql = "SELECT article, year, issue FROM table WHERE id = :id";

Related

PHP MVC Model Relations - MySQL

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

PHP- How to get last inserted ID from mysqli query inside a class

I know returning the last inserted ID when running a straight query is simple:
$query = $db->query("INSERT INTO site_stats (PageView) VALUES('1')");
if ($query) {
$id = $db->insert_id;
echo 'The ID is: '.$id;
}
However, I am running all of my queries through a class which helps me keep track of how many queries I am executing and how long it's taking. The class is simple and straightforward:
class dbLog {
public $queries = array();
public function query($sql) {
global $db;
$start = microtime(true);
$query = $db->query($sql);
$this->queries[] = microtime(true) - $start;
return $query;
}
public function getCount() {
return sizeof($this->queries);
}
public function getTime() {
return number_format(array_sum($this->queries), 5);
}
} // end dbLog class
$dbLog = new dbLog;
The problem is, when I run queries through this class, I can not figure out how to return the last inserted ID:
$query = $dbLog->query("INSERT INTO site_stats (PageView) VALUES('1')");
if ($query) {
$id = $dbLog->insert_id;
echo 'The ID is: '.$id; // Returns error
}
The error I get returned is Undefined property: dbLog::$insert_id
How can I get the ID of the last inserted row?
Since your class is just using $db from the global scope, you can use $db->insert_id as you originally did.
To do this in a proper manner you'd want to actually wrap the $db object inside your own class / object, either by extending (inheriting) from the original class and then instantiating your own, or by implementing the methods you want to expose through your own class, and giving $db to the constructor and keeping the reference internally.
You're wrapping the mysqli class in another class (you're also using global, which is not ideal)
What you need is a method to get the ID from the actual mysqli instance
public function getId() {
global $db;
return $db->insert_id;
}
If I were you I'd use dependency injection and make your mysqli instance part of the class itself
class dbLog {
/** #var \mysqli */
protected $db;
public function __construct(\mysqli $db) {
$this->db = $db;
}
public function query($sql) {
$start = microtime(true);
$query = $this->db->query($sql);
$this->queries[] = microtime(true) - $start;
return $query;
}
public function getId() {
return $this->db->insert_id;
}
}

PDO FETCH_CLASS multiple rows into array of objects

I'm trying to fetch multiple rows of bookings from a database and I want each one to be an instance of a specific class - so I attempted to store them within a multidimensional array.
So far it works in terms of creating the array of objects, however I need the index for the array to be the booking ID so that I can easily access each one. For example:
$bookings[id] => booking Object ( [name:protected] => name, [time:protected] => time )
Is this possible and is it the best way to go about what I want to achieve? Any help would be hugely appreciated, here's the code:
class create_bookings {
private $db;
protected $error;
function __construct($db, $current, $end) {
$this->db = $db;
$query = "SELECT id, name, time
FROM bookings";
$stmt = $this->db->prepare($query);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_CLASS, 'booking');
print_r($result);
}
}
...and the 'booking' class is just a set of properties:
class booking {
private $db;
protected $name;
protected $time;
}
Instead of building a class to create the booking objects, it would be advisable to either create a standalone function, or a function within a parent class to achieve this. The issue I run in to with using fetchAll() with the FETCH_CLASS parameter, is that the objects do not automatically have access to the database object, causing you to need to iterate over the objects afterwards anyways, so I simply build the __construct() method to capture the $db and generated array.
Assuming you go the route of a standalone function:
function create_bookings($db, $current, $end) {
$query = "SELECT id, name, time
FROM bookings";
$stmt = $db->prepare($query);
$stmt->execute();
$result = array()
$bookings = $stmt->fetchAll();
if($bookings) {
foreach($bookings as $booking) {
$result[$booking['id']] = new booking($db, $booking);
}
}
return $result;
}
class booking {
private $db;
public $id;
public $name;
public $time;
public function __construct($db, $array) {
if(/*validate your array here*/) {
$this->db = $db;
$this->id = $array['id'];
$this->name = $array['name'];
$this->time = $array['time'];
}
}
}
Yes there is a way to do this.
The trick is PDO::FETCH_GROUP. Just do
$result = $stmt->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_CLASS, 'booking');
The only downside to this is that there will be an array in every result before the Object
There's a simple way to remove the array too
$output= array_map('reset',$result);

PHP get function's result from class itself?

I'm wondering how to receive the results from a function "from the class itself". An example of this is the PDO functions, where I can do the following to get i.e. the last ID:
$db->query($sql);
$id = $db->lastInsertId();
Right now I have to do the following:
$newThread = $forums->newThread('title','category');
$id = $newThread['id'];
Of course this works great, but I have to use the variable $newThread, which I don't want to. How do I save the value in order to call it later?
In case you have problems understanding how the PDO version works, it's roughly like this:
class PDO {
private $lastInsertId;
public function query($sql) {
magic_sql_api_call($sql);
/* here be dragons */
$this->lastInsertId = magic_sql_api_get_last_insert_id();
}
public function lastInsertId() {
return $this->lastInsertId;
}
}
You can create code like this
class Forums {
private $id;
...
function createTread($title, $category) {
$newThread = $forums->newThread($title, $category);
$this->id = $newThread['id'];
}
function lastId() {
return $this->id;
}
}
You can use it
$forums->createTread('title','category');
$id = $forums->lastId();
You probably will need to save $newThread in property too.

Object Oriented Programming accessing variable values, How to?

I am a learner, I have a class db to help me connect and fetch results in mySQL.
$set = $db->get_row("SELECT * FROM users");
echo $set->name;
this way i use echo results outside a class.
Now i have created another class name user and it has this function
public function name() {
global $db;
$set = $db->get_row("SELECT * FROM users");
$this->name = $set->name;
}
after initializing the class user, when i try to echo $user->name i dont get expected results.
Note i have declared above var $name; in class user
I'm pretty concerned by several things I see here
The method name name() is terribly uncommunicative as to what the method is supposed to do. Remember, methods are actions - try to give them some sort of verb in their name.
Usage of global in a class (or even usage of global period) when you should be using aggregation or composition.
You don't show any execution examples, but I can only assume you never actually call User::name(), which is why your test is failing
Here's some code that addresses these concerns.
<?php
class DB
{
/* Your methods and stuff here */
}
class User
{
protected $db;
protected $name;
public function __construct( DB $db )
{
$this->db = $db;
}
public function getName()
{
if ( is_null( $this->name ) )
{
$set = $this->db->get_row( "SELECT * FROM users" );
$this->name = $set->name;
}
return $this->name;
}
}
$db = new DB();
$user = new User( $db );
echo $user->getName();
class DB
{
public function get_row($q)
{
# do query and store in object
return $object;
}
}
class User
{
public $name;
public function __construct()
{
$this->name();
}
public function name() {
global $db;
$set = $db->get_row("SELECT * FROM users");
echo "<pre>".print_r($set)."</pre>"; # make sure $set is returning what you expected.
$this->name = $set->name;
}
}
$db = new DB();
$user = new User();
echo $user->name;
I am very much sorry, i figured out that problem was on my part, i was using cookies and had two cookies set which were giving problems :(

Categories