Database results as objects or arrays? - php

This question is similar to Mysql results in PHP - arrays or objects? However, my question expands on what has been discussed there.
I'm trying to decide which format is better for working with database results: objects or arrays. I'm not concerned about performance (from what I understand it makes little difference). My focus is also more on displaying the results—not creating, updating or deleting them.
To date I've always used objects, via functions like mysqli_fetch_object or PDO's fetchObject. This normally works nice, until I start doing joins. Joins lead to strange objects that are a blend of fields from two or more tables. My code quickly starts getting confusing.
I should note, I'm assigning specific class names, and not sticking with the default stdClass. I do this so that I can access any helper methods I've created in my classes. For example:
foreach ($members as $member)
{
echo $member->full_name();
echo $member->age();
}
For the sake of clarity, I'm considering moving to arrays for all my database results. From what I've read others do this as well. However, this leaves me with no easy way to access my helper methods.
Using the above example, I guess I could just output both the first and last name instead of using the full_name() method, not a big deal. As for the age() method, I guess I could create a generic utility class and put it in there.
My questions:
If you use (model) objects, how do you deal with joins?
If you use arrays, how do you deal with helper methods?

I've always used objects - but I don't put the data in directly from the query. Using 'set' functions I create the layout and so avoid issues with joins and naming collisions. In the case of your 'full_name' example I would probably use 'as' to get the name parts, set each in the object and offer 'get_full_name' as a member fn.
If you were feeling ambitious you could add all sorts of things to 'get_age'. Set the birth date once and go wild from there.
EDIT:
There are several ways to make objects out of your data. You can predefine the class and create objects or you can create them 'on the fly'.
--> Some v simplified examples -- if this isn't sufficient I can add more.
on the fly:
$conn = DBConnection::_getSubjectsDB();
$query = "select * from studies where Status = 1";
$st = $conn->prepare( $query );
$st->execute();
$rows = $st->fetchAll();
foreach ( $rows as $row )
{
$study = (object)array();
$study->StudyId = $row[ 'StudyId' ];
$study->Name = $row[ 'StudyName' ];
$study->Investigator = $row[ 'Investigator' ];
$study->StartDate = $row[ 'StartDate' ];
$study->EndDate = $row[ 'EndDate' ];
$study->IRB = $row[ 'IRB' ];
array_push( $ret, $study );
}
predefined:
/** Single location info
*/
class Location
{
/** Name
* #var string
*/
public $Name;
/** Address
* #var string
*/
public $Address;
/** City
* #var string
*/
public $City;
/** State
* #var string
*/
public $State;
/** Zip
* #var string
*/
public $Zip;
/** getMailing
* Get a 'mailing label' style output
*/
function getMailing()
{
return $Name . "\n" . $Address . "\n" . $City . "," . $State . " " . $Zip;
}
}
usage:
$conn = DBConnection::_getLocationsDB();
$query = "select * from Locations where Status = 1";
$st = $conn->prepare( $query );
$st->execute();
$rows = $st->fetchAll();
foreach ( $rows as $row )
{
$location = new Location();
$location->Name= $row[ 'Name' ];
$location->Address = $row[ 'Address ' ];
$location->City = $row[ 'City' ];
$location->State = $row[ 'State ' ];
$location->Zip = $row[ 'Zip ' ];
array_push( $ret, $location );
}
Then later you can loop over $ret and output mailing labels:
foreach( $ret as $location )
{
echo $location->getMailing();
}

It’s preference at the end of the day. Personally, I prefer objects. Although CakePHP uses arrays for results using the “object” name as the array key. However, things start to get funny when you fetch related records in CakePHP.
With your problem, you could simply have objects within objects. For example:
stdClass Object
(
[id] => 1
[title] => Article Title
[published] => 2013-03-04 16:30:00
[category] => stdClass Object
(
[id] => 1
[name] => Category Name
)
)
You can then display associated data in your views as follows:
<?php echo $article->category->name; ?>
Or if you use getters and setters:
<?php echo $article->getCategory()->getName(); ?>
There’s no right or wrong answer. As I say, it’s all personal preference.

