This is not really a problem but more of a question.
My question is how it's 'supposed to be' programmed.
It might not be too clear to explain, though.
So my question is; do I have to make multiple methods to retrieve data from a database?
For example, I have a table called FRUITS.
It contains the ID, name and date the fruit was added.
Now I want to get the name of the fruit based on a given ID, and later on in the script I want to get the date of the fruit as well.
Should I make one method such as get_fruit($id) which returns both the name and date, or two separate methods get_name($id) and get_date($id)?
Thanks in advance.
You should use one object which would contain all the required data. For example:
class Fruit {
protected ... variables;
public function getId() {...}
public function getDate() {...}
...
}
Also implementing __set and __get would be nice example of using full php potential.
You also may implement save() method (or extend database row class, such as Zend_Db_Table_Row.
So whole code would look like:
$fruit = $model->getFruid( 7); // $id = 7 :)
echo $fruit->id; // would call internally $fruit->__get( 'id')
echo $fruit->date;
// And modification:
$fruit->data = '2011-08-07';
$fruit->save();
EDIT: using separate methods to load certain data is useful (only?) when you need to load large amount of data (such as long texts) which is required only on one place in your code and would affect performance.
EDIT 2: (answer to comment):
__get and __set are called when you try to access undefined property of an object, for example:
class Foo {
public $bar;
public function __get( $name){
echo $name "\n";
return 'This value was loaded';
}
}
// Try to use
Foo $foo;
echo $foo->bar . "\n";
echo $foo->foo . "\n";
There are two "large" approaches to this that I know about:
// First use __get and __set to access internal array containing data
class DbObject {
protected $data = array();
public function __get( $propertyName){
// Cannot use isset because of null values
if( !array_key_exits( $propertyName,$this->data)){
throw new Exception( 'Undefined key....');
}
return $this->data[ $propertyName];
}
// Don't forget to implement __set, __isset
}
// Second try to call getters and setter, such as:
class DbObject {
public function getId() {
return $this->id;
}
public function __get( $propertyName){
$methodName = 'get' . ucfirst( $propertyName);
if( !method_exits( array( $this, $methodName)){
throw new Exception( 'Undefined key....');
}
return $this->$methodName();
}
}
To sum up... First approach is easy to implement, is fully automatized... You don't need large amount of code and sources would be pretty much the same for every class. The second approach required more coding, but gives you a better control. For example:
public function setDate( $date){
$this->date = date( 'Y-m-d h:i:s', strtotime( $date));
}
But on the other hand, you can do this with first approach:
class Fruit extends DbObject {
public function __set( $key, $val){
switch( $key){
case 'date':
return $this->setDate( $val);
default:
return parent::__set( $key, $val);
}
}
}
Or you can use total combination and check for getter/setter first and than try to access property directly...
here is the code how you can use the one function to get different field's value.
function get_fruit($id,$field = ''){
$sql = "select * from table_name where id = $id";
$result = mysql_fetch_object(mysql_query($sql));
if($field != ''){
return $result->$field;
}else{
return $result;
}
}
echo get_fruit(1,'field_name');
class data_retrieve
{
public $tablename;
public $dbname;
public $fieldset;
public $data_array;
public $num_rows
function __construct()
{
$this->tablename='junk';
$this->dbname='test';
$this->fieldset=array('junk_id');
}
function getData($where_str)
{
$this->data_array= array();
global $dbconnect, $query;
if($dbconnect ==0 )
{
echo" Already Connected \n ";
$dbconnect=db_connect("objectdb") or die("cannot connect");
}
$where_str;
if(empty($where_str))
{
$where_str=NULL;
}
else
{
$where_str= "where". $where_str ;
}
$query= "select * from $this->tablename $where_str";
$record= mysql_query($query) or die($query);
$recNo=mysql_num_rows($record);
$this->num_rows=$recNo;
while($row= mysql_fetch_assoc($record))
{
$this->data_array[]=$row;
}
mysql_free_result($record);
return $this->data_array;
}
class fruit extends data_retrieve
{
function __construct()
{
parent::__construct();
$this->tablename='fruit';
$this->fieldset=array('fruit_id','fruit_name','date');
}
}
then
in your file create a fruit object like
$str="fruit_id=5";
$fruit_data = new fruit();
$records=$fruit_data->getData($str);
to display
foreach($records as $row )
{
print <<< HERE
<label class='table_content' > $row[fruit_id]</label>
<label class='table_content' > $row[fruit_name]</label>
<label class='table_content' > $row[date]</label>
HERE;
}
Related
I have a PHP class whose instances are rows in my database table. The class has methods that allow one to create, read, update, and delete rows corresponding to the given instance of the class. It works well as it stands, but I currently name each of the database columns explicitly in my code. As a result, when I add or delete columns (an occasional, but not trivially infrequent action), I must update the class to include those column names. I would like to abstract my code so that it queries the database first to determine what the columns it needs to fill in. (Incidentally, I'm working with PHP 7.2)
The code below is what I've got now, but I'm at a loss as to how to abstract it so that it is not tied to spelling out each specific column name.
<?php
class Example extends DatabaseObject {
static protected $table_name = "data";
static protected $db_columns = ['id','column1', 'column2', 'column3', 'column4'];
public $id;
public $column1;
public $column2;
public $column3;
public $column4;
public function __construct($args=[]) {
$this->column1 = $args['column1'] ?? '';
$this->column2 = $args['column2'] ?? '';
$this->column3 = $args['column3'] ?? '';
$this->column4 = $args['column4'] ?? '';
}
public function attributes() {
$attributes = [];
foreach (static::$db_columns as $column) {
if ($column === "id") { continue; }
$attributes[$column] = $this->$column;
}
return $attributes;
}
public function save() {
// calls private methods to validate, sanitize, then create or update, acc. to context
// to create, it preps an sql statement based on a call to attributes()
}
}
?>
I'm not sure how far you want to take this. This is one way that you can abstract the data for a row.
You will notice that I use set and get functions to access the data.
class Example extends DatabaseObject {
static protected $table_name = "data";
static protected $db_columns = ['id', 'column1', 'column2', 'column3', 'column4'];
public $id;
protected $data = [];
public function __construct($args=[]) {
foreach(self::$db_columns as $columnName) {
if ( isset($args[$columnName]) ) {
$this->data[$columnName] = $args[$columnName];
}
}
}
public function set($columnName, $value)
{
if ( in_array($columnName, $this->data) && isset($this->data[$columnName]) ) {
$this->data[$columnName] = $value;
}
}
public function get($columnName)
{
if ( isset($this->data[$columnName]) ) {
return $this->data[$columnName];
}
return null;
}
public function attributes() {
$attributes = [];
foreach (static::$db_columns as $columnName) {
if ($column === "id") { continue; }
$attributes[$columnName] = $this->data[$columnName];
}
return $attributes;
}
public function save() {
// calls private methods to validate, sanitize, then create or update, acc. to context
// to create, it preps an sql statement based on a call to attributes()
}
}
You may need to look at the magic methods __get() and __set(). Basically, they are class methods that are triggered when trying to access a property that doesn't exist in your class, so you can have a fallback for them.
Please take a look at the documentation: https://www.php.net/manual/en/language.oop5.overloading.php#object.set
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 overloaded in one of my models the afterFind() function:
public function afterFind()
{
parent::afterFind();
echo "<PRE>";
echo var_dump($this);
echo "</PRE>";
die();
}
I get the dump in case that the query result is NOT empty.
But this is not getting called in case that the query that gets executed returns no result.
I need to call it especially in the case that no result is found to try to load the requested data via a different source then.
How can i achieve this?
[EDIT]
Find gets called by:
return $this->hasOne(Myclass::className(), ['id' => 'key_id']);
since hasOne(...) uses
public function hasOne($class, $link)
{
/* #var $class ActiveRecordInterface */
/* #var $query ActiveQuery */
$query = $class::find();
$query->primaryModel = $this;
$query->link = $link;
$query->multiple = false;
return $query;
}
::find() method is from ActiveRecord class and creates your ActiveQuery object
->afterFind() method is from ActiveQuery class/object, but it gets triggered only in case query returns non-empty result
If you need to do some action regardless of whether query returned result or not, you can:
Simply use your relation method
$query = $this->hasOne(Myclass::className(), ['id' => 'key_id']);
// Do your stuff here and...
return $query;
If you search for some more global solutions, then you can extend yii\db\ActiveRecord inside your own active record class, e.g. app\components\MyActiveRecord and override __get method. Then use MyActiveRecord as a base class for your models (Myclass in your example) instead of usual ActiveRecord.
namespace app\components;
class MyActiveRecord extends \yii\db\ActiveRecord {
public function __get($name)
{
if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
return $this->_attributes[$name];
} elseif ($this->hasAttribute($name)) {
return null;
} else {
if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
return $this->_related[$name];
}
$value = parent::__get($name);
if ($value instanceof ActiveQueryInterface) {
$result = $this->_related[$name] = $value->findFor($name, $this);
// Do your stuff here.
return $result;
} else {
return $value;
}
}
} }
I want to create properties that are set to mysql data.
class MyClass{
private $a = $r['a'];
private $b = $r['a'];
private $c = $r['c'];
}
I know this is incorrect syntax but I want you to get the idea.
I could create a method that returns a requested mysql data, but I don't want the function to be called for every single row.
You need to implement the magic method __get.
Something like:
class MyClass {
protected $_row = array();
public function __get( $name )
{
if (array_key_exists($name, $this->_row)) {
return $this->_row[$name];
}
return null;
}
public function __isset( $name )
{
return array_key_exists($name, $this->_row);
}
}
And you could used as:
$obj = new MyClass();
$obj->load(); // Or any method to load internal data
echo $obj->a . $obj->b;
Why reinvent the wheel ?
check this mysqli_result::fetch_object
I'm trying to extend my ActiveRecord class with some dynamic methods. I would like to be able to run this from my controller
$user = User::find_by_username(param);
$user = User::find_by_email(param);
I've read a little about overloading and think that's the key. I'v got a static $_attributes in my AR class and I get the table name by pluralizing my model (User = users) in this case.
How do I do this? All models extends the ActiveRecord class.
You have to use the __callStatic() magic method, which is available as PHP5.3
public static function __callStatic($name, $arguments) {
/*
Use strpos to see if $name begins with 'find_by'
If so, use strstr to get everything after 'find_by_'
call_user_func_array to regular find method with found part and $arguments
return result
*/
}
This might also be of use, it's more complex, but it allows true dynamic functions with access to member variables.
class DynamicFunction {
var $functionPointer;
var $mv = "The Member Variable";
function __construct() {
$this->functionPointer = function($arg) {
return sprintf("I am the default closure, argument is %s\n", $arg);
};
}
function changeFunction($functionSource) {
$functionSource = str_replace('$this', '$_this', $functionSource);
$_this = clone $this;
$f = '$this->functionPointer = function($arg) use ($_this) {' . PHP_EOL;
$f.= $functionSource . PHP_EOL . "};";
eval($f);
}
function __call($method, $args) {
if ( $this->{$method} instanceof Closure ) {
return call_user_func_array($this->{$method},$args);
} else {
throw new Exception("Invalid Function");
}
}
}
if (!empty($argc) && !strcmp(basename($argv[0]), basename(__FILE__))) {
$dfstring1 = 'return sprintf("I am dynamic function 1, argument is %s, member variables is %s\n", $arg, $this->mv);';
$dfstring2 = 'return sprintf("I am dynamic function 2, argument is %s, member variables is %s\n", $arg, $this->mv);';
$df = new DynamicFunction();
$df->changeFunction($dfstring1);
echo $df->functionPointer("Rabbit");
$df->changeFunction($dfstring2);
$df->mv = "A different var";
echo $df->functionPointer("Cow");
};