As the title says there is a problem accessing variable (associative array) inside class from included file. Here is the source code both class and include file:
require("applications/cw_database.php");
require("config/dbConfig.php");
require("config/appConfig.php");
class APP_ASSESMENTS
{
private $dbObj;
private $DisplayOutput = "";
public function __construct($PageParams)
{
try
{
$dbObj = new CW_DB($dbConfig['hostname'],$dbConfig['username'],$dbConfig['password'],$dbConfig['name'],$dbConfig['port']);
} catch (Exception $e) {
throw new ErrorException($e);
}
}
...
The other part has nothing to do with $dbConfig.
Also this is the included file (config/dbConfig.php):
/*
Testing configuration for MySQL database
*/
$dbConfig['username'] = "phpcoursework"; // changed on demand
$dbConfig['password'] = "phpcoursework"; // changed on demand
$dbConfig['hostname'] = "localhost"; // changed on demand
$dbConfig['name'] = "students"; // changed on demand
$dbConfig['port'] = 3306; // default for MySQL
First, $dbObj will not automatically assume class member scope, it will create a local copy of CW_DB and discard it when __construct returns. You need to explicitly reference the property;
$this->dbObj = ...
Anyway, global state using global as suggested by others will "work", but if you're using OOP practices you're best not to do that. You can actually return from an include(), so an option would be to do the following:
// your config file dbConfig.php
return array(
'username' => "phpcoursework",
'password' => "phpcoursework",
'hostname' => "localhost",
'name' => "students",
'port' => 3306,
);
And inject it into the object, via constructor or method (here's constructor)
class APP_ASSESMENTS
{
private $dbObj;
public function __construct($dbConfig, $PageParams)
{
$dbObj = new CW_DB($dbConfig['hostname'], $dbConfig['username'],
$dbConfig['password'], $dbConfig['name'], $dbConfig['port']);
// ...
}
}
// include() here, will actually return the array from the config file
$appAssesments = new \APP_ASSESMENTS(include('dbConfig.php'), $PageParams);
It would be recommended that you go another level up: instead, inject the database object itself, taking the dependency out of your APP_ASSESSMENTS class.
(Also, PascalCase is the typical convention of class naming, such as AppAssessments and CwDb)
$dbObj = new CwDb(include('dbConfig.php'));
$appAssessments = new AppAssessments($dbObj, $etc, $etc);
This simple change allows you to remove the dependency from AppAssessments on CwDb. That way, if you extend CwDb for some reason, you can just pass in an instance of the extended class without having to change any code in AppAssessments
You can change the AppAssessments constructor like so:
public function __construct(CwDb $db, $etc, $etc){
$this->db = $db;
// ...
}
This takes advantage of PHPs (limited, albeit still useful) type-hinting, ensuring the first argument is always of the correct type.
This plays into part of the open/closed principle: classes should be open to extension but closed for modification.
Includes are used in the scope of access. So, to access the variables you need to include the files within your class. As earlier mentioned global will let you access variables from another scope too. But global should be used with caution! See the documentation.
See the manual for more information.
Edit: I need to make it clear that globals are never a good alternative for handling these kind of critical variables..
public function __construct($PageParams){
global $dbConfig;
try{
$dbObj = new CW_DB($dbConfig['hostname'],$dbConfig['username'],$dbConfig['password'],$dbConfig['name'],$dbC onfig['port']);
} catch (Exception $e) {
throw new ErrorException($e);
}
}
or you could use $GLOBALS['dbConfig'].
Related
I'm using Monolog to create my app's logging system. In the core app file, after I create a new Monolog object, I need to select the log level that I want to print in the log file. I want to use a global constant LOG_LEVEL which could be 'DEBUG', 'INFO', etc. I need the Monolog class to treat its value as a class constant.
// content of config.php
// Here I declare the constants in a separate file called 'config.php'
define("LOG_FILE", "patch/to/my/log.log");
define("LOG_LEVEL", "ERROR");
// content of app.php
require 'config.php';
require 'vendor/autoload.php';
$container['logger'] = function($c) {
$logger = new \Monolog\Logger('logger');
error_log('log level ' . LOG_LEVEL); // prints 'log level ERROR'
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, $logger::LOG_LEVEL); // here I get the error 'Undefined class constant LOG_LEVEL'
//the normal syntax would be '$logger::ERROR' in this case and that works fine
$logger->pushHandler($fileHandler);
return $logger;
};
I need the 'LOG_LEVEL' constant to be used as 'ERROR' by the monolog class, not as 'LOG_LEVEL'. What am I doing wrong here, been searching an answer for hours now without any luck.
You are now doing $logger::LOG_LEVEL, which is taking the 'LOG_LEVEL' out of the class whichever $logger is (in this case a \Monolog\Logger). That doesn't have a static variable named LOG_LEVEL, thus you get the undefined.
You have just have 'LOG_LEVEL' defined, out of any class, so:
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, LOG_LEVEL);
Fancy solution:
You could do a static class and include that in your main page:
Class CONFIG {
public static $LOG_LEVEL = 'default Value';
}
// Then you can use this anywhere:
CONFIG::$LOG_LEVEL
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, CONFIG::$LOG_LEVEL);
The advantage of this is having only one file for configs, not scattered across all kinds of files, which'll become very annoying very fast.
Make a static class and include that...
class GLOBALCONF{
public static $VALUE= 'Something in here';
}
// Use it where you want
GLOBALCONF::$VALUE
You're making this more complicated than it needs to be. Monolog has a function to convert an error level as as string to its own internal value. Just change your code to this:
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, $logger::toMonologLevel(LOG_LEVEL));
You can also use Logger::getLevels() like the following:
$log_level = $logger->getLevels()[LOG_LEVEL];
$fileHandler = new ...StreamHandler(LOG_FILE, $log_level);
So, simply put, I feel like this code should work. Literally at this moment I am just trying to create a PHP class that takes in some information and runs a command against the database. I know the command works so it's not that, it something to do with the scope of my variables.
I'm new to PHP, and it's been interesting to handle.
<?php
require __DIR__ . '/../bin/composer/vendor/autoload.php';
$cx = new Customer();
$cx->WriteCxToDB();
class Customer {
public $database = new medoo([
'database_type'=>'mysql',
'database_name'=>'dbname',
'server'=>'localhost',
'username'=>'dbusername',
'password'=>'dbpassword',
'charset'=>'utf8'
]);
public function WriteCxToDB(){
global $database;
if($database->has("customer", [
"OR"=>[
"username"=>"cxusername",
"email"=>"email#gmail.com"
]
]))
{
echo "User already exists";
}else{
$database->insert("customer", [
"username"=>"username",
"keyword"=>"keyword",
"email"=>"email#gmail.com",
"phone"=>"444-444-4444",
"first_name"=>"First",
"last_name"=>"Last"
]);
echo "User added";
}
echo "Done";
}
}
?>
I am using composer and medoo to do this database entry. I know the database code works because I've ran it on it's own and it runs fine.
What I'm struggling with the seems to be the variable $database in the code. The function call works if I remove that variable from the mix. I feel like I'm just not understanding where I am supposed to declare the variable / how to reference it from within / outside the function. Thanks.
As suggested in the previous example you should be using something like this and pass a db connection into the class, extending a base class would allow reuse of the db connection:
private $database;
public function __construct($db_connection = null){
//do stuff or set db
$this->database = $this->db_connect;
}
OR make a method in the class to do it
private function db_connect(){
return new medoo([
// required
'database_type' => 'mysql',
'database_name' => 'name',
'server' => 'localhost',
'username' => 'your_username',
'password' => 'your_password',
'charset' => 'utf8',
]);
}
to check consider catching the errors. Using a unique or primary key on the DB would be a safer way of doing this otherwise you have to do validation and searching on the DB. Add the keys and check for duplicate errors.
if($database->error()){
//deal with return or pass to logging
}
The problem here is the use of global scope. Instead of:
global $database;
if($database->has("customer",
use
if($this->database->has("customer",
you could also consider instantiating $database in the constructor, i.e.
private $database;
public function __construct() {
$this->database = new medoo([args....
Before I ask my question I want to make sure a few things are clear:
1) I read the official PHP manual on OOP (intro, basics, inheritance, properties and so on) It seems im honestly not getting something. Im on it for hours now and fixed a few things there and then but new errors are popping up.
2) I read the error messages Missing argument 1 for Main::__construct() and Undefined variable: array. This means my $start variable is NULL.
3) I did do a few searches on stackoverflow either it is not fully related or very hard for me understand whats going on since the example code is so complex (Not the best thing for a starter).
My question: Why is the below code not working? I would really appreciate why it is failing and what I have to consider.
class Main {
protected $config;
protected $start;
function __construct($arr) {
if(!isset($this -> start)) {
if ($arr['secure']){
$this -> config = parse_ini_file('C:\xampp\htdocs\project\config.ini');
$this -> start = mysqli_connect($this -> config['host'], $this -> config['username'],
$this -> config['password'], $this -> config['database']);
}
else {
$this -> start = mysqli_connect($arr['host'], $arr['username'], $arr['password'], $arr['database']);
}
}
if($this -> start == false) {
return mysqli_connect_error();
}
}
}
$Main = new Main($array = ["host" => '', "username" => '', "password" => '', "database" => '',
"errors" => false, "secure" => true]);
class Test extends Main {
public function sample() {
$query = "SELECT id, user,
FROM users";
$result = mysqli_query($Main -> start , $query);
$row = mysqli_fetch_array($result);
echo $row['user'];
}
}
$x = new Test();
$x -> sample();
So here's what happens on run time:
$Main = new Main(...);
OK, you may get a connection there if those details are filled in, but there is an issue in determining whether you made a successful connection or not. See below for more info, but $Main is important to note for now.
$x = new Test();
Class Test extends your class Main.
Your class Test therefore inherits the class Main's constructor, which requires an argument. The argument isn't provided, so it generates a warning.
To account for this, make $arr optional if you're not always going to be passing it:
function __construct($arr = array())
Then check if it exists by using isset on the index:
if(isset($arr['secure'])) // ...
Fast forward, because you haven't provided $arr, you will not be able to successfully connect to your DB. According to mysqli::__construct(), which mysqli_connect is an alias of, it will try to return an object ("which represents the connection to a MySQL Server.").
Now, take a look at these lines:
$this -> start = mysqli_connect( ... )
// ...
if ($this -> start == false) {
You must check against the return's connect_error attribute (or mysqli_connect_error()) to verify if the connection worked or not.
Thanks to the comments below, you should ALSO check for a false assignment, as mysqli_connect has been known to generate a warning and return false too, even though it is not shown on the PHP docs.
Let's continue.
$x -> sample();
Test::sample uses mysqli_query which expects the database connection as it's first argument. You attempt this by passing $Main->start.
Unfortunately, $Main is not in your variable scope, and cannot be accessed from inside of that method. What you need to reference is $this->start.
In fact, if this is the only reason you instantiated $Main, then you don't need to at that point. Instead, pass the connection details array through to new Test() instead.
Here's two solutions:
Pass your DB connection details through to $x = new Test();
The instance will then connect to the DB as intended and will be able to run the query.
Separate class Test from class Main, and pass an instance of class Main through your class Test constructor.
Probably the better option. If your Test is meant to be for your query don't have it extend Main, create your Database connection object (new Main(..)) and then pass that through into new Test($Main). In your Test class, create a constructor which accepts the Main class as an argument:
public function __construct(Main $main)
{
$this->db = $main;
}
Allow access to your connection object by making $main's $start attribute public, or by creating a getter function (e.g. getConnection()), then use than in your query:
$result = mysqli_query($this->db->getConnection(), $query);
There's many, many ways you can approach your scenario, it's down to you.
The answers have addressed the initial problems, but I thought I might also offer a few implementation suggestions also.
You are working with two different instances.
// This is one instance
$Main = new Main([...]);
// This is a different instance.
$test = new Test();
Extending a class does not mean that it gets the values from the existing instances of the class. It only means that it gets the same methods (and default properties).
Therefore, your class Test gets the same constructor as Main, meaning you need to send in the array to Test to be able to use instantiate it. In your example, there is no reason to instantiate Main directly at all.
$result = mysqli_query($Main -> start , $query);
to
$result = mysqli_query($this -> start , $query);
That removes the syntax error at least. $this is an introspective variable, it always refers to the current scope of instances.
Check out the comments below
//I suggest to make this class abstract to make sure
//php doesn't let you to instantiate it directly as you did before
abstract class Main {
protected $config;
protected $start;
function __construct($arr) {
if(!isset($this -> start)) {
if ($arr['secure']){
$this -> config = parse_ini_file('C:\xampp\htdocs\project\config.ini');
$this -> start = mysqli_connect($this -> config['host'], $this -> config['username'],
$this -> config['password'], $this -> config['database']);
}
else {
$this -> start = mysqli_connect($arr['host'], $arr['username'], $arr['password'], $arr['database']);
}
}
if($this -> start == false) {
return mysqli_connect_error();
}
}
}
class Test extends Main {
public function sample() {
$query = "SELECT id, user,
FROM users";
//Here you should use $this instead of $Main as you can access protected
//properties of the parent from the child class.
$result = mysqli_query($this -> start , $query);
$row = mysqli_fetch_array($result);
echo $row['user'];
}
}
//Instantiate Test class instead of Main as it inherits Main anyway
//Your code also had a typo in the constructor argument.
$x = new Test(array("host" => '', "username" => '', "password" => '', "database" => '', "errors" => false, "secure" => true));
$x -> sample();
Please not that I didn't check the mysql part of your code - just an OOP structure
How can I pass a dynamic dependency from one registered container definition to another? In this case, a generic Database object wants to inherit from a generic Config object. The twist is config is not static, but loaded depending on a given environment variable.
Config pertinent methods
public function __construct()
{
$configFile = 'example.config.yml';
$yamlParser = new Parser();
$reader = new Config\Reader($yamlParser);
$configYaml = $reader->parse(file_get_contents($configFile));
$config = new Config\Environment(getenv('SITE'), $configYaml);
$this->config = $config;
}
public function getEnvironmentConfig()
{
return $this->config;
}
Registering config is as simple as
$container->register('config', 'Config');
Database is currently added to the container as follows:
$container
->register('database', 'Database')
->addArgument($config->getEnvironmentConfig('Database', 'db.username'))
->addArgument($config->getEnvironmentConfig('Database', 'db.password'))
;
But I want to do something like
$container
->register('database', 'Database')
->addArgument(new Reference('config')->getEnvironmentConfig('Database', 'db.username'))
->addArgument(new Reference('config')->getEnvironmentConfig('Database', 'db.password'))
;
The $config in-PHP variable makes migrating from a PHP-built config impossible. I want to define the services in yaml force the container to:
Instantiate Config
Parse the config yaml file and create an environment-specific version
Return this on a call to getEnvironmentConfig
Is this possible?
This was solved by using the Expression Language Component
So you can easily chain method calls, for example:
use Symfony\Component\ExpressionLanguage\Expression;
$container->register('database', 'Database')
->addArgument(new Expression('service("config").getEnvironmentConfig("Database", "db.username")'));
I'm just trying to master the art of using classes in PHP and have come across a concern.
For security reasons, I sometimes use two database connections in my application; one with read-only privileges and one with full read/write. Unfortunately, I wasn't really thinking about this when I started to build a few classes.
So, I have a database class, which is essentially a pointless PDO wrapper (pointless because PDO is a wrapper), but thought it'd be good practice to write one anyway and I may extend PDO later too. This is what I did:
<?php
class Database {
private $dbh;
public function __construct($accessLevel = NULL, $credentials = NULL) {
if (gettype($credentials) === 'array') {
$dsn = 'mysql:host='.$credentials['dbHost'].';dbname='.$credentials['dbName'];
$options = array (
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
if ($accessLevel == "public") {
$this->dbh = new PDO($dsn, $credentials['publicUser'], $credentials['publicPassword'], $options);
} else if ($accessLevel == "private") {
$this->dbh = new PDO($dsn, $credentials['privateUser'], $credentials['privatePassword'], $options);
}
}
}
// other database functions
}
?>
For a public connection (read/write), I simply used this:
$db = new Database('public', config['dbConfig']);
... or for a private connection (read-only), I used this:
$db = new Database('private', config['dbConfig']);
... and when I wanted to use the connection in another class, I simply injected the connection, like so:
$user = new User($db);
Works fine, but then I realised that I may need two connections inside that class, one for reading only and one for all. So I got a little confused, but had a go regardless. This is what I did:
Instead of calling the class with either of the connections, I called the class twice with both, like so:
$publicDB = new Database('public', $config['db']);
$privateDB = new Database('private', $config['db']);
and then injected both of those to my other class:
$user = new User($publicDB, $privateDB);
Inside this User class, this how I used the two connections:
<?php
class User {
private $dbh;
private $dbhp;
public function __construct($publicDatabase = NULL, $privateDatabase = NULL) {
$this->dbh = $publicDatabase;
$this->dbhp = $privateDatabase;
}
public function doSomething() {
$this->dbh->query("INSERT INTO......");
$this->dbh->execute();
}
public function doSomethingSafely() {
$this->dbhp->query("SELECT * FROM......");
$results = $this->dbhp->resultset();
}
}
?>
Right, this actually works fine but I'm worried it's not the acceptable method or it may cause problems later down the road. I have a few questions:
Is using two connections with different privileges still considered good practice? Even though I'm properly escaping and validating my data and binding the values using PDO?
If yes for above, is there a better way to manage the two connections for using in many classes or is what I have done acceptable?