PDO FETCH_CLASS multiple rows into array of objects - php

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);

Related

oop model Base class design : static and non-static data access

I am trying to make a base class ... tiny framework if you will just for practice
So I start with example of child class because it has less code !!
class User extends Base {
public $id ;
public $username ;
public $email ;
public $password ;
function __construct(){
$this->table_name = 'users';
$this->set_cols(get_class_vars('User'));
}
}
$u = new User;
$u->username = 'jason';
$u->email = 'j#gmail.com';
$u->insert();
Here is my Base class
class Base {
protected $table_name ;
protected $table_columns ;
protected function set_cols($cols){
unset($cols['table_name']);
unset($cols['table_columns']);
$this->table_columns = array_keys($cols);
}
public function insert(){
$colums = $values = array();
foreach($this->table_columns as $col )
{
if(!$this->$col) continue ;
$values[] = $this->$col ;
$colums[] = $col ;
}
$values = implode(',' , $values);
$colums = implode(',' , $colums);
echo $sql = "INSTER INTO ".$this->table_name ." ($colums)
VALUES ($values) ";
}
}
Here is the problem , I want to make filter or get method (basically reading from database) static and then return an array of objects from database data
class Base{
static function filter($conditions =array()){
$query_condition = $conditions ; // some function to convert array to sql string
$query_result = "SELECT * FROM ".$this->table_name ." WHERE $query_condition ";
$export = array();
$class = get_called_class();
foreach($query_result as $q )
{
$obj = new $class;
foreach($this->table_columns as $col )
$obj->$col = $q[$col];
$export[] = $obj;
}
return $export;
}
}
$users = User::filter(['username'=>'jason' , 'email'=>'j#gmail.com']);
Here is the problem , with filter as static function __construct in User class will not get called and table_columns, table_name will be empty
also in the filter method I can't access them anyway because they are not static ... I can make a dummy User object in the filter method and solve this problems but somehow it doesn't feel right
Basically I have a design problem any suggestion is welcomed
The problem is that the static object is not really "created" when you run statically.
If you want the constructor to run, but still in a static sort of way, you need a "singleton". This is where the object is created once and then you can re-use. You can mix this technique in a static and non-static way (as you're actually creating a "global" object that can be shared).
An example is
class Singleton {
private static $instance;
public static function getInstance() {
if (null === static::$instance) {
self::$instance = new static();
}
return self::$instance;
}
}
$obj = Singleton::getInstance();
Each time this gets the same instance and remembers state from before.
If you want to keep your code base with as few changes as possible, you can create yourself an "initialized" variable statically - you just need to remember to call it in each and every function. While it sounds great, it's even worse than a Singleton as it still remembers state AND you need to remember the init each time. You can, however, use this mixed with static and non-static calls.
class notASingletonHonest {
private static $initialized = false;
private static function initialize() {
if (!self::$initialized) {
self::$initialized = true;
// Run construction stuff...
}
}
public static function functionA() {
self::$initialize();
// Do stuff
}
public static function functionB() {
self::$initialize();
// Do other stuff
}
}
But read a bit before you settle on a structure. The first is far better than the second, but even then if you do use it, ensure that your singleton classes can genuinely be ran at any time without reliance on previous state.
Because both classes remember state, there are many code purists that warn you not to use singletons. You are essentially creating a global variable that can be manipulated without control from anywhere. (Disclaimer - I use singletons, I use a mixture of any techniques required for the job.)
Google "php Singleton" for a range of opinions and more examples or where/where not to use them.
I agree with a lot of your premises in your code and design. First - User should be a non static class. Second - Base base should have a static function that acts a factory for User objects.
Lets focus on this part of your code inside the filter method
1 $query_result = "SELECT * FROM ".$this->table_name ." WHERE $query_condition ";
2 $export = array();
3
4
5 $class = get_called_class();
6 foreach($query_result as $q )
7 {
8 $obj = new $class;
9
10 foreach($this->table_columns as $col )
11 $obj->$col = $q[$col];
12
13 $export[] = $obj;
14
15 }
The issue is that lines 1 and 10 are trying to use this and you want to know the best way to avoid it.
The first change I would make is to change protected $table_name; to const TABLE_NAME like in this comment in the php docs http://php.net/manual/en/language.oop5.constants.php#104260. If you need table_name to be a changeable variable, that is the sign of bad design. This will allow you change line 1 to:
$class = get_called_class()
$query_result = "SELECT * FROM ". $class::TABLE_NAME . "WHERE $query_condition";
To solve the problem in line 10 - I believe you have two good options.
Option 1 - Constructor:
You can rewrite your constructor to take a 2nd optional parameter that would be an array. Your constructor would then assign all the values of the array. You then rewrite your for loop (lines 6 to 15) to:
foreach($query_result as $q)
{
$export[] = new $class($q);
}
And change your constructor to:
function __construct($vals = array()){
$columns = get_class_vars('User');
$this->set_cols($columns);
foreach($columns as $col)
{
if (isset($vals[$col])) {
$this->$col = $vals[$col];
}
}
}
Option 2 - Magic __set
This would be similar to making each property public, but instead of direct access to the properties they would first run through a function you have control over.
This solution requires only adding a single function to your Base class and a small change to your current loop
public function __set($prop, $value)
{
if (property_exists($this, $prop)) {
$this->$prop = $value;
}
}
and then change line 10-11 above to:
foreach($q as $col => $val) {
$obj->$col = $val
}
Generally it is a good idea to seperate the logic of storing and retrieving the data and the structure of the data itself in two seperate classes. A 'Repository' and a 'Model'. This makes your code cleaner, and also fixes this issue.
Of course you can implement this structure in many ways, but something like this would be a great starting point:
class Repository{
private $modelClass;
public function __construct($modelClass)
{
$this->modelClass = $modelClass;
}
public function get($id)
{
// Retrieve entity by ID
$modelClass = $this->modelClass;
return new $$modelClass();
}
public function save(ModelInterface $model)
{
$data = $model->getData();
// Persist data to the database;
}
}
interface ModelInterface
{
public function getData();
}
class User implements ModelInterface;
{
public int $userId;
public string $userName;
public function getData()
{
return [
"userId" => $userId,
"userName" => $userName
];
}
}
$userRepository = new Repository('User');
$user = $userRepository->get(2);
echo $user->userName; // Prints out the username
Good luck!
I don't think there is anything inherently wrong with your approach. That said, this is the way I would do it:
final class User extends Base {
public $id ;
public $username ;
public $email ;
public $password ;
protected static $_table_name = 'users';
protected static $_table_columns;
public static function getTableColumns(){
if( !self::$_table_columns ){
//cache this on the first call
self::$_table_columns = self::_set_cols( get_class_vars('User') );
}
return self::$_table_columns;
}
public static function getTableName(){
return self::$_table_name;
}
protected static function _set_cols($cols){
unset($cols['_table_name']);
unset($cols['_table_columns']);
return array_keys($cols);
}
}
$u = new User;
$u->username = 'jason';
$u->email = 'j#gmail.com';
$u->insert();
And then the base class, we can use Late Static Binding here static instead of self.
abstract class Base {
abstract static function getTableName();
abstract static function getTableColumns();
public function insert(){
$colums = $values = array();
foreach( static::getTableColumns() as $col ){
if(!$this->$col) continue ;
$values[] = $this->$col ;
$colums[] = $col ;
}
$values = implode(',' , $values);
$colums = implode(',' , $colums);
echo $sql = "INSERT INTO ". static::getTableName() ." ($colums) VALUES ($values) ";
}
static function filter($conditions =array()){
$query_condition = $conditions ; // some function to convert array to sql string
$query_result = "SELECT * FROM ".static::getTableName() ." WHERE $query_condition ";
$export = array();
$columns = static::getTableColumns(); //no need to call this in the loop
$class = get_called_class();
foreach($query_result as $q ){
$obj = new $class;
foreach( $columns as $col ){
$obj->$col = $q[$col];
}
$export[] = $obj;
}
return $export;
}
}
Now on the surface this seems trivial but consider this:
class User extends Base {
public $id ;
public $username ;
public $email ;
public $password ;
final public static function getTableName(){
return 'users';
}
final public static function getTableColumns(){
return [
'id',
'username',
'email',
'password'
];
}
}
Here we have a completely different implementation of those methods from the first Users class. So what we have done is force implementation of these values in the child classes where it belongs.
Also, by using methods instead of properties we have a place to put custom logic for those values. This can be as simple as returning an array or getting the defined properties and filtering a few of them out. We can also access them outside of the class ( proper like ) if we need them for some other reason.
So overall you weren't that far off, you just needed to use static Late Static Binding, and methods instead of properties.
http://php.net/manual/en/language.oop5.late-static-bindings.php
-Notes-
you also spelled Insert wrong INSTER.
I also put _ in front of protected / private stuff, just something I like to do.
final is optional but you may want to use static instead of self if you intend to extend the child class further.
the filter method, needs some work yet as you have some array to string conversion there and what not.

php putting an array from _construct into function

II'm using an API and was wondering why I am having trouble getting an array to cross into a function. The following works fine but how can I make it work for an array.
public function __construct()
{
parent::__construct(); // Init parent constructor
$this->dbConnect();
$this->test();
}
public function test()
{
$this->category = "bracelets";
}
private function piece()
{
// Pass an array into this function here and then use depending on array key
$cat = $this->category;
}
So instead of a constant $this->category="bracelets. I would like this to be an array. e.g.
public function test()
{
$array = [
"foo" => "bar",
"bar" => "foo",
];
$this->category = $array;
}
Ok, this has been resolved. It was due to a minor error elsewhere. For a moment I believed there was an issue with arrays in a restful API.
I hope this is useful to any others who wish to pass one function results to another in an api class.
Looking at you code, it seems you want the category property to be an array of all categories, read from the database..?
I did spot some bugs in your code:
You have variable name mixups on $cat_array vs. $cat_arr
You select cat column from DB but try read category
I have made slight changes in your test() method to fix it:
public function __construct()
{
parent::__construct(); // Init parent constructor
$this->dbConnect();
$this->test();
}
// Array with name of all categories, indexed by category id
private $category;
public function test()
{
$query = "SELECT c.id, c.cat FROM category c order by c.id asc";
$cat = $this->mysqli->query($query)
or die ($this->mysqli->error . __LINE__);
if ($cat->num_rows > 0) {
$cat_array = array();
while ($crow = $cat->fetch_assoc()) {
$id = $crow['id'];
$cat_array[$id] = $crow['cat'];
//$cat_array[$crow['id']]=$crow['category'];
}
$this->category = $cat_array;
//$this->response($this->json($result), 200); // send details
}
}
private function piece()
{
// Pass an array into this function here and then use depending on array key
$cat = $this->category;
// Check if it is working as expected
print_r($cat);
}

Learning OOP - distributing logic and dividing responsibility

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";

PHP OOP query is not executing

I am using PHP with OOP to select rows from the database (MySQL).
When I execute the query, it returns an empty row.
Here is the classe I am using:
<?php
class EmploiManager
{
private $_db;
public function __construct($db)
{
$this->setDb($db);
}
public function category($category)
{
$q = $this->_db->prepare('SELECT * FROM DemandeEmploi WHERE category = :category');
$q->execute(array('category' =>$category));
$donnees = $q->fetch(PDO::FETCH_ASSOC);
return new Emploi($donnees);
}
public function setDb(PDO $db)
{
$this->_db = $db;
}
}
$type = $_GET['category'];
$manager = new EmploiManager($db);
$row = $manager->category($type);
foreach ($row as $demandeE)
{
?>
<div class="list"><h4><? echo $demandeE->title();?></h4> </div>
<?php
}
?>
Can any one tell me what's wrong with that code?
Thanks!
It's my bad, I didn't use a loop to select all the rows.
I corrected the code and it works fine now, here is what it looks like:
public function category($category)
{
$datas = array();
$q = $this->_db->prepare('SELECT * FROM DemandeEmploi WHERE category = :category');
$q->execute(array('category' =>$category));
while ($donnees = $q->fetch(PDO::FETCH_ASSOC))
{
$datas[] = new Emploi($donnees);
}
return $datas;
}
$q->fetch() just returns one row of the results. If you want all the results, you must use $q->fetchAll().
Since you specified PDO::FETCH_ASSOC, the elements of $row will be associative arrays, not objects; aren't you getting errors saying that you're trying to call a method on a non-object? So $demandeE->id() should be $demandeE['id'], and $demandeE->title() should be $demandeE['title'].
Alternatively, you could specify PDO::FETCH_OBJ. Then, the values will be properties, not methods, so it should be $demandeE->id and $demandeE->title (no parentheses).

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