PHP - extending PHP Data Object to add new functionality - php

So this is what I would like to achive.
I would like to use PDO, joust as it is I like it, but I would like to add some new methods on top of PDO.
Like: update(), insert(), delete(), fetchAllAssoc(), etc...
So I would have to work with both PDO and PDO Statement objects from inside one class.
Beacuse to achive update, i would have to build a query, prepare() it, that would return PDO Statement object and then I would have to bindValues and then execute.
This is what I have done so far:
<?php
namespace Database\DBAL;
use PDO;
class Database extends PDO
{
protected static $instance;
public static function getInstance()
{
if(empty(static::$instance))
{
// This will be fetched from config
$dns = "mysql:host=localhost;dbname=db_name";
$username = "root";
$password = "my_pwd";
static::$instance = new Database($dns, $username, $password);
}
return static::$instance;
}
/**
* Detect param type
*/
protected function detectType($value)
{
if(is_string($value))
{
return PDO::PARAM_STR;
}
else if(is_int($value))
{
return PDO::PARAM_INT;
}
else if(is_null($value))
{
return PDO::PARAM_NULL;
}
else if(is_bool($value))
{
return PDO::PARAM_BOOL;
}
else
{
return false;
}
}
/**
* Updates table
*/
public function update($table, $data, $identifier)
{
$set = array();
foreach ($data as $columnName => $columnValue) {
$set[] = $columnName . ' = ?';
}
$query = 'UPDATE ' . $table . ' SET ' . implode(', ', $set) . ' WHERE ' . implode(' = ? AND ', array_keys($identifier)) . ' = ?';
$params = array_merge(array_values($data), array_values($identifier));
array_unshift($params, null);
unset($params[0]);
echo $query;
$pdos = static::$instance->prepare($query);
foreach($params as $key => $param)
{
$pdos->bindValue($key, $param, $this->detectType($param));
}
return $pdos->execute();
}
}
?>
You would use it like this:
use Database\DBAL\Database;
$db = Database::getInstance();
$db->update("users", array("user_password" => "new password"), array("user_id" => 1));
My question is, is this the right way to do it?
I was reading about Decorator and Facade pattern and it dosent look like this.
What is the best way to extend PDO class with my methods that would do simple deletes, inserts and fetch some data? And I want PDO to be intact, so I can use it the way I would usualy do... What pattern should I use and what would be the best way?
Thanks!

If i'm correct you are implementing the active record pattern which is an anti pattern. Please look after for a few ORM framework.
I've created my own: DataContext class which contains all the PDO stuffess (i'm not extending the PDO!), TableEntity classes which handles all the CRUD operations and Entity classes which are "the rows from tables".
$dc = new TestDataContext(...);
$dc->users()->Where(column, 1, '=')->...
$dc->users()->InsertOnSubmit(new User())
$dc->SubmitChanges();
Like Linq to SQL. :-)

Related

Assign object attributes from the result of prepared statement

