Table joins accross multiple database tables and objects in PHP and MySQL - php

I have 4 classes, each with their own database tables:
GlobalObject
Image
Project
Page
GlobalObject has 3 properties
global_object_id
added_by
datetime_added
Image, Project and Page have many different fields, they ALL have the following though:
id
global_object_id (Foreign key)
At the moment, my class structure has Images, Projects and Pages as subclasses of the GlobalObject class, this allows the 3 subclasses access to the variables that are required of them, datetime_added etc.
How should I set the properties using PHP and MySQL? At the moment, ALL of the fields (including those in the global_object table) are in each section's table (except global_object_id which cannot exist without the new table - and is why I need to do this), So i request the data from the Image table for example, which includes all the properties in the parent class and is set using parent::set_by_row($database_args), and $this->set_by_row($database_args);
At the moment I do the following (Using Image as an example):
class Image extends GlobalObject
{
$id;
$title;
function set_by_id($id)
{
// Select Q from Image table
$this->set_by_row($db_result);
}
function set_by_row($args)
{
parent::set_by_row($args);
// Set class properties
}
}
To reiterate: I want to be able to do the following:
$image = new Image()
$image->set_by_global_object_id(23);
And the image be set, including everything in the parent class (which is stored in a separate table)
Please ask away if any of that is unclear.