I think its better to represent all of your datas and its type in form of Model. For both joined and singular objects. Doing so will always omit your problem.
class Member_Details {
public $id;
public $first_name;
public $last_name;
public function FullName() {
return $this -> first_name." ".$this -> last_name;
}
}
class Member_Address {
public $id;
public $address;
public $city;
}
class MemberJoins {
public $objects = array();
}
After creating such classes you can configures a JOIN in the following way.
$obj_details = new Member_Details();
$obj_address = new Member_Address();
//Add data to the objects and then
//Then create the join object
$obj_address_details = new MemberJoins();
$obj_address_details -> objects = array($obj_details, $obj_address);
These both have a common property id from which its data can be linked.

I think you are talking about weird objects coming up using SELECT on two or more tables.
I solve this by using AS in my sql to give it a more simple name.
SELECT IFNULL(SUM(table2.big_name),0) AS sumBig
...
$result=$PDO->fetchObject();
$sum=$result->sumBig;

Related

Assign Array to Property in PHP constructor function

Is it possible to store an array as an object property in PHP?
I am building an article class that pulls various information about a research article and stores them as a properties in an object. Since the number of authors vary per research article, I would like to store them as an array in an $authors property, rather than store each author as a separate property. In this code sample, I realize this problem results from working with a poorly designed table, but nonetheless, I would like to see how this code could be used to store an array as an object property.
<?php
Class Article {
public $id;
public $authors;
public $article_name;
public $journal;
public $volume_number;
public $issue_number;
public $article_location;
public function __construct($id, array $authors, $article_name, $journal,
$volume_number, $issue_number, $article_location)
{
$this->$id = $id;
$this->$authors = $authors;
$this->$article_name = $article_name;
$this->$journal = $journal;
$this->$volume_number = $volume_number;
$this->$issue_number = $issue_number;
$this->$article_location = $article_location;
}
}
//function to pull Article information from Articles Table
function getArticle($id){
try {
$query = "SELECT * FROM articles WHERE ID = :ID";
$db = Db::getInstance();
$results = $db->prepare($query);
$results->execute([':ID'=>$id]);
$row = $results->fetch();
$authors = array();
if(!empty($row['author'])){
$authors[] = $row['author'];
}
if(!empty($row['author2'])){
$authors[] = $row['author2'];
}
if(!empty($row['author3'])){
$authors[] = $row['author3'];
}
//This repeats for a while.
return new article($row['ID'],
$authorList,
$row['article_name'],
$row['journals'],
$row['volume_number'],
$row['issue_number'],
$row['article_location']);
} catch (PDOException $e) {
return "Unable to pull articles from the Articles table.";
echo $e->getMessage();
}
}
Yes, it is possible to store an array as a property.
The problem is that you use properties wrong.
$this->$authorList
Is wrong, you should use:
$this->authorList
Your code currently creates properties for your class based on the original property's value - if $article_name has the value of 'ABCD', $this->$article_name creates and fills the property 'ABCD' - being the equivalent of $this->ABCD = $article_name;, meaning you won't be able to access the value in the original property. It's the same with $this->$authors = $authors; - if you are passing an array as $authors, your code will try to store it as a string, making the situation even worse. Removing the $ before $authors solves this issue too.
Also, when you use $authorList[], you are pushing values into a local variable, not into the class property. It's not necessarily the wrong way to do it, as long as you copy the local variable's content into the property, but I would strongly suggest not to use variables named after properties. It makes your code harder to maintain, as it can confuse developers.

Zend Framework 2/3 model with relations to itself and another model

