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);
Related
I am new in PHP OOP and was wondering if someone could help me with this.
I have a basic class with one method which returns data from database. Currently I am calling the method which displays everything inside the function.
Here is my class Definition:
class Products{
//properties
public $familyName = "";
public $familyProduct = "";
//Methods
public function getFamily($catId){
global $conn;
$sql = "SELECT * FROM product_family WHERE catID = '$catId'";
$result = $conn->query($sql);
if($result->num_rows > 0){
while($row = $result->fetch_assoc()){
echo "<li>".$row['familyName']."</li>";
echo "<li>".$row['familyProduct']."</li>";
}
}
}
}
Here is how I call the method:
$Products = new Products;
$Products->getFamily( 4 );
This works however, how can I assign each data coming from database ( ex familyName, familyProduct ) into variables inside class implementation and then access them individually where ever I need to. Something like this:
$Products = new Products;
$Products->familyName;
$Products->familyProduct;
I have empty properties but I am not sure how can I assign values to them coming from the loop and then return them each.
Thanks,
There are view things I would change in your Code.
Don't make Properties public use use Getters and Setters.
This will protect you Object from being used the wrong way e.g. now you can't change the familyName from outside: $products->familyName = "some value" because this would make the data of the object corrupt.
global $conn; is a no go in OOP use the construct of the Object,
in your case $products = new Products($conn);
Now you can set a Cat ID $products->setCatId(4); and read the result
$familyName = $products->getFamilyName(); or $familyProduct = $products->getFamilyProduct();
If you have more than one result you will get an array, if catId will always result one row you can delete this part. If you learn more about OOP you will find out that the hole SQL stuff can be done with a separate Object, but this is off Topic.
class Products
{
// Properties
protected $conn;
protected $catId;
protected $familyName;
protected $familyProduct;
public function __construct($conn)
{
$this->conn = $conn;
}
// set Cat ID and get date
public function setCatId($catId)
{
$this->catId = (int) $catId;
$this->getDate();
}
public function getCatId()
{
return $this->catId;
}
// get Family Name
public function getFamilyName()
{
return $this->familyName;
}
// get Family Product
public function getFamilyProduct()
{
return $this->familyProduct;
}
// get date
protected function getDate()
{
$sql = "SELECT * FROM product_family WHERE catID = '$this->catId'";
$result = $this->conn->query($sql);
// Default if no result
$this->familyName = null;
$this->familyProduct = null;
// if one Result
if ($result->num_rows == 1)
{
$row = $result->fetch_assoc();
$this->familyName = $row['familyName'];
$this->familyProduct = $row['familyProduct'];
}
if ($result->num_rows > 1)
{
$this->familyName = [];
$this->familyProduct = [];
while ($row = $result->fetch_assoc())
{
$this->familyName[] = $row['familyName'];
$this->familyProduct[] = $row['familyProduct'];
}
}
}
}
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've written a class which in the construct accesses the db and gets a list of names. These names go into an associative array e.g. ('name' => 'id').
i.e. the point is to pass in the name to get back an ID:
$id = names::nameToId('some name');
print $id;
// prints int
The problem is when I try and return the array from the construct I get an error:
Notice: Undefined variable: nameArray in (etc)
Here is the code so far:
class nameToId {
public $nameArray;
private $mysqli;
public function __construct($mysqli) {
...
while($row = mysqli_fetch_assoc($res)) {
$nameArray[$row['name']] = $row['id'];
}
return $nameArray;
}
static public function nameToId($name) {
$nameId = $nameArray[$name];
return $nameId;
}
}
$namesToId = new nameToId($mysqli);
$nameId = $namesToId::nameToId('some name');
echo $nameId;
Why doesn't $nameArray get passed to nameToId()? I'm new to classes, and I thought by declaring $nameArray as public when I first create the class that it would make it available. I have also tried to make it global even though I know that is not good form but even still it didn't work.
Because you cannot return anything from a constructor. Any return value is being ignored and just goes into the aether. $nameArray is a local variable and is not shared in any other scope, i.e. you can't access it in nameToId. Further, since nameToId is static, it won't have access to data from any non-static methods like __construct to begin with.
You probably want something like this:
class nameToId {
public $nameArray;
private $mysqli;
public function __construct($mysqli) {
...
while ($row = mysqli_fetch_assoc($res)) {
$this->nameArray[$row['name']] = $row['id'];
}
}
public function nameToId($name) {
return $this->nameArray[$name];
}
}
$namesToId = new nameToId($mysqli);
echo $namesToId->nameToId('some name');
Fix your code:
class nameToId {
public static $nameArray;
private $mysqli;
public function __construct($mysqli) {
$this->mysqli = $mysqli;
$sql = 'SELECT id, name FROM teams';
$res = mysqli_query($this->mysqli,$sql);
while($row = mysqli_fetch_assoc($res)) {
self::$nameArray[$row['name']] = $row['id'];
}
}
static public function nameToId($name) {
$nameId = self::$nameArray[$name];
return $nameId;
}
}
$namesToId = new nameToId($mysqli);
$nameId = $namesToId::nameToId('some name');
echo $nameId;
I have seen these codes:
$result = $db->result($query);
$rows = $result->fetchAll();
how can I do similar effect? ($result contains methods?)
I think this is what you are looking for:
<?php
class test{
private $value = 0;
function foo(){
$this->value = 1;
return $this;
}
function bar(){
$this->value = 2;
echo $this->value;
}
}
$test = new test();
$result = $test->foo();
$result->bar();
?>
By having the method return itself, you can chain them together in this fashion.
Strictly speaking, you're asking about OOP in PHP, in which case, this is a reasonable example:
class HasResultMethod
{
public function result( $query )
{
return new HasFetchAllMethod();
}
}
class HasFetchAllMethod
{
public function fetchAll(){}
}
// you have a variable with a result method that has one parameter.
$result = $db->result($query);
// that returns an object which has a fetchAll method.
$rows = $result->fetchAll();
You probably are dealing with some wrapper around PDO, a library to interface with databases. Their query methods will return a PDOStatement which has methods which will allow you to get results from the DB. result is either a typo, or it behaves in a very similar way.
I got it already. What a great hint Headspin
http://sandbox.phpcode.eu/g/147bd.php
<?php
class foo{
function bar(){
return $this;
}
function fetch(){
echo "yeah";
}
}
$foo = new foo();
$result = $foo->bar();
$result->fetch();
That is easy
$db is instance of class that returnes class, so when you say
$db->result($query);
$db will return object
e.g.
//this method is inside $db class
function result($query)
{
$result = new Result();
$result->rows = mysql_query...
return $result;
}
and when you say
$result->fetchAll();
that is method inside class Result that will fetch all rows saved inside $result->rows;
e.g.
//method inside Result class
function fetchAll()
{
//fetch rows inside variable $this->rows
}
So basically what you can do with ORM (object relational mapping), you can return Array of objects, each object will represent one record from db
e.g.
Class User
{
var $ID;
var $Name;
var $LastName;
var $Email;
function load($row)
{
$this->ID = $row["ID"];
... etc
}
function save()
{
$sql = "update tbl_users set Name=:Name, LastName=:LastName, Email=:Email where ID=:ID";
//then execute your query
}
}
so how to get list of objects, its easy
select all records and add them into array
$ar = new Array();
for($i = 0; $i < count($rows); $i++)
{
$r = new User();
$r->load($rows[$i]);
}
return $ar;
simple as that...
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 :(