I'm wanting to create a new instance of my Class and assign it's attributes the values that are returned. The reason for this is I'm creating a series of methods inheriting from the calling class, as opposed to using static methods which I already had working.
Example of what I'm using currently:
public static function findById($id) {
$id = self::escapeParam($id);
$idVal = is_int($id) ? "i" : "s";
$sql = "SELECT * FROM ".static::$db_table." WHERE id = ? LIMIT 1";
return static::findByQuery($sql,$idVal,$id);
}
public static function findByQuery($sql,$bindChar = '',$bindVal = '') {
try {
$callingClass = get_called_class();
$object = new $callingClass;
$statement = Database::$connection->prepare($sql);
if(!empty($bindChar)) :
$statement->bind_param($bindChar, $bindVal);
endif;
if($statement->execute()) :
$result = $statement->get_result();
$object = $result->fetch_object();
endif;
$statement->close();
if(!empty($object)) :
return $object;
endif;
} catch(Exception $e) {
}
}
What I tried was writing an instantiation method that creates a new instance of my class, and then assign each attribute of the object the value it returns from an array from a tutorial I did. However, the tutorial was fairly outdated and didn't use any new syntax or binding, so I was trying to rework this.
Example from the tutorial below:
public static function find_by_id($id) {
global $database;
$the_result_array = static::find_by_query("SELECT * FROM " . static::$db_table . " WHERE id = $id LIMIT 1");
return !empty($the_result_array) ? array_shift($the_result_array) : false;
}
public static function find_by_query($sql) {
global $database;
$result_set = $database->query($sql);
$the_object_array = array();
while($row = mysqli_fetch_array($result_set)) {
$the_object_array[] = static::instantation($row);
}
return $the_object_array;
}
public static function instantation($the_record){
$calling_class = get_called_class();
$the_object = new $calling_class;
foreach ($the_record as $the_attribute => $value) {
if($the_object->has_the_attribute($the_attribute)) {
$the_object->$the_attribute = $value;
}
}
return $the_object;
}
private function has_the_attribute($the_attribute) {
return property_exists($this, $the_attribute);
}
What I was trying to do from the tutorial, was to return my result as an array using a while, and then assigning a variable by passing the built array into the static::instantation() method, but it doesn't seem to ever be working correctly, as any public functions I create in my calling class (Admin for example) aren't called after as they don't exist due to the Class not being instantiated.
mysqli_result::fetch_object() accepts the class name as the first argument. You can pass the class name as an argument to that method and get the instance of the model. I am not sure why you have that much code but consider my example which I wrote based on your own code:
<?php
class Model
{
public static function findByQuery(string $sql, ?string $bindChar = null, ?string $bindVal = null): ?static
{
$statement = Database::$connection->prepare($sql);
if ($bindChar) :
$statement->bind_param($bindChar, $bindVal);
endif;
$statement->execute();
$result = $statement->get_result();
return $result->fetch_object(static::class);
}
}
class User extends Model
{
private $id;
}
class Database
{
public static mysqli $connection;
}
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
Database::$connection = new mysqli('localhost', 'user', 'password', 'test');
$user = User::findByQuery('SELECT ? as id', 's', 'Dharman');
var_dump($user);
The output from that example is:
object(User)#4 (1) {
["id":"User":private]=>
string(7) "Dharman"
}
As you can see, the code created an instance of the class using late-static binding and it also assigned the value to a private property, which you can't do otherwise.
P.S. My example is a little bit tidier. I added parameter typing and removed a lot of unnecessary code. In particular, I remove empty try-catch which is a terrible practice.
I have now got this working, although I feel this is probably not the best way of doing it.
I'm primarily front end so please comment if there are improvements or best practices.
public static function findByQuery($sql,$bindChar = '',$bindVal = '') {
try {
$statement = Database::$connection->prepare($sql);
if(!empty($bindChar)) :
$statement->bind_param("$bindChar", $bindVal);
endif;
if($statement->execute()) :
$result = $statement->get_result();
$output = $result->fetch_object();
endif;
$statement->close();
if(!empty($output)) :
$class = get_called_class();
$object = new $class;
foreach(get_object_vars($output) as $key => $value) :
$object->$key = $value;
endforeach;
endif;
if(!empty($object)) :
return $object;
endif;
} catch(Exception $e) {
}
}
My initial thoughts were declaring an object and then I thought that the PHP fetch_object call would have just assigned my object it's properties after initiating the Class but that wasn't the case.
So what I've done is that if the statement is successful and a results object is created, I then get the object properties and values with the get_object_vars() command, and then loop through these as a key value pair, assigning each attribute it's returned value.
I can confirm this works as I can now run $admin->remove() from my removal script, as opposed to what I was having to do before which was Admin::remove($id);

Chaining methods in PHP

