Deprecated: Creation of dynamic property ... is deprecated
I'm seeing this more and more, and I'm not sure what I need to do to stop this warning.
This is my class:
class database {
public $username = "root";
public $password = "pasword";
public $port = 3306;
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->{$key} = $value;
}
}
}
This is how I'm instanacing it.
$db = new database(array(
'database' => 'db_name',
'server' => 'database.internal',
));
Which gives me two messages:
Deprecated: Creation of dynamic property database::$database is deprecated
Deprecated: Creation of dynamic property database::$server is deprecated
The warning is telling you that there is a property you are trying to set which isn't listed at the top of the class.
When you run this:
class database {
public $username = "root";
public $password = "pasword";
public $port = 3306;
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->{$key} = $value;
}
}
}
$db = new database(array(
'database' => 'db_name',
'server' => 'database.internal',
));
It is roughly equivalent to this:
class database {
public $username = "root";
public $password = "pasword";
public $port = 3306;
}
$db = new database;
$db->database = 'db_name';
$db->server = 'database.internal';
The warning is that there is no line in the class definition saying that $db->database or $db->server exist.
For now, they will be dynamically created as untyped public properties, but in future, you will need to declare them explicitly:
class database {
public $database;
public $server;
public $username = "root";
public $password = "pasword";
public $port = 3306;
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->{$key} = $value;
}
}
}
$db = new database(array(
'database' => 'db_name',
'server' => 'database.internal',
));
In some rare situations, you actually want to say "the properties of this class are whatever I decide to add at run-time"; in that case, you can use the #[AllowDynamicProperties] attribute, like this:
#[AllowDynamicProperties]
class objectWithWhateverPropertiesIWant {
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->{$key} = $value;
}
}
}
So the warnings are coming from the constructor adding dynamic class properties. If you don't have to pass in those fields dynamically and really, it does seem like you're overcomplicating something simple then try it like this.
class database {
public $username = "root";
public $password = "pasword";
public $port = 3306;
public $database = 'db_name';
public $server = 'database.internal';
}
$db = new database();
Is there a reason you needed dynamic parameters? You could also do this:
class database {
public $username = "root";
public $password = "pasword";
public $port = 3306;
public $database;
public $server;
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->{$key} = $value;
}
}
}
If you add the parameters ahead of time, they're not dynamic, and you're just assigning a value to something already existing.
This should work now without any warnings.
$db = new database(array(
'database' => 'db_name',
'server' => 'database.internal',
));
You can avoid the need to specify an explicit list of member variables by storing them in a single array, like this:
class database {
public $data;
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->data[$key] = $value;
}
}
}
Then you can instantiate a database object with any params you want:
$db = new database(array(
'database' => 'db_name',
'server' => 'database.internal',
'foo' => 'bar', // etc.
));
Of course, you'll probably want to document what the expected params are, but at least you'll be able to pass in anything you want, and be able to use it in the future, without changing the class definition.
The warning message you are seeing is related to the use of a feature in PHP called "dynamic properties". Dynamic properties allow you to set and get object properties by using variable names, like you are doing in the __construct method of your database class.
Quick fix:
public function __construct(array $params)
{
$this->username = $params['username'] ?? null;
$this->password = $params['password'] ?? null;
$this->port = $params['port'] ?? null;
}
To fix this warning, you can remove the use of dynamic properties in your code and use a more modern way of setting object properties.
Named arguments:
class database
{
public function __construct(
public string $username,
public string $password,
public int $port,
) {}
}
$db = new database(
username: 'root',
password: 'password',
port: 3306,
);
Alternatively, you can also use the __set and __get magic methods to set and get object properties, like this:
public function __set($key, $value)
{
$this->$key = $value;
}
public function __get($key)
{
return $this->$key;
}
This will allow you to use the $object->property notation to set and get object properties, without triggering the warning message.
An alternative solution, which is specific to this example in that the parameters are quite easy to identify. Databases have common parameters that are referenced.
Using PHP 8 named parameters and Constructor property promotion, allows you to specify all of the possible parameters in the constructor (which is a bit long winded, but great for code completion) and then when creating an instance, the parameter names are fixed...
class database
{
public function __construct(
private string $database = '',
private string $server = '',
private string $port = '',
private string $user = '',
private string $password = ''
) {
}
}
Then call with
$db = new database(
database: 'db_name',
server: 'database.internal',
);
Adding this just above the class{ will fix it:
#[\AllowDynamicProperties]
simply replace this:
class database {
for this:
class database extends \stdClass {
Related
I've got the classes in my structure:
require_once("classes/config.php");
require_once("classes/database.php");
require_once("classes/template.php");
require_once("classes/route.php");
require_once("classes/user.php");
$config = new config;
$route = new route();
$db = new database ($config['db_host'], $config['db_name'], $config['db_user'], $config['db_pasw']);
Class 'template' uses template files.
What is the right way to work with database or user class, for example, in the template class?
How it's working now:
global $user;
But it's not 'good coding practices', right?
So, in User class you can do something like this object-oriented way:
class User{
private $user;
public function getUser(){
return $this->user;
}
}
And to get user in your script,
$userObj = new User();
$user = $userObj->getUser();
Dependency injection.
Collect the data that you want to render in your template in a data set that will be injected into the template render function, like:
// This can be class or array, as you prefer
$viewData = array(
'user' => $user,
'something_else' => ...
);
$template = new Template('id');
$template->render($viewData);
Yes it's not very good.
You can specify the connection variables in a separate configuration class.
Then, connect it to the constructor where necessary and use it.
Thus, there will not be a single global variable in your project.
Here is an example. But you must know. It's a bicycle, just for example. But here there is no DI (Dependency injection):
Config
namespace Model;
class ConfigModel
{
public $db = 'name_db';
public $user = 'user_u';
public $pass = '123456';
public $protocol = 'https:';
public $brand = 'MyBrand.com';
public $salt = 'ougfhdsihgsugsdopgf';
public $path = __DIR__;
}
DB class
namespace Model;
class DB
{
protected $host = 'localhost';
protected $db = '';
protected $charset = 'utf8';
protected $user = '';
protected $pass = '';
public function __construct() {
$config = new ConfigModel();
$this->db = $config->db;
$this->user = $config->user;
$this->pass = $config->pass;
$dsn = "mysql:host=$this->host;dbname=$this->db;charset=$this->charset";
$opt = array(
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC
);
try {
$this->DBconnect = new \PDO($dsn, $this->user, $this->pass, $opt);
}
catch( PDOException $Exception ) {
echo $Exception->getMessage();
echo $Exception->getCode();
if(!empty($Exception->getCode())){
die('Error');
}
}
}
public function showAllUser() {
$db = $this->DBconnect->prepare('SELECT `mail` FROM `users`');
$db->execute();
return $db->fetchAll();
}
}
I don't understand, doyou want to render global the user information like as, for example, the name of the user?I think the correct way to use the user details or the login details is using $_SESSION and after with $_SESSION['user'] you can render the user details in template.
I have a config file that contains an array of settings/parameters. The problem I'm running into is accessing this array from within a class. My first guess was to do something like this.
Settings File:
$config = array (
'db' => array (
'host' => 'localhost',
'password' => 'password',
'port' => 3306,
'database' => 'my_db',
'user' => 'root'
),
'email' => array (
'username' => 'someone#somewhere.com',
'smtp' => 'smtp.somewhere.com',
'ssl' => true,
'password' => 'my_password',
'port' => 587
)
// other stuff
);
The Confiuration class
class CoreConfig
{
static $confArray;
public static function read($name)
{
return self::$confArray[$name];
}
public static function write($name, $value)
{
self::$confArray[$name] = $value;
}
}
And use it like: CoreConfig::write('username', $config['email']['username']);. Example using it in a class:
Usage
class SomeClass {
public function __construct() {}
public function getUserName() {
return CoreConfig::read('username');
}
}
Although this works, it feels like it's considered bad practice. Alternatively I can take the initial $config and load it right away in my $confArray instead "writing", which looks like an ever worse idea. Is there another approach to this?
It sure feels a bit weird to hard code anything in an application, even if you're doing a configuration file. There are many methods for doing what you're trying to do, but the most important thing is to keep all "hard coded" values at the same place.
If you have access to any kind of SQL to do your work, I would recommend to put all configurations values in a data table. Doing this, you avoid having any hard coded values directly in the code, since they're in a database.
Config class
class CoreConfig
{
static $confArray = null;
public static function read($name)
{
if(self::$confArray == null)
self::loadConfig();
return self::$confArray[$name];
}
private static function loadConfig()
{
self::$confArray = DatabaseManager::fetchAll("SELECT * FROM config");
}
}
Database Manager class
class DatabaseManager
{
private static $instance = null;
private static $host = "YOUR HOST NAME HERE";
private static $dbName = "YOUR DATABASE NAME";
private static $dbUser = "YOUR DATABASE USER";
private static $dbPass = "YOUR DATABASE PASSWORD";
private function __construct() {}
private static function getDb()
{
if(is_null(self::$instance))
{
self::$instance = new PDO("mysql:host=" .self::$host. ";dbname=" .self::$dbName, self::$dbUser, self::$dbPass);
self::$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return self::$instance;
}
public static function fetchAll($SQL)
{
$return = null;
try
{
$return = self::getDb()->query($SQL)->fetchAll();
}
catch(Exception $e)
{
// If you want to display errors directly on the page
echo "SQL Query Error<br />";
echo "Request: $SQL<br />";
echo $e->getMessage();
}
return $return;
}
}
You may refer to PDO Documentation on php.net in order to add features to the Database manager. You will have to add more methods if you want to be able to write in the database using PHP.
If you have no database options, then you can use an XML or JSON configuration file and parse it into an array at the moment of reading its values.
Hope this helps!
I made a db connection class like this :
class db_connector{
const host = "localhost";
const user = "root";
const password = "";
const dbname = "test";
public $db;
public function __construct()
{
$database = $this::dbname;
$db = new mysqli($this::host, $this::user, $this::password, $this::dbname);
if($db->connect_errno)
{
die (mysql_connect_error());
}
return $db;
}
}
When i create an object for the class it works fine enough, though then i want to make a query within the class i get the following error:
Fatal error: Call to undefined method db_connector::query()
The object is as follows (outside the class):
$con = new db_connector;
$con->query("SELECT * FROM test");
The connection gets established, just the query gives the error. I thought the object would inherit the mysqli methods since I returned it. Can anybody help me fix this one? Im fairly new in OOP so maybe my logic is not the best.
How about a little magic:
class db_connector{
protected $connectionData = array(
'host' => 'localhost',
'user' => 'root',
'password' => '',
'dbname' => 'test',
);
protected $db;
public function __construct($connectionData=array())
{
$cd = array_merge($this->connectionData, $connectionData);
$this->db = new mysqli($cd['host'], $cd['user'], $cd['password'], $cd['dbname']);
if($this->db->connect_errno)
{
die (mysql_connect_error());
}
}
public function foo()
{
return 'I am foo, i exist so i will be called even if mysqli::foo would exist, because i am defined in this class!';
}
public function __get($property)
{
if(property_exists($this->db, $property)){
return $this->db->$property;
}
}
public function __call($name, $args)
{
if(method_exists($this->db, $name))
return call_user_func_array(array($this->db, $name), $args);
}
}
And call it like:
$db = new db_connector();
$result = $db->query('SELECT foo FROM bar LIMIT 1');
// this should also work:
echo $result->num_rows;
The class constructor won't return the mysqli object as you expect.
$con = new db_connector;
//$con is a db_connector object not a mysqli object
$con->query("SELECT * FROM test");
Try:
$con = new db_connector;
$con->db->query("SELECT * FROM test");
instead. $con->db is a public field which holds the mysqli object.
Edit
Also change:
$db = new mysqli($this::host, $this::user, $this::password, $this::dbname);
if($db->connect_errno)
{
die (mysql_connect_error());
}
return $db;
To:
$this->db = new mysqli($this::host, $this::user, $this::password, $this::dbname);
if($this->db->connect_errno)
{
die (mysql_connect_error());
}
I have a database class that uses PDO. Here's a portion example of it:
class db {
private $host;
private $username;
private $password;
private $con;
private $pdo;
public function __construct( $database = "dnname" )
{
$this->host = "localhost";
$this->username = "username";
$this->password = "pass";
$conStr = "host={$this->host};dbname={$database}";
try {
$this->pdo = new PDO( "mysql:$conStr", $this->username, $this->password );
$this->pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
catch( PDOException $e ) {
echo "error ". $e->getMessage();
}
}
public function fetch_single_row($sql, $data)
{
if( $data !== null )
$data = array_values( $data ); // Incase it's an associative array
$sel = $this->pdo->prepare( $sql );
$sel->execute( $data );
$sel->setFetchMode( PDO::FETCH_OBJ );
$obj = $sel->fetch();
return $obj;
}
I would like to use this class and its functions (it has more than I've included) inside other classes.
I have tried many many different things, but the only thing that works so far is starting a new instance of db in every new class which I think is bad practice. For instance:
class cms {
function cms(){
$this->db = new db();
}
function is_admin($id) {
if($this->db->fetch_single_row("SELECT id FROM user WHERE id = ? LIMIT 1", array($id))){
return true;
} else {
return false;
}
}
in my index.php, I include these classes and use them:
include("db.class.php");
include("cms.class.php");
$cms = new cms();
if($cms->is_admin($id)){
//code here
}
What is the correct way to accomplish this?
Take a look into the Singleton Design Pattern, it works great for a DB class
I used to use a class like this for quite a while
abstract class DB
{
protected static $instance;
protected $db;
protected static $host = 'host';
protected static $user = 'user';
protected static $pass = 'pass';
protected static $database;
public static function getInstance()
{
if (!isset(self::$instance)) self::$instance = new static();
return self::$instance;
}
protected function __construct()
{
$this->db = new PDO(sprintf('mysql:host=%s;dbname=%s', static::$host, static::$database), static::$user, static::$pass);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public static function db()
{
return static::getInstance()->db;
}
public function fetch_single_row($sql, $data)
{
if( $data !== null )
$data = array_values( $data ); // Incase it's an associative array
$sel = self::db()->prepare( $sql );
$sel->execute( $data );
$sel->setFetchMode( PDO::FETCH_OBJ );
$obj = $sel->fetch();
return $obj;
}
}
I would then extend that class for each different database I would need to connect to
class Main extends DB
{
protected static $database = 'db';
}
You can then use the class like this
$db = Main::getInstance();
$obj = $db->fetch_single_row($sql, $data);
Not sure of OOP syntax to do this...
I'd like to have aclass that calls the mysqli object
class Voovid_DB {
private $host = 'localhost';
private $user = 'blahblah';
private $password = 'blahblah';
private $name = 'blahblah';
public function __contstuct(){
$dbh= new mysqli( $this->host, $this->user, $this->password, $this->name );
return $dbh;
}
//get and set methods for host, user etc... go here
}
now I'd like to access all the mysqli methods like so
$dbconnect = new Voovid_DB();
if ( $result = $dbconnect->query( "SELECT first_name, last_name FROM members WHERE member_id=9" ) ) {
while ( $row = $result->fetch_assoc() ) {
$first_name = ucfirst( $row['first_name'] );
$last_name = ucfirst( $row['last_name'] );
}
} else {
$errors = $dbconnect->error;
}
i'm new to PHP OOP and not sure how to get to the mysqli methods inside the Voovid_DB class
You have to either extend the MySQLi class, or build a proxy around it.
The easiest is probably to extend it:
class Voovid_DB extends MySQLi {
private $host = 'localhost';
private $user = 'blahblah';
private $password = 'blahblah';
private $name = 'blahblah';
public function __construct(){
// call parent (MySQLi) constructor
parent::__construct( $this->host, $this->user, $this->password, $this->name );
}
// no need for other methods, they already are there
}
Notice the extends MySQLi.
Then your second code snipet should work.
Or, build a proxy:
class Voovid_DB {
private $host = 'localhost';
private $user = 'blahblah';
private $password = 'blahblah';
private $name = 'blahblah';
private $dbh;
public function __construct(){
$this->dbh = new MySQLi($this->host, $this->user, $this->password, $this->name);
}
// this will proxy any calls to this class to MySQLi
public function __call($name, $args) {
return call_user_func_array(array($this->dbh,$name), $args);
}
}
You could define a __call method:
public function __call($method, $arguments) {
return call_user_func_array(array($this->dbh, $method), $arguments);
}
__call is invoked if an undefined or inivisible method is called.
you code is correct.
the only thing you have to do is to make sure that you define your functions in Voovid_DB as public.
private or protected methods cannot be accessed from other classes
store your mysqli object in a public field in your class then you can access it like this:
$dbconnect->mysqlField->query
Constructors aren't supposed to return anything. When you say $dbconnect = new Voovid_DB(); you would normally be trying to create a Voovid_DB object, but it looks like you're using it to try an make a mysqli object. Don't make this the constructor and call the function after you create the voovid_db object.
$obj = new voovid_DB();
$dbConnect = $obj->createMysqli();