Clarify a line/s --> setting sql data into object variables - php

This is my first question here and I will try to clarify it as much as possible.
I am a begginer and I am going through lynda - beyond PHP MySQL lessons when I got to this part.
Code is working just fine, I just need better explanation for myself to the line that is commented in a code.
require_once('database.php');
class User {
public $id;
public $username;
public $password;
public $first_name;
public $last_name;
public static function find_all() {
return self::find_by_sql("SELECT * FROM users");
}
/////
public static function find_by_sql($sql="") {
global $database;
$result_set = $database->query($sql);
$user_array = array();
while ($row = $database->fetch_array($result_set)) {
$user_array[] = self::instantiate($row);
}
return $user_array;
}
And finaly lines that i almost understand :)
private static function instantiate($row) {
$user = new self;
foreach($row as $attribute=>$value){
if($user->has_attribute($attribute)) {
$user->$attribute = $value; /// THIS LINE BUGS ME
}
}
return $user;
}
private function has_attribute($attribute) {
$user_vars = get_object_vars($this);
return array_key_exists($attribute, $user_vars);
}
}
So I think I don't understand array_key_exists which returns TRUE or FALSE, in my case its true, but then line $users->$attributes =$value ; makes no sense for me,
So, I check if keys from fetch array MATCH variable names from object,
if($user->has_attribute($attribute)) { //and then this is true,perform nxt line
$user->$attribute = $value; // i got match of attribute above,how does it put values in $user_vars???
I know it says something like " if user has same attribute as key from that fetch array then put into that same attribute value of that attribute $value but i just dont see how it is done when i never returned object variables
Thank you for your time !
edit:
class variables names are equal to names of column_names from database

The User class has public attributes $id, $username, $password, etc. That means that you can assign values to the attributes in the following form:
$u = new User;
$u->id = 123;
$u->username = 'username';
and using dynamic property names:
$prop = 'id';
$u->$prop = 123;
$prop = 'username';
$u->$prop = 'username';
This is just the thing that happens in the lines that you don't understand:
if ($user->has_attribute($attribute)) {
$user->$attribute = $value;
The has_attribute method fetches all attributes of the $user object with get_object_vars function. The latter fetches the object properties as an array:
$user_vars = get_object_vars($this);
/* i.e.
$user_vars = array (
'id' => ...,
'username' => ...,
...
);
*/
Then the has_attribute method checks if the given $attribute key exists in the array of properties:
return array_key_exists($attribute, $user_vars);
If it exists (true), the $attribute property is assigned to $value.

Related

How to fetch data with PDO into class with enum property?

Since the enum feature was released in PHP8.1, I was wondering how can I fetch data from my database with PDO into an object with an ENUM property.
I have the following enum:
enum UserType{
case Master: 1;
case Admin: 2;
case Manager: 3;
}
And I have the following class:
class User{
private int $id;
private string $name;
private UserType $userType;
}
Every time I try to execute the code below I get the error Cannot assign int to property User::$userType of type UserType
Database::getInstance()->fetchObject(sql: "SELECT id, name, userType FROM user WHERE id = 1", class_name: User::class);
I want to know if there is a way to make the code above works or what is the best way to implement the new enum feature in my code.
My fetchObject code:
public function fetchObject($sql, array $args = array(), string $class_name = "stdClass"): mixed
{
$sql = self::$instance->prepare($sql);
if(empty($args)){
$sql->execute();
} else{
$sql->execute($args);
}
$object = $sql->fetchObject($class_name);
$sql->closeCursor();
return $object;
}
You can't do it with fetchObject(). The reason is that you can't assign int to a property of type UserType. You can use a magic method __set(), but you would have to not declare the property (this is highly not recommended).
You can use __set with lazy property initialization and PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE. If that's confusing for you, this is because it is confusing.
enum UserType:int {
case Master = 1;
case Admin = 2;
case Manager = 3;
}
class User
{
private int $id;
private string $name;
private UserType $userType;
public function __construct()
{
// unset it for lazy initialization (PDO will call __set method instead)
unset($this->userType);
}
public function __set($key, $value)
{
if ($key === 'userType') {
$this->userType = UserType::from($value);
}
}
}
// Tell PDO to call constructor first,
// then try to assign the property if it exists or call magic method __set()
$stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, User::class);
$user = $stmt->fetch();
It might be easier to just stick to doing this in the constructor. You could then fetch the row as an associative array and unpack it into the constructor.
class User
{
private UserType $userType;
public function __construct(private int $id, private string $name, int $userType)
{
$this->userType = UserType::from($userType);
}
}
Then your fetchObject() would look like:
public function fetchObject($sql, array $args = [], string $class_name = "stdClass"): ?object
{
$stmt = self::$instance->prepare($sql);
$stmt->execute($args ?: null);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? new $class_name(...$row) : null;
}
You need to unpack the row because PDO doesn't have the capability to pass values to the constructor.

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

Why isn't the array I return in my __construct class available in the following function?

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;

pass variable as an array

Hi in my user class i am passing the variables in constructor instead of passing variables i want to pass as an array.
Class User{
var $userid;
var $alias;
var $firstname;
var $password;
var $email;
var $photo;
var $avatar_url;
var $thumb;
var $crop_url;
var $crop_position;
protected $db;
function User($userid='',$alias='',$firstname='',$lastname='',$password='',$email='',$photo='',$avatar_url='',$thumb='',$crop_url='',$crop_position='',PDO $db){
$this->userid=$userid;
$this->alias= $alias;
$this->firstname=$firstname;
$this->lastname=$lastname;
$this->password= $password;
$this->email=$email;
$this->photo= $photo;
$this->avatar_url= $avatar_url;
$this->thumb= $thumb;
$this->crop_url= $crop_url;
$this->crop_position= $crop_position;
$this->db = $db;
}
}
and the variable coming in constructor
$user=new User($id,$alias,$firstname,$lastname,$password,$email,$photo='',$avatar_url='',$thumb='',$crop_url='',$crop_position='',$db);
this all are coming through the request variable.
Please help.Thanks
You didn't clarify what your issue is. If you want to pass an array, then pass an array. If you cannot change your API for the ctor for BC reasons, you can add another method to your User class, e.g.
class User
{
// other code …
public function populateFromArray(array $data)
{
foreach ($data as $property => $value) {
if (property_exists($this, $property)) {
$user->$property = $value;
}
}
}
}
Then you can do
$user = new User('','','','','','','','','','','',$db);
$user->populateFromArray(array(
'id' => 'johndoe',
'email' => 'jdoe#example.com',
// other …
));
The ctor call looks pretty ugly, so if you can afford to change the API, I suggest to move required arguments to the beginning of the signature. This is suggested good practise in the PHP Manual anyway, e.g. change your ctor to
public function __construct(PDO $pdo, $id = '', $email = '', …) {
Note that I changed it to the new PHP5 style constructor. Naming the ctor after the class name is PHP4 style and is not compatible with namespaces as of PHP5.3.3.. You might also want to change your var keyword to public (or better yet protected and add proper getter and setter).
Since everything but the PDO instance is optional, you can just as well remove all the optional arguments and always use your new populateFromArray method instead, reducing the instantiation to
$user = new User($db);
$user->populateFromArray($dataArray);
If you want to implement the populateFromArray functionality in other classes as well, you might want to consider adding an interface IPopulate, e.g.
interface IPopulate
{
public function populateFromArray(array $data);
}
But your classes implementing this interface would have to add the method body each time, which is a bit redundant given that our populating code is quite generic. With php.next there will be traits for an elegant solution for horizontal reuse like this.
Yet another possible solution would be to just use the Reflection API to pass the array to your regular ctor (though you should give it a benchmark afterwards because the Reflection API is considered slow). See
Pass arguments from array in php to constructor
User.php Class:
// define your default values here. so that you will not have to pass them
// everytime when you pass the array to `AssignVal` function.
Class User{
var $userid = '';
var $alias = '';
var $firstname = '';
var $password = '';
var $email = '';
var $photo = '';
var $avatar_url = '';
var $thumb = '';
var $crop_url = '';
var $crop_position = '';
protected $db;
function User(PDO $db) {
$this->db = $db;
}
}
Index.php (where you want the object to be created):
$user = assignVal('User',$arr);
functions.php (a place where you include all your functions):
// the following function creates an object with the array you send it.
// this is specially useful if your class contains a lot of variables
// thus minimizing the manual work of defining constructors again and again...
function assignVal($obj,$arr,$child=null) {
if (is_string($obj)) $obj = new $obj();
$applyon = $child == null ? $obj : $obj->$child;
if(!empty($arr)) {
foreach ($arr as $name => $val) {
$applyon->$name = $val;
}
}
if ($child != null) $obj->$child = $applyon;
else $obj = $applyon;
return $obj;
}
First create your array:
$Usr_info = array('id' => 0, 'alias' => 'value'); //add all the values you want like that
And then in your constructor you can access each item in the array:
function User($Usr_info)
{
$this->userid = $Usr_info['id'];
//and so on...
}
version for PHP5
class User {
private $userid;
...
public function assign ($class_member, $value) {
$this->$class_member = $value;
}
public function __construct ($db) {
$this->db = $db;
}
}
...
$user = new User($db);
$user->assign('userid', 1);

Object Oriented PHP Arrays

I've never tried OO PHP before so I decided to make a simple CMS to learn more. I am having a problem loading values into a multi-dimensional array.
class Article {
private $index = 0;
private $article;
public function Article() {
$get_articles = mysql_query("SELECT * FROM `articles`");
while ($result = mysql_fetch_array($get_articles)) {
echo $result["article"];
$this->article[$index]["Tags"] = $result["tags"];
$this->article[$index]["Categories"] = $result["categories"];
$this->article[$index]["Date"] = $result["date"];
$this->article[$index]["Article"] = $result["article"];
$this->article[$index]["URL"] = $result["url"];
$index++;
}
}
public function getArticle($articleID) {
return $this->article[$articleID]["Article"];
}
public function getTags($articleNumber) {
}
public function getCategories($articleNumber) {
}
public function getDate($articleNumber) {
}
}
The line echo $result["article"] outputs the one and only article value just fine, but apparently doesn't put it into the array?
$art = new Article();
echo $art->getArticle(0);
This doesn't output the article however. Would someone so kindly point out my noob mistake?
You didn't initialize your array.
$this->article = array();
while ($result = mysql_fetch_array($get_articles)) {
$this->article[$index] = array();
You probably should define your $index variable before using it in the loop. Maybe set it to the primary key field you retrieved from your query.
<?php
$index = $result['id'];
$this->article[$index]['tags'] = ...
You also need to initialize the $article member variable.
<?php
class Article {
private $article = array();
Remember that you define member variables within a class to be referenced via $this-> so you also don't need to define private $index = 0; in your class definition. Just define it inside the method.
You'll notice you used $this->article but not $this->index if you want to keep track of the length for the life of the object you'll need to replace $index with $this->index

Categories