$db->select("users")->where(array("username", "=", "username"));
$db->update("users", array("username" => "username", "password" => "12345"))->where(array("id", "=", "14"));
Ok, I want to write the statements like above, by chain the where() method onto select, update and delete.
My problem is; how to determine if I used the select, update or delete before the where, so I can bind the right values onto the right statement.
I want something like this:
public function where() {
if($this->select()) {
// so if $db->select("users")->where(array("username", "=", "username"));
// save the where data in the select variable.
}
elseif($this->update()) {
// so if $db->update("users", array("username" => "username", "password" => "12345"))->where(array("id", "=", "14"));
// save the where data in the update variable.
}
elseif($this->delete()) {
// so if $db->delete("users")->where(array("username", "=", "username"));
// save the where data in the delete variable.
}
}
But the code above is of course not valid, and I dont use any frameworks.
public function select($table, $what = null) {
$what == null ? $what = "*" : $what;
$this->_select = "SELECT {$what} FROM {$table}";
return $this;
}
You would have to maintain that state. It's not about telling whether the previous call was a select() or an update(), that's the wrong way to think about the problem. You just need each of select/update/delete to modify $this, so that $this, always knows what kind of query it's building.
A dead simple example:
public function select() {
$this->kind == 'select';
return $this;
}
public function where() {
if ($this->kind == 'select') {
...
return $this;
}
The only thing that your chained methods share is that they each return $this, so that a subsequent method can be chained onto the end. It's all about storing up state in $this until some final method call actually evalates the built-up query.
Something like:
public function select($table, $fields = '*')
{
$this->query = "SELECT {$fields} FROM `{$table}`";
return $this;
}
public function where($conditions = [])
{
if ($this->query)
{
if ($conditions)
{
foreach ($conditions as $key => &$val)
$val = "`{$key}` = '{$val}'";
$this->query .= ' WHERE ' . implode(' AND ', $conditions);
}
$db->query($this->query);
$this->query = '';
return $this;
}
}
This would work, however, you have to notice that this structure would allow you to do things like:
$db->where();
This is perfectly valid even though doesn't make sence to call where() in the database directly.
Also, queries that don't require a WHERE clause would not run, because only where() actually makes the call.
How to solve this?
We can actually use a very interesting mechanic of OOP: The destructor method. PHP destroys objects immediately after they are no longer in use, and we can explore this feature here as a trigger to run the query. We only have to separate the query to a new object.
class dbQuery
{
private $db;
private $conditions = [];
function where($conditions = [])
{
$this->conditions = array_merge($this->conditions, $conditions);
return $this;
}
function __construct($db, $query)
{
$this->db = $db;
$this->query = $query;
}
function __destruct()
{
if ($this->conditions)
{
foreach ($this->conditions as $key => &$val)
$val = "`{$key}` = '{$val}'";
$this->query .= ' WHERE ' . implode(' AND ', $this->conditions);
}
$this->db->result = $db->query($this->query);
}
}
class Database
{
public $result = null;
protected static $instance;
function __construct()
{
if (!self::$instance)
self::$instance = new mysqli('localhost', 'user', 'password', 'dbname');
}
public function query($query)
{
return self::$instance->query($query);
}
public function select($table, $fields = '*')
{
return new dbQuery($this, "SELECT {$fields} FROM `{$table}`");
}
}
This way $db->where() won't work as it doesnt exist, and using $db->select('table') or $db->select('table')->where([...]) will both give results, and even allows extending the syntax to use where() multiple times like:
$db->select('table')->where(['id' => 100])->where(['price' => 1.99]);

PHP Database class - select function

I worked with procedural PHP for a long time but not to long ago I just started to learn OOP PHP. For better understanding I decided to create a class to manage my DB. As I started to learn from phpacademy my first select function was quite poor, so I just added some other arguments. I ended up with this:
public function get($tabel, $where = null, $columns = array('*'), $other = null){
if($where){ $where = $this->where($where);; }
$select = 'SELECT '.$this->select($columns);
return $this->action($select, $tabel, $where, $other);
}
// $db->get('users',array('group',1),array(*),array('LIMIT' => 10));
(action executes the query)
Then I decided to modify this to get better control.
public function getModified($table, $param = array()){
$select = (isset($param['S'])) ? $this->select($param['S']) : '*';
$where = (isset($param['W'])) ? $param['W'] : array();
$other = array();
if(isset($param['GB'])){ $other['GROUP BY'] = $param['GB']; }
if(isset($param['OB'])){ $other['ORDER BY'] = $param['OB']; }
if(isset($param['L'])){ $other['LIMIT'] = $param['L']; }
return $this->action('SELECT '.$select, $table, $where, $other);
}
// $db->getModified('users',array('WHERE' => array('id',1), 'LIMIT' => 10));
But today I found in FuelPHP's documentation this: DB::get()->from('users')->where('id', 1)->limit(10);
Because I do this class to practice OOP PHP I've tried to create something similar but to execute the query I had to add an other function, which I want to skip. Could you show me an example how this method should/could work?
And I know that it's objective but which one would you prefer?
I'll just explain how the FuelPHP way works. This is btw a pattern that is used in a lot of DB wrapper classes.
In short, your Database package consists of 2 classes. 1 that handles connection to your database, multiple connections,... This is the DB class. this will look something like this:
class DB
{
private static $connections = array();
public static function addConnection($name, $connection)
{
self::$connection[$name] = $connection;
}
public static function get($name='default')
{
return new QueryBuilder(self::$connection[$name]);
}
}
This class manages all connections and returns a queryBuilder instance if you need to query a connection. The QueryBuilder will look something like this:
class QueryBuilder
{
private $connection;
public function __construct($connection)
{
$this->connection = $connection;
}
public function select()
{
$this->queryType = 'SELECT';
return $this;
}
public function from($table)
{
$this->table = $table;
return $this;
}
public function all()
{
return $this->connection->query(
$this->getSqlQuery()
);
}
public function getSqlQuery()
{
return $this->queryType . ' ' . $this->columns . ' FROM ' . $this->table;
}
}
so now you can use the clases above as:
DB::setConnection('default', $myPdoConnection);
DB::get()->select()->from('my_table')->all();
Note that the Querybuilder assumes that your $connection has a query method
A nice example from the Eloquent/laravel QueryBuilder: https://github.com/illuminate/database/blob/master/Query/Builder.php
What you are searching for is method chaining, also fuelphp (just like doctrine and others) is caching what you are sending in and builds the query after that:
public function from($table){
$this->_table = $table;
return $this; //this is the important part for chaining
}
public function where($key,$value){
$this->where = array($key => $value);
return $this;
}
public function andWhere($key,$value){
if(!$this->where) $this->where = array();
$this->where[$key] = $value;
return $this;
}
public function getQuery(){
//Build query here from what we stored before
$query = '';
....
return $query;
}
Oh well, what i forgot is what DB::get returns, an instance of what the class above is and executes that then:
public static function get(){
return new Query(); //above functions are part of Query class
}

calling statically and dynamically same method

i'm writing a php class that is like an orm.
I have a method, that can be called statically or instanciated, and it must work in both cases.
Can you see what's wrong.
Basically is an object called Model.
When created it creates a table based on the inherited class.
For example:
Podcast extends Model ....
There are some functions like this that needs to be called statically and dynamically.
for example:
$podcastList = Podcast::findAll($db);
I get all podcasts objects from DB without need to have a podcast object instanciated.
But i can also do:
$podcast = new Podcast($db)
$podcastList = $podcast->findAll(); //no db here.... passed before
$db is a class i wrote to make operation on Database. IT simply does with OOP, what mysql_* do with functions. I'm not using PDO, i may use in future, but now i use mysql_* :P
that are the incriminated functions
public static function findAll($db=NULL, $self=NULL) {
if($self == NULL) {
$self = new static($db);
} else {
$self = $this;
}
$self->tableName = "";
$self->db = NULL;
$is_static = !(isset($this) && get_class($this) == __CLASS__);
if($is_static) {
//die(__CLASS__ . "::" . __FUNCTION__ . " CALLED STATICALLY");
if(!$self->db) {
die(__CLASS__ . "::" . __FUNCTION__ . " CALLED STATICALLY AND DB IS NULL");
//It stops here!
}
$self->tableName = $self->genTableName();
} else {
$self->db = $this->db;
$self->tableName = $this->tableName;
}
$query = "SELECT * FROM {$self->tableName}";
$r = $self->db->exec($query);
if(!$r) {
die(__CLASS__ . ":Error " . __FUNCTION__ . " record: " . $self->db->getError());
}
if($self->db->countRows($r) == 0) {
return NULL;
}
$objects = array();
while($row = $self->db->fetch($r, DBF::FETCH_ASSOC)) {
$objectClass = __CLASS__;
$object = new $objectClass($this->db);
//TODO Do it dinamically indipendently of column name
$f = get_class_vars($objectClass);
foreach ($f as $field => $value) {
$chuncks = explode("_", $field);
if($chuncks[0] == "f") {
$object->{$field} = $row[$chuncks[2]];
}
}
$objects[] = $object;
}
return $objects;
}
public function __call($name, $arguments) {
if ($name === 'findAll'){
return static::findAll($arguments, $this);
}
}
Both are part of a class.
Thank you for the help !
There's a lot wrong with this code. More important than your many logic mistakes (why are you setting $self = $this, then $self->db = NULL, then $self->db = $this->db?) is that you are misunderstanding what it means to be able to call static functions dynamically in PHP. The object $this simply doesn't exist in a static method. The call $podcast->findAll() looks non-static, but it's still static.
To do what you want to do, here are some options:
leave the function static and call findAll($this->db, $tablename) as needed
put the function into the db class and call it with parameter tablename
EDIT:
The second in my list is how I would do it. This is because you already have to have a db object in your original example, and there is nothing in particular that makes the function's purpose only suited to Podcast objects and not to, say, any other object representing database rows.
//calling examples:
$podcastlist = $db->findAll('Podcast');
$podcast = new Podcast($db);
$podcastlist = $podcast->findAll();
public class db {
....
function findAll($classname, $tablename=NULL) {
if(!isset($tablename)) {
//let's pretend you put default table names as class constants
$tablename = get_constant($classname.'::DEFAULT_TABLE');
}
$query = "SELECT * FROM {$tableName}";
$r = $this->exec($query);
if(!$r) {
throw new Exception("Error " . __FUNCTION__ . " record: " . $this->getError());
}
if($this->countRows($r) == 0) {
return NULL;
}
$objects = array();
while($row = $this->fetch($r, DBF::FETCH_ASSOC)) {
$object = new $classname($this);
//the following is an easier way to do your original foreach
foreach($row as $field=>$value) {
if(property_exists($classname, "f_".$field)) {
$object->{'f_'.$field} = $value;
}
}
$objects[] = $object;
}
//something you forgot:
return $objects;
}
}
public class Podcast extends Model {
....
public function findAll($tablename=NULL) {
return $this->db->findAll(class_name($this), $tablename);
}
}

How can I add a : to the beginning of the first array key when the first array key is unknown?

I'm trying to dynamically add class properties into an PDO prepared statement. To achieve this I need to create an array that will grab the properties from the class, put them in an array, and add a : to the beginning of each key. Then, separate each key with a comma. The closest I've gotten to achieve this is using:
foreach ($property as $field)
$propertyValues = implode(", :", array_keys($property));
return $propertyValues;
}
which gives me
username, :password
I just need a way to add a : to the first key which in this case is username. So it would look like
:username, :password
Keep in mind, however, that I'm trying to make this dynamic so that I can extend its functionality to other classes, and I'm not always going to know what the first array key is.
If your interested in reading the entire class, here it is:
<?php
require_once("../config/main.php");
class Database{
protected static $dbFields = array('username', 'password');
public $dbConnection;
public $tableName = 'users';
public $id;
public $username;
public $password;
public function __construct() {
$this->connect();
}
public function connect(){
try {
$this->dbConnection = new PDO("mysql:host=".DB_SERVER."; dbname=".DB_NAME, DB_USER, DB_PASS);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
}
public function properties() {
$properties = array();
foreach (self::$dbFields as $field) {
if (isset($this->field) || property_exists($this, $field)) {
$properties[$field] = $this->$field;
}
}
return $properties;
}
public function property_values() {
$property = $this->properties();
$propertyValues = implode(", :", array_keys($property));
return $propertyValues;
}
public function insert(){
// this is where all the is going to happen. it's a work in progress
// $sql = "INSERT INTO". $this->tableName. " (";
// $sql .= implode(", ",array_keys($this->properties()));
// $sql .= ")VALUES(". ;
// $q = $this->db->prepare($sql);
// $q->execute(array('John', 'hula'));
}
}
$database = new Database();
$vals = $database->property_values();
print_r($vals);
?>
$propertyValues = ':'. implode(", :", array_keys($property));
To get the first array key, you can use the foolproof:
reset($array);
$firstkey = key($array);
Since you also need the value (cannot change the key, can only unset the old one and set a new), you can use:
$value = reset($array);
$firstkey = key($array);
unset($array[$firstkey]);
$array[':'.$firstkey] = $value;
However
This isn't really the cleanest approach. As often happens, you have become fixated on tweaking "your" approach to work when there is in fact a cleaner approach available.
One approach:
$keys = array_map(function($key) { return ':'.$key; }, array_keys($property));
$property = array_combine($keys, $property);
Now all the property names have had a ':' added in front.
Another approach would be to just manually prepend a ':'; webbiedave beat me to that.

Categories