A very convenient way is offered by those "magic methods", like __get and __set. The following example provides read-only access to the data, but can easily be extended with a __set method.
abstract class GlobalObject {
protected $_data = array();
/* Magic Method: Enable read access via $this->name */
public function __get($key) {
if (isset($this->_data[$key])) {
return $this->_data[$key];
}
throw new Exception('Property "'. $key .'" not found.');
}
protected function _setFromArray(array $data) {
// .. do whatever needs to be done with the data ..
$this->_data = $data;
}
}
Now just extend this class:
class Image extends GlobalObject {
public function setByGlobalId($id) {
$result = mysql_query('SELECT i.*, g.* FROM images AS i
LEFT JOIN global_objects AS g
ON g.global_object_id = i.global_object_id
WHERE i.global_object_id = '. intval($id) .' LIMIT 1');
// #todo check if result is empty
$data = mysql_fetch_assoc($result);
$this->_setFromArray($data);
}
}
And a simple example:
$image = new Image();
$image->SetByGlobalId(42);
echo $image->added_by;

Related

Find whether an Image is used anywhere

In my SilverStripe 3.4 environment I have a bunch of different models that have an attached Image, e.g.:
BlogPost has_one Image (via silverstripe/blog)
Widget has_one Image (via silverstripe/widgets)
MyWidget has_one Image (custom module)
I want to prevent an Image e.g. ID 123 from being deleted in the CMS admin if it's used in any of the above (as examples - this should be system wide).
Is there a way that I can check all models that have a related Image at once, maybe via a Image belongs_many_many lookup or something?
You'd prob need to decorate Image via a DataExtension subclass and declare a $belongs_to static array with a custom onBeforeDelete() or possibly validate().
Regardless, within either is where you'd call a routine that checks your database for the necessary conditions. Your choice of which method to use
is dictated by the scenarios under which an Image record might be deleted in your system (e.g you may have some automated, non-Human tasks where the scenario you wish to avoid is played out - so you'd avoid validate() and use onBeforeDelete())
Something like this (Totally untested!)
class MyImageExtension extends DatExtension
{
public function onBeforeDelete()
{
if (!$this->imagesExistThatShouldNotBeDeleted()) {
parent::onBeforeDelete();
}
}
/**
* #return boolean True if images exist that shouldn't be deleted, false otherwise.
*/
private function imagesExistThatShouldNotBeDeleted()
{
$owner = $this->getOwner();
$dataObjectSubClasses = ClassInfo::getValidSubClasses('DataObject');
$classesWithImageHasOne = [];
foreach ($dataObjectSubClasses as $subClass) {
if ($classHasOneImage = $subClass::create()->hasOneComponent('Image')) {
$classesWithImageHasOne[] = $classHasOneImage;
}
}
if (in_array($owner->class, $classesWithImageHasOne)) {
return true;
}
return false;
}
}

Implementing a S.O.L.I.D Domain Object Model in the following project

I have the following example in which I tend to use a couple of classes, to create a simple web app.
The file hierarchy seems like this.
> cupid
- libs
- request
- router
- database
- view
- bootstrap.php
- index.php
The index.php just calls the bootstrap.php which in turn contains something like this:
// bootstrap.php
namespace cupid
use request, router, database, view;
spl_autoload_register(function($class){ /* autoload */ });
$request = new view;
$response = new response;
$router = new router;
$database = new database;
$router->get('/blog/{id}', function($id) use ($database, $view) {
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?",[$id]);
$view->layout('blogPage', ['article'=>$article]);
});
As you can probably tell, my problem is this line:
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?", [$id]);
Which I don't want to use, and instead try a " Domain Object Model " approach.
Now, given that I will add another folder called domain, with blog.php
> cupid
- domain
- Blog.php
- libs
...
And fill blog.php with properties mapping table rows, and getter and setters ..
namespace App\Domain;
class Blog {
private $id, $title, $content, $author;
public function getTitle(){
return $this->title;
}
public function setTitle($title){
$this->title = $title;
}
...
}
My question is: Assuming my understanding of DOM is so far correct, and that I have a CRUD/ORM class, or a PDO wrapper to query the database;
"How can I tie together, i.e. the blog model with the PDO wrapper to fetch a blog inside my bootstrap file?"..
As far as a Domain Object you basically already have written one, your blog object. To qualify as a domain model all a class must to is to provide a representation along with any of the functionality of a concept within your problem space.
The more interesting problem here and the one you appear to be struggling with is how to persist a domain model. Keeping with the tenet of the single responsibility principle your Blog class should deal with being a blog post and doing the things that a blog post can do, not storing one. For that you would introduce the concept of a repository of blog posts that would deal with storing and retrieving objects of this type. Below is a simple implementation of how this can be done.
class BlogRepository {
public function __construct(\cupid\database $db){
$this->db = $db;
}
public function findById($id){
$blogData = $this->db->select("select * from blog where id = ?", [$id]);
if ($blogData){
return $this->createBlogFromArray($blogData);
}
return null;
}
public function findAllByTag($tag){...}
public function save(Blog $blog) {...}
private function createBlogFromArray(array $array){
$blog = new Blog();
$blog->setId($blogData["id"]);
$blog->setTitle($blogData["title"]);
$blog->setContent($blogData["content"]);
$blog->setAuthor($blogData["author"]);
return $blog;
}
}
Then your controller should look something like this.
$router->get('/blog/{id}', function($id) use ($blogRepository, $view) {
$article = $blogRepository->findById($id);
if ($article) {
$view->layout('blogPage', ['article'=>$article]);
} else {
$view->setError("404");
}
});
To truly be SOLID the above class should be a database specific implementation of a BlogRepository interface to adhere to IoC. A factory should also probably be supplied to BlogRepository to actually create the blog objects from data retrieved from the store.
In my opinion one of the great benefits of doing this is you have a single place where you can implement and maintain all of your blog related interactions with the database.
Other Advantages to this method
Implementing caching for your domain objects would be trivial
Switching to a different data source (from flat files, blogger api, Document Database Server,PostgresSQL etc.) could be done easily.
You can alternatively use a type aware ORM for a more general solution to this same problem. Basically this Repository class is nothing more than a ORM for a single class.
The important thing here is that you are not talking directly to the database and leaving sql scattered throughout your code. This creates a maintenance nightmare and couples your code to the schema of your database.
Personally I always tend to stick the database operations in a database class which does all the heavy lifting of initialising the class, opening the connection etc. It also has generic query-wrappers to which I pass the SQL-statements which contains the normal placeholders for the bound variables, plus an array of the variables to be bound (or the variable number of parameters approach if thats suits you better). If you want to bind each param individually and not use the $stmt->execute(array()); You just pass in the types with the value in a data structure of your choosing, multi dim array, dictionary, JSON, whatever suits your needs and you find easy to work with.
The model class it self (Blog in your case) then subclasses the Database. Then you have a few choices to make. Do you want to use the constructor to create only new objects? Do you want it to only load based on IDs? Or a mix of both? Something like:
function __construct(id = null, title = null, ingress = null, body = null) {
if(id){
$row = $this->getRow("SELECT * FROM blog WHERE id = :id",id); // Get a single row from the result
$this->title = $row->title;
$this->ingress = $row->ingress;
$this->body = $row->body;
... etc
} else if(!empty(title,ingress,body)){
$this->title = title;
... etc
}
}
Maybe neither? You can skip the constructor and use the new(title, ingress, body), save() and a load(id) methods if thats your preference.
Of course, the query part can be generalised even further if you just configure some class members and let the Database-superclass do the query building based on what you send in or set as member-variables. For example:
class Database {
$columns = []; // Array for storing the column names, could also be a dictionary that also stores the values
$idcolumn = "id"; // Generic id column name typically used, can be overridden in subclass
...
// Function for loading the object in a generic way based on configured data
function load($id){
if(!$this->db) $this->connect(); // Make sure we are connected
$query = "SELECT "; // Init the query to base string
foreach($this->columns as $column){
if($query !== "SELECT ") $query .= ", "; // See if we need comma before column name
$query .= $column; // Add column name to query
}
$query .= " FROM " . $this->tablename . " WHERE " . $this->idcolumn . " = :" . $this->idcolumn . ";";
$arg = ["col"=>$this->idcolumn,"value"=>$id,"type"=>PDO::PARAM_INT];
$row = $this->getRow($query,[$arg]); // Do the query and get the row pass in the type of the variable along with the variable, in this case an integer based ID
foreach($row as $column => $value){
$this->$column = $value; // Assign the values from $row to $this
}
}
...
function getRow($query,$args){
$statement = $this->query($query,$args); // Use the main generic query to return the result as a PDOStatement
$result = $statement->fetch(); // Get the first row
return $result;
}
...
function query($query,$args){
...
$stmt = $this->db->prepare($query);
foreach($args as $arg){
$stmt->bindParam(":".$arg["col"],$arg["value"],$arg["type"]);
}
$stmt->execute();
return $stmt;
}
...
}
Now as you see the load($id), getrow($query,$args) and query($query,$args) is completely generic. ´getrow()´is just a wrapper on query() that gets the first row, you may want to have several different wrappers that to or interpret your statement result in different ways. You may also even want to add object specific wrappers to your models if they cannot be made generic. Now the model, in your case Blog could look like:
class Blog extends Database {
$title;
$ingress;
$body;
...
function __construct($id = null){
$this->columns = ["title","ingress","body","id",...];
$this->idcolumn = "articleid"; // override parent id name
...
if($id) $this->load($id);
}
...
}
Use it as so: $blog = new Blog(123); to load a specific blog, or $blog = new Blog(); $blog->title = "title"; ... $blog->save(); if you want a new.
"How can I tie together, i.e. the blog model with the PDO wrapper to fetch a blog inside my bootstrap file?"..
To tie the two together, you could use an object-relational mapper (ORM). ORM libraries are built just for glueing your PHP classes to database rows. There are a couple of ORM libraries for PHP around. Also, most ORMs have a built in database abstraction layer, which means that you can simply switch the database vendor without any hassle.
Considerations when using an ORM:
While introducing a ORM also introduces some bloat (and some learning), it may not be worthwhile investing the time for simply a single Blog object. Although, if your blog entries also have an author, one or multiple categories and/or associated files, an ORM may soon help you reading/writing the database. Judging from your posted code, an ORM will pay off when extending the application in the future.
Update: Example using Doctrine 2
You may have a look at the querying section of the official Doctrine documentation to see the different options you have for read access. Reconsider the example you gave:
// current implementation
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?",[$id]);
// possible implementation using Doctrine
$article = $em->getRepository(Blog::class)->find($id);
However, ideally you define your own repository to separate your business logic from Doctrines API like the following example illustrates:
use Doctrine\ORM\EntityRepository;
interface BlogRepositoryInterface {
public function findById($id);
public function findByAuthor($author);
}
class BlogRepsitory implements BlogRepositoryInterface {
/** #var EntityRepository */
private $repo;
public function __construct(EntityRepository $repo) {
$this->repo = $repo;
}
public function findById($id) {
return $this->repo->find($id);
}
public function findByAuthor($author) {
return $this->repo->findBy(['author' => $author]);
}
}
I hope the example illustrates how easily you can separate your business domain models and logic from the underlying library and how powerful ORMs can come into play.

Getting data from database to model

So far I've used many objects in my applications but often if I had to for example display for example users' profiles on page I simply got 20 users from database as array using some method in my object and assigned it to view.
Now I want to create application more with models that represent real data. So for each user I should probably have User object with properties .
Here I put sample code to get users from database and to display them in PHP:
<?php
$db = new mysqli('localhost', 'root', '', 'mytest');
class User
{
private $id;
private $name;
public function __construct($data)
{
foreach ($data as $k => $v) {
if (property_exists($this, $k)) {
$this->$k = $v;
}
}
}
public function show()
{
return $this->id . ' ' . $this->name ;
}
}
$result = $db->query("SELECT * FROM `user` LIMIT 20");
$users = array();
while ($data = $result->fetch_object()) {
$data->x = 10; // just to test if property isn't created
$users[] = new User($data);
}
// displaying it on page
foreach ($users as $user) {
echo $user->show() . "<br />";
}
Questions:
Is it the way I should use data from database into model? I mean if should I create object for each record even if the only role of this object would be returning some data to view (for example even not modified by any functions as in this example). Of course I know that often data should be prepared to display or made some calculations or additional data should be retrieved from database before those data could be used to display.
Is there any way to assign object properties from database simpler than using constructor with loop?
First of all, i'd move the DB operations in a separate class, not inline with the User class. You could create an abstract Model class, which the User class would extend and add DB logic to it.
You'd have a select() method to query the database, which would return an array of objects of the class that extended the Model class (the User class in this case).
To answer your questions:
I think it's ok. Most ORMs work this way.
An alternative would be to assign the user row data from the DB to a $data attribute in your User class and use the magic methods __get and __set to access them.

PHP OOP: Good practice for fetching from database with object constructors

I have two database tables Players and Units. Respectively I also have two classes in PHP that look pretty identically in the base.
class Player {
private $id;
private $name;
// a bunch of other properties
public function __construct($id){
// fetch info from database with $id and populate properties
}
}
 
class Unit {
private $id;
private $name;
// a bunch of other properties
public function __construct($id){
// fetch info from database with $id and populate properties
}
}
But one player can have multiple units, so I implemented the method loadUnits() in the Player class
public function loadUnits(){
$units = array();
foreach($db->prepare('SELECT id FROM units WHERE owner = ?')->execute([$this->id])->fetchAll() as $unit){
$units[] = new Unit($unit['id']);
}
return $units;
}
The problem is that constructing Unit X number of times will make X number of calls to the database and this is really something I don't like. I would like to ask what are the good practices and how is this done in reality? Thanks!
Instead of just selecting the id in loadUnits and then issuing another query for the properties per id, I recommend getting all unit properties with a single query and passing those properties to the constructor of Unit
//don't just get the id's, get the actual unit properties
public function loadUnits(){
$units = array();
foreach($db->prepare('SELECT <all properties> FROM units WHERE owner = ?')->execute([$this->id])->fetchAll() as $properties){
$units[] = new Unit($properties['id'],$properties);
}
return $units;
}
class Unit {
private $id;
private $name;
// a bunch of other properties
//make the unit properties an optional parameter and use it
//instead of querying the db if available
public function __construct($id,$properties=null){
if(is_null($properties)) {
// fetch info from database with $id and populate properties
}
else {
// populate via $properties
}
}
}
I have a similar problem in one of my projects. Calling Room->computeDoors() will run a query to find what Doors are attached to the Rooms, then for each of them it has to run a query to find out what the Door's properties are... and then it has to query each room to find out what the door is connected to!
Problem solved: Memcache. Store as much as you can in cache, that way the data's already there to be used no matter how many times you need it, even across pageloads/AJAX calls, or even across users! Just make sure to invalidate or update the cache when you update the object's state.
You can solve this bei either using an ORM like Doctrine or Eloquent.
In Doctrine you define the relations between your entities, it will automatically generate the SQL and tables, it will generate proxies containing findBy() methods, give notes about wrong relations or missing values.
Doctrine has implemented different fetching methods and caching for example persistence of entities and lazy loading. You define your model and Doctrine takes care of everything else the most stable and fastest way.
If you want to implement your own, you should cache the results locally in the instance.
private $units;
protected function loadUnits(){
$units = array();
foreach($db->prepare('SELECT id FROM units WHERE owner = ?')->execute([$this->id])->fetchAll() as $unit){
$units[] = new Unit($unit['id']);
}
$this->setUnits($units);
return $this;
}
public function setUnits($units) {
assert(is_array($units));
$this->units = $units;
return $this;
}
public function getUnits() {
// this if needs improvement to fit your needs
if (!is_array($this->units)) {
$this->loadUnits();
}
return $this->units;
}

How to self-initialize Doctrine_Record (as if Doctrine_Query would)?

I have models that extend Doctrine_Record, and are directly mapped to one specific record from the database (that is, one record with given a given id, hardcoded statically in the class).
Now, I want the specific record class to initialize itself as if Doctrine_Query would. So, this would be the normal procedure:
$query = new Doctrine_Query();
$model = $query->from('Model o')->where('id = ?', 123)->fetchOne();
I would like to do something like this
$model = new Model();
And in the Model:
const ID = 123;
//note that __construct() is used by Doctrine_Record so we need construct() without the __
public function construct()
{
$this->id = self::ID;
//what here??
$this->initialize('?????');
}
So for clarity's sake: I would like the object to be exactly the same as if it would be received from a query (same state, same attributes and relations and such).
Any help would be greatly appreciated: thank you.
The first thing I need to say is I'd put the constant in the class. So like this:
class Application_Model_Person
{
const ID = 1234;
}
Then, a Doctrine method like Doctrine_Record::fetchOne() is always returning a (new) instance of the model and never merges the data with the record you're calling fetchOne() to. Doctrine is nevertheless able to merge a retreived record with another class, so it rather simple to do:
class Application_Model_Person extends Doctrine_Record_Abstract
{
const ID = 1234;
public function __construct($table = null, $isNewEntry = false)
{
// Calling Doctrine_Record::__construct
parent::__construct($table, $isNewEntry);
// Fetch the record from database with the id self::ID
$record = $this->getTable()->fetchOne(self::ID);
$this->merge($record);
}
}
Then you're able to do:
$model = new Application_Model_Person;
echo $model->id; // 1234
Although having multiple classes for the same data type (i.e. table) is really not what ORM should be like, what you want can be done in Doctrine using Column aggregation inheritance. Assuming you are using Doctrine 1.2.x, you can write the following YML:
Vehicle:
columns:
brand: string(100)
fuelType: string(100)
Car:
inheritance:
extends: Entity
type: column_aggregation
keyField: type
keyValue: 1
Bicycle:
inheritance:
extends: Entity
type: column_aggregation
keyField: type
keyValue: 2
Now, the Vehicle table will have a 'type' column, that determines the class that Doctrine will instantiate when you select a vehicle. You will have three classes: Vehicle, Car and Bicycle. You can give every class their own methods etc, while the records their instances represent reside in the same database table. If you use $a = new Bicycle, Doctrine automatically sets the type for you so you don't have to take care of that.
I don't think a model instance could decide to hang on a certain database entry after it has been initialized. That said, you can do something like this:
<?php
class Model extends baseModel {
public static function create($id = null)
{
if ($id === null) return new Model;
return Doctrine::getTable('Model')->findeOneById($id);
}
}
And then, you can either use
$newModel = Model::create();
Or fetch an existing one having ID 14 (for example) using
$newModel = Model::create(14);
Or, if you want your 123 to be default instead of a new item, declare the function like this:
public static function create($id = 123)

Categories