I have a class containing some instance variables:
class MyClass
{
public $id;
public $name;
}
and a mysql table containing th above fields + some additional fields like updated_at. I try to instantiate objects of the above class using pdo and converting them into a json array with the following code:
$statement = $pdo->prepare('SELECT * FROM `mytable`');
$statement->execute();
$statement->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'MyClass');
$objects = $statement->fetchAll();
echo json_encode( (array) $objects );
It now appears that the resulting array also contains the additional fields although they are not part of my class definition:
[{"id": 5, "name":"a name", "updated_at":"<timestamp>", "created_at":"<timestamp>"}]
How can this be and whats the best way to avoid this?
thanks in advance.
class MyClass
{
public $id;
public $name;
protected function setSelection() {
return implode(
',',
array_map(
function ($value) {
return '`'.$value.'`';
},
array_keys(get_object_vars($this))
)
);
}
public function getData() {
....
$statement = $pdo->prepare(
'SELECT ' . $this->setSelection() . ' FROM `mytable`'
);
....
}
}
Related
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.
I want to use a associative array (outcome of a PDO query) in a class, so that I can construct a DIV with some database content.
How to get the array inside the Class? In the while loop I want to make an object, in the Class of this object I want to construct the HTML.
while($data = $stmt->fetch( PDO::FETCH_ASSOC )){
$html = new class;
echo $html;
}
If you want your while loop to be able to work about like you've written it, you can write the constructor of the class so that it accepts the data as an argument, then implement the div output in the __toString method.
class HtmlDivFormatter {
private $data;
public function __construct($data) {
$this->data = $data;
}
public function __toString() {
return '<div>' . $this->data['column_name'] . '</div>';
// whatever HTML you have in mind
}
}
while ($data = $stmt->fetch( PDO::FETCH_ASSOC )) {
$html = new HtmlDivFormatter($data);
echo $html;
}
see PDO::FETCH_CLASS or PDO::FETCH_INTO
more info here http://php.net/manual/en/pdostatement.fetch.php and this
PHP PDO fetching into objects
and some example code:
<?php
$sql = 'SELECT firstName, lastName FROM users';
$stmtA = $pdo->query($sql);
$stmtA->setFetchMode(PDO::FETCH_CLASS, 'Person');
$objA = $stmtA->fetch();
var_dump($objA);
//first create the object we will fetch into
$objC = new Person;
$objC->firstName = "firstName";
$objC->lastName = "lastName";
$stmtC = $pdo->query($sql);
$stmtC->setFetchMode(PDO::FETCH_INTO, $objC);
$objC = $stmtC->fetch(); // here objC will be updated
var_dump($objC);
class Person{
public $firstName;
public $lastName;
}
I have probleme when i want add array from PDO to contructor of class news :
class News{
protected $erreurs = array(),
$id,
$auteur,
$titre,
$contenu;
public function __News(array $donnees){
$this->hydrate($donnees);
}
public function hydrate(array $donnees)
{
foreach ($donnees as $key => $value)
{
$method = 'set'.ucfirst($key);
if (method_exists($this, $method))
{
$this->$method($value);
}
}
}
Class Manager with function get news by id :
public function getUnique($id){
$id = (int)$id;
$sql = $this->db->prepare('SELECT id, auteur, titre, contenu FROM news WHERE id = ?');
$sql->bindParam('1', $id);
$sql->execute();
$mang = $sql->fetch(PDO::FETCH_ASSOC);
return new News($mang);
}
And index. php
$db = new PDO('mysql:host=localhost;dbname=new', 'root','');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$manager = new Manager($db);
$uniqueNews = $manager->getUnique(2);
echo 'id : '.$uniqueNews->getAuteur();
But i don't know why the value of auteur do not display . If I'm going about this contructor completely wrong, please point me in the right direction. Thanks.
The constructor of a class should be either __construct() or a method named after the class. I prefer the first option. The second option is for legacy code.
class News{
public function __construct(array $donnees){
// This is where your constructor code goes
}
}
Your constructor should be called __construct()
class News{
protected $erreurs = array(),
$id,
$auteur,
$titre,
$contenu;
public function __construct(array $donnees){
$this->hydrate($donnees);
}
}
I am trying to build a custom ActiveRecord class and am trying to return MySQL results in an object. The object will be the model.
Here's what I'm doing:
while($object = mysql_fetch_object($result, $this->_model)) {
foreach($object as $key => $val) {
if($this->is_serialized($val)) $object->$key = unserialize($val);
}
$return_value[] = $object;
}
If I do a print_r() on the result, I get an empty class (no variables have been set or anything).
Here is my model:
class User extends ActiveRecord {
public $_hasOne = "Profile";
public $_required = array('full_name', 'user_name', 'password', 'country');
public $_unique = array('full_name', 'user_name');
}
I can't figure out what I'm doing wrong!
EDIT: Fixed it in the end. At the beginning of my Activerecord class I had this:
$fields = $this->query("SHOW COLUMNS FROM " . $this->_table);
while($field = mysql_fetch_assoc($fields)) {
$this->_columns[] = $field['Field'];
$this->$field['Field'] = null;
}
Which was setting all the values to NULL!
Bit of a silly mistake. It's now:
$fields = $this->query("SHOW COLUMNS FROM " . $this->_table);
while($field = mysql_fetch_assoc($fields)) {
$this->_columns[] = $field['Field'];
if(!isset($this->$field['Field'])) $this->$field['Field'] = null;
}
Stupid question, but $this->_model is "User" right (I'd echo that value, it might have gotten unset somehow).
After that, your class should have fields by the same names as the columns which you want to represent. So:
class Foo
{
private $id;
public $val;
public function __toString(){ return "$this->id => $this->val"; }
}
mysql_connect('localhost','root','');
$q = mysql_query('SELECT 1 as id, 2 as val FROM DUAL');
$foo = mysql_fetch_object( $q, 'Foo' );
echo $foo; // 1 => 2
Next, you can't iterate over an object. You need to iterate over object properties:
foreach(get_object_vars($object) as $key => $val) {
if($this->is_serialized($val)) $object->$key = unserialize($val);
}
I have a class "test" with 2 functions, one to create the prepared statement and one to execute one or more times. The problem is how do i set the binding in the first one and fill in the vars on the other function.
class test {
private static $moduleSql = "SELECT
id,
module
FROM
phs_pages
WHERE
prettyurl = :prettyUrl AND
mainId = :mainId
";
private static function pre() {
$db = Db_Db::getInstance();
self::$preModuleSql = $db->prepare(self::$moduleSql);
self::$preModuleSql->bindParam(':prettyUrl', $prettyUrl);
self::$preModuleSql->bindParam(':mainId', $mainId);
}
private static function getClassByDb($mainId = 0, $id = 0) {
$prettyUrl = self::$parts[$id];
self::$preModuleSql->execute();
self::$preModuleSql->debugDumpParams();
$result = self::$preModuleSql->fetch(PDO::FETCH_ASSOC);
// get lower level classes
if (isset(self::$parts[$id + 1])) {
self::getClassByDb($result['id'], $id + 1);
} else {
return $result;
}
}
}
You donĀ“t need to bind any parameters in your first function, you need to do that in your second function, right before you execute the prepared statement.
Edit: By the way, you can also call execute with an array that contains your parameters as keys and the values you want to send.
There are so many things wrong with this class definition .. ehh.
It should look like this :
class RepositoryException extends Exception{}
interface iRepository
{
public function store( $key , PDOStatement $statement );
public function fetch( $key );
}
class StatementRepository implements iRepository{
protected $_pool = array();
public function store( $key , PDOStatement $statement )
{
$this->_pool[ $key ] = $statement;
}
public function fetch( $key )
{
if ( !array_key_exists( $key , $this->_pool ) ){
throw new RepositoryException(
"Statement with name '$key' does not exist" );
}
return $this->_pool[ $key ];
}
}
$repo = new StatementRepository;
$foo = new Foo( $repo );
$bar = new Bar( $repo );
$db = new PDO('sqlite::memory:');
$statement = $db->prepare( 'SELECT .... ');
$repo->store( 'bljum' , $statement );
This way all the object which have accepted in the constructor an object which implements iRepository can fetch statements by name.
You class should not have more responsibilities then it needs. If it is for storing prepared statements , then it should have nothing to do with binding parameters to them and executing something.
Here is what Foo class would do :
class Foo
{
protected $_repo = null;
public function __construct( iRepository $repo )
{
$this->_repo = $repo;
}
public function ergo( $x , $y )
{
$statement = $this->_repo->fetch('bljum');
//bind values
$statement->execute();
}
}