First I've read the following two stackoverflow questions, but they didn't really give me an answer:
How to extend the ZF2 skeleton application - entities with foreign keys
Zend Framework 2 model foreign keys
In my application I have an employee database table, which has numerous properties but the most interesting for me currently is the manager_id and the bank_id which are foreign keys.
manager_id is a foreign key to another employee (as you can imagine an employee can have one manager)
bank_id is a foreign key to another model/db-table called bank - because an employee can have a bank account ;-)
Now in my EmployeeTable.php file I have those magic methods where I get the database results.
To get one employee I do this:
/**
* #param int $id
*
* #return Employee
*/
public function getEmployeeById($id)
{
$rowset = $this->tableGateway->select(['id' => (int) $id]);
/** #var Employee $row */
$row = $rowset->current();
if (!$row) {
throw new RuntimeException(sprintf(
'Could not find row with identifier %d',
(int) $id
));
}
return $row;
}
But without any sql joins I only have the manager_id and bank_id in my returned employee object.
Question: What is the best practise to get those needed information?
So far I have two thoughts:
First
Should I - if the $row isn't empty - call for e.g the bankTable object (via dependency injection) which has an getBankById method.
Then I'll extend my Employee.php model with an $bank property with a getter/setter and before the return statement in my getEmployeeId method I would do something like this:
$row->setBank($this->bankTable->getBankById($row->bank_id));
But I'm afraid of a recursive loop doing this for the manager_id because I would call the same method I'm currently in.
Second
Or should I extend my getEmployeeById method with a left join to get the data from the bank table like this:
$select = $this->tableGateway->getSql()->select()
->join(['b' => 'bank'], 'bank_id = m.id',
[
'bank.id' => 'id',
'bank.description' => 'description',
'bank.bic' => 'bic',
],
Select::JOIN_LEFT)
->join(['m' => 'employee'], 'manager_id = m.id',
[
'manager.id' => 'id',
'manager.forename' => 'forename',
'manager.surname' => 'surname',
// and all the other properties
],
Select::JOIN_LEFT);
$result = $this->tableGateway->selectWith($select);
$row= $result->current();
$resultSet = $this->hydrator->hydrate($this->hydrator->extract($row), $row);
Unfortunately I have to give my joined columns alias names else I would overwrite the id from the employee with the bank id etc.
After this kind of sql statement you can see, that I would extract the result to get the properties as values and then hydrate them.
Hydration would look like this:
/**
* #param array $data
* #param Employee $object
*
* #return Employee
*/
public function hydrate(array $data, $object)
{
if (!$object instanceof Employee) {
throw new \BadMethodCallException(sprintf(
'%s expects the provided $object to be a PHP Employee object)',
__METHOD__
));
}
$employee = new Employee();
$employee->exchangeArray($data);
$bank = new Bank();
$bank->exchangeArray($data, 'bank.');
$employee->setBank($bank);
$manager = new Employee();
$manager->exchangeArray($data, 'manager.');
$employee->setManager($manager);
return $employee;
}
As a result of this I have a clean employee model (without those extra alias columns) and additionally 2 new properties which are objects of another employee(manager) and the bank.
But this looks quite overloaded...
Thanks for reading so far - If you have any hints or advices they are warmly welcomed!
EDIT
I've edited my EmployeeTableFactory to do the following (about hydrating):
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new HydratingResultSet();
$resultSetPrototype->setHydrator(new EmployeeHydrator());
$resultSetPrototype->setObjectPrototype(new Employee());
$tableGateway = new TableGateway('employee', $dbAdapter, null, $resultSetPrototype);
return new EmployeeTable($tableGateway);
}
I changed my EmployeeHydrator to implement the HydratorInterface because I was already using the extract stuff and now it matches the necessary interface for the resultSetPrototype->setHydrator() method.
Now things are getting pretty easy in the getEmployeeById method because with the following code I already have my finished employee object and all related foreign key objects (due to my EmployeeHydrator)
$result = $this->tableGateway->selectWith($select);
$row = $result->current(); // the given employee object result already hydrated!
return $row;
I kind of like this implementation

Get enum options in laravels eloquent

In my migration file, I gave my table pages a enum field with 2 possible values (as seen below). My question is, if it's possible to select these values with Laravels Eloquent?
$table->enum('status', array('draft','published'));
There are several Workarounds that I found, but there must be some "eloquent-native" way to handle this. My expected output would be this (that would be perfect!):
array('draft','published')
Thank you in advance!
Unfortunately, Laravel does not offer a solution for this. You will have to do it by yourself. I did some digging and found this answer
You can use that function and turn it into a method in your model class...
class Page extends Eloquent {
public static function getPossibleStatuses(){
$type = DB::select(DB::raw('SHOW COLUMNS FROM pages WHERE Field = "type"'))[0]->Type;
preg_match('/^enum\((.*)\)$/', $type, $matches);
$values = array();
foreach(explode(',', $matches[1]) as $value){
$values[] = trim($value, "'");
}
return $values;
}
}
And you use it like this
$options = Page::getPossibleStatuses();
If you want you can also make it a bit more universally accessible and generic.
First, create a BaseModel. All models should then extend from this class
class BaseModel extends Eloquent {}
After that, put this function in there
public static function getPossibleEnumValues($name){
$instance = new static; // create an instance of the model to be able to get the table name
$type = DB::select( DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$name.'"') )[0]->Type;
preg_match('/^enum\((.*)\)$/', $type, $matches);
$enum = array();
foreach(explode(',', $matches[1]) as $value){
$v = trim( $value, "'" );
$enum[] = $v;
}
return $enum;
}
You call this one like that
$options = Page::getPossibleEnumValues('status');
Made a small improvement to lukasgeiter's function. The foreach loop in his answer is parsing the string. You can update the regex to do that for you.
/**
* Retrieves the acceptable enum fields for a column
*
* #param string $column Column name
*
* #return array
*/
public static function getPossibleEnumValues ($column) {
// Create an instance of the model to be able to get the table name
$instance = new static;
// Pulls column string from DB
$enumStr = DB::select(DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$column.'"'))[0]->Type;
// Parse string
preg_match_all("/'([^']+)'/", $enumStr, $matches);
// Return matches
return isset($matches[1]) ? $matches[1] : [];
}
This throws an error if the column does not exist. So I added a small check in the code
public static function getPossibleEnumValues ($column) {
// Create an instance of the model to be able to get the table name
$instance = new static;
$arr = DB::select(DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$column.'"'));
if (count($arr) == 0){
return array();
}
// Pulls column string from DB
$enumStr = $arr[0]->Type;
// Parse string
preg_match_all("/'([^']+)'/", $enumStr, $matches);
// Return matches
return isset($matches[1]) ? $matches[1] : [];
}
As of L5.17 Eloquent does not include this functionality, instead you need to fall back to native QL. Here's an example that will work with SQL and in one line - returning an array like you asked.
In the spirit of one liner complexity ;)
I threw this in one of my view composers - it fetches the column from the table, explodes it and assembles the values in an array.
I iterate over that in my views using a foreach.
explode (
"','",
substr (
DB::select(" SHOW COLUMNS
FROM ".(new \Namespace\Model)->getTable()."
LIKE 'colName'"
)[0]->Type,
6,
-2
)
);

Autogenerate model classes with Laravel 4 (aka using an existing database with L4)

I've designed my database in MySQL Workbench, and have all my foreign keys setup, etc.
I'm wanting to use this DB schema with Laravel 4, however from the docs there is no word of any sort of ability to work with an existing set of database tables. From my understanding, other frameworks such as Cake with its 'Baking' allow you to automatically generate your model classes based on the tables already in your database.
I've looked around everywhere and cant see anything about this at all for Laravel 4. The closest thing I've found is Jeffrey Way's Generator package for artisan, however this only creates the base model, and doesn't detect established foreign key relationships.
Is this even possible with Laravel 4 or am I going to have to just do it all manually?
The good news is that Antonio just finished his MySQL WorkBench to Eloquent ORM converter
This is a beautiful solution but comes a way to late for me but may help you a lot.
Update: The link isn't working in the moment. The wabpage says "We are redesigning things, will be back soon!". I allready sent antonio an email asking him, when this service will be available again.
Antonio said that it'll be back but there is no estimated time of arrival. We have to wait..
cakePHP does a great job at fleshing out your whole project from the DB schema already in place. Laravel currently does not support anything like this. One of the minor features still holding me back from adopting laravel.
Hmm I had the same issue and I wrote a little script myself which generates base classes and solves the foreign key issues. It's a basic solution and only determines "hasOne" relations, which you might have to change to hasMany later on. I used a Controller and build my code Template in a view:
Controller:
namespace Admin;
/**
* just a quick helper to generate model classes
* from mysql to Eloquent style ones..
* #author Mario
*/
class ModelController extends \BaseController {
/**
* save Classes in folder of choice
*
* #return void
*/
public function create($folder)
{
$sql = "SELECT * FROM information_schema.tables WHERE table_schema = 'UR_SCHEMA'";
$tables = \DB::select($sql);
$sql2 = "select * from information_schema.`KEY_COLUMN_USAGE` where constraint_schema = 'UR_SCHEMA' order by table_name";
$keys = \DB::select($sql2);
$meta = $this->sortOutMetadata($keys);
foreach ($tables as $table) {
$metaData = null;
if(!empty($meta[$table->TABLE_NAME])){
$metaData = $meta[$table->TABLE_NAME];
}
$code = \View::make('model.start', array('table' => $table, 'meta' => $metaData))->render();
file_put_contents($folder.DIRECTORY_SEPARATOR.ucfirst(camel_case($table->TABLE_NAME).'.php'), $code);
}
}
/**
* provide structure indexed by table
*
* #param type $keys
* #return type
*/
private function sortOutMetadata($keys)
{
$return = array();
foreach ($keys as $key) {
if ($key->CONSTRAINT_NAME == 'PRIMARY') {
$return[$key->TABLE_NAME]['pk'] = $key->COLUMN_NAME;
} elseif (!empty($key->REFERENCED_TABLE_NAME)) {
//one way
$return[$key->TABLE_NAME]['fk'][] = array('column' => $key->COLUMN_NAME,
'refColumn' => $key->REFERENCED_COLUMN_NAME,
'refTable' => $key->REFERENCED_TABLE_NAME,);
//and the other
$return[$key->REFERENCED_TABLE_NAME]['fk'][] = array('column' => $key->REFERENCED_COLUMN_NAME,
'refColumn' => $key->COLUMN_NAME,
'refTable' => $key->TABLE_NAME,);
}
}
return $return;
}
}
My view Template (pretty much my Class Template)
<?php echo '<?php'; ?>
namespace Model\Base;
use Model\Model;
class <?php echo ucfirst(camel_case($table->TABLE_NAME));?> extends Model {
/**
* #var String
*/
protected $table = '<?php echo $table->TABLE_NAME;?>';
<?php if (isset($meta['pk'])):?>
/**
* #var String
*/
protected $primaryKey = '<?php echo $meta['pk'];?>';
/**
* attributes not writable from outside
* #var mixed
*/
protected $guarded = array('<?php echo $meta['pk'];?>');
<?php endif;?>
/**
* Timestamps we dont want here
* #var Boolean
*/
public $timestamps = false;
<?php if (isset($meta['fk'])):?>
<?php foreach($meta['fk'] as $keys):?>
/**
* #return HasOne
*/
public function <?php echo camel_case($keys['refTable']);?>()
{
return $this->hasOne('Model\<?php echo ucfirst(camel_case($keys['refTable']));?>', '<?php echo $keys['refColumn'];?>', '<?php echo $keys['column'];?>');
}
<?php endforeach;?>
<?php endif;?>
}
Then Simply generate your base classes by giving it the folder: (from wherever you prefer)
$controller = new \Admin\ModelController();
$controller->create(__DIR__ . DIRECTORY_SEPARATOR . 'tmpModel');
This gave me some decent way to get my base classes Auto generated the way I needed. Remember you got to be able to see the information_schema schema with your db user.
Hope this helps

A self-creator: What pattern is this? php

I have several classes that are basically interfaces to database rows. Since the class assumes that a row already exists ( __construct expects a field value ), there is a public static function that allows creation of the row and returns an instance of the class.
Here's a pseudo-code ( so there are mistakes and missing improvements in this ) example :
class fruit {
public $id;
public function __construct( $id ) {
if ( ! is_numeric($id) ) {
throw new Exception("Id is not numeric.");
}
$this->id = $id;
$sql = "SELECT * FROM Fruits WHERE id = $id";
...
$this->arrFieldValues[$field] = $row[$value];
}
public function __get( $var ) {
return $this->arrFieldValues[$var];
}
public function __set( $var, $val ) {
$sql = "UPDATE fruits SET $var = " . mysql_real_escape_string($val) . " WHERE id = $this->id";
}
public static function create( $fruit ) {
$sql = "INSERT INTO Fruits ( fruit_name ) VALUE ( '" mysql_real_escape_string($fruit) . "' )";
$id = mysql_insert_id();
$fruit = & new fruit($id);
return $fruit;
}
}
$obj1 = fruit::create( "apple" );
$obj2 = & new fruit( 12 );
What is this pattern called?
Edit: I changed the example to one that has more database-interface functionality. For most of the time, this kind of class would be instantiated normally, through __construct(). But sometimes when you need to create a new row first, you would call create().
I think it's the Factory method pattern.
The factory method pattern is an object-oriented design pattern to implement the concept of factories.
Like other creational patterns, it deals with the problem of creating objects (products) without specifying the exact class of object that will be created. The factory method design pattern handles this problem by defining a separate method for creating the objects, which subclasses can then override to specify the derived type of product that will be created.
Outside the scope of design patterns, the term factory method can also refer to a method of a factory whose main purpose is creation of objects.
As this is related to databases, I think this is close to something that may be called Data Mapper.
If you are looking for something like this in PHP, move to Propel ORM. Look at the first example : it's almost your code !

Categories