New to OOP PHP, guidance request, data sharing between classes design - php

Writing my first application in PHP that makes use of classes and objects.
I have a DB class which takes a string to select the appropriate config because there are multiple databases. I started out with a login class but scratched that idea in exchange for a user class so I can do user->isLoggedIn stuff. The user class uses MySQL which stores user and login information, as well as credentials for the second database.
$mysql = new db( 'mysql' );
$user = new user( $mysql );
if( !( $user->isLoggedIn() === true ) )
{
goToPage( 'login.php' );
exit();
}
The second database is Sybase and stores account information. I need the credentials from the user class to get lists and accounts information from here. I think an account class should be next but not sure of the best way to do it. Something like this maybe..
$sybase = new db( 'sybase' );
$account = new account( $sybase );
$account_list = $account->getList( $user->info );
$user->info being an array i guess of credentials and info needed for the account table, or is there a better way to go about this?
Edited to include db class example
config.db.php
$config_array = array();
$config_array[ 'mysql' ][ 'dsn' ] = "mysql:host=localhost;dbname=********;charset=UTF-8";
$config_array[ 'mysql' ][ 'user' ] = "********";
$config_array[ 'mysql' ][ 'pass' ] = "**************";
$config_array[ 'sybase' ][ 'dsn' ] = "odbc:DRIVER={Adaptive Server Anywhere 8.0};***************";
$config_array[ 'sybase' ][ 'user' ] = "**********";
$config_array[ 'sybase' ][ 'pass' ] = "*********";
class.db.php
public function __construct( $type )
{
require 'config.db.php';
$this->type = $type;
foreach( $config_array[ $this->type ] AS $key => $value )
{
$this->$key = $value;
}
try
{
$this->connection = new PDO( $this->dsn, $this->user, $this->pass, $this->options );
}
catch( PDOException $ex )
{
log_action( "db->" . $this->type, $ex->getCode() . ": " . $ex->getMessage() );
$this->error = true;
}
return $this->connection;
}
Does something like the following make sense?
class User()
{
public function getAccountList()
{
$this->Account = new Account( new Database( 'sybase' ) );
return $this->Account->list( $this->userid );
}
}
Also, Accounts have different sections (i.e. History & Notes, Financial Transactions, Documents) that will be different 'tabs' on the page. Should each one of those be a class too?

First up, as a secondary answer on the other answer by Dimitry:
The things you'll gain by having a Singleton pale to the things you lose. You get a global object, which every part of your program can read and modify, but you lost testability, readability, and maintainability.
Because the Singleton object is in fact global, you cannot accurately isolate the single units in your application (which is crucial for Unit Testing), because your function is dependant on other components, which are "magically" inserted into it.
You lose readability because method() may actually need other things to work (for instance, the user object needs a db instance, this makes new user($db) much more readable than new user(), because even without looking at the source code, I can tell what the method/object needs to work.
You lose maintainability because of the reason stated above as well. It's harder to tell which components are inserted via the Singleton than it is to see it in the function's "contract", for that reason, it'll be harder for future you and/or any other developer to read and refactor your code.
As a side note, it's considered good naming convention to name Class names with its first letter upper cased, I'll be using this convention from now on.
Let's start from the top. You have 2 possible states for your Db object, MysqlDb, and SybaseDb. This calls for polymorphism. You have an abstract class Db, and two concrete classes MysqlDb and SybaseDb inheriting from it. The instantiation of the correct Db object is the responsibility of a factory
class DbFactory {
private $db;
/**
* Create a new Db object base on Type, and pass parameters to it.
*
* #param string $type Type of database, Mysql or Sybase.
* #param string $dsn The DSN string corresponding to the type.
* #param string $user User credential
* #param string $pass Password credential
*
* #return Db
*/
public function create($type, $dsn, $user, $pass) {
if (!is_a($this->db, "Db")) {
$type = $type . "Db";
$this->db = new $type($dsn, $user, $pass);
}
return $this->db;
}
}
abstract class Db {
/**
* #param $dsn
* #param $user
* #param $pass
*/
public function __construct($dsn, $user, $pass) {
$this->db = new PDO("$dsn", $user, $pass);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
}
class MysqlDb extends Db {
/*
* More specific, Mysql only implementation goes here
*/
}
class SybaseDb extends Db {
/*
* More specific, Sybase only implementation goes here
*/
}
Now you should ask yourself, who is responsible for getting the list of accounts? Surely they shouldn't fetch themselves (just as much as it isn't the user's responsibility to fetch its own data from the database). It's the User's responsibility to fetch these accounts, using the SybaseDb connection.
In fact, the User needs the following to work:
Sybase database connection - to fetch Account list. We'll pass the DbFactory instance to it, so that we can easily get the instance if it were already instantiated, and instantiate it if not.
State information (logged in status, user ID, user name, etc).
The User isn't responsible to set this data, it needs it in order to work.
So the User constructor should look like this (assuming "ID" and "name" fields):
User::__construct($id, $name, DbFactory $factory);
The User will have a field $accounts, which would hold an array of Account objects. This array would be populated with a User::getAccounts() method.

I'd make Account the member of User class. "User has an Account", right?
Also, speaking about classes, make sure that your two DBs are singletones, and get them via getInstance(), not __construct().

Related

How to get last inserted inserted row id from PDO

I am following mvc structure in PHP and I want to retrieve last inserted row ID.
I have created following sql code:
$sql = "INSERT INTO song (artist, track, link) VALUES (:artist, :track, :link)";
$query = $this->db->prepare($sql);
$query->execute(array(':artist' => $artist, ':track' => $track, ':link' => $link));
echo $query->lastInsertId(); // To retrieve last inserted row ID.
but unfortunately I ma getting this error: Fatal error: Call to undefined method PDOStatement::lastInsertId()
I have also tried this stack links but not worked for me so I will happy if you help me for retrieve ID.
I am also sharing my controller.php file here.
/**
* This is the "base controller class". All other "real" controllers extend this class.
*/
class Controller{
/**
* #var null Database Connection
*/
public $db = null;
/**
* Whenever a controller is created, open a database connection too. The idea behind is to have ONE connection
* that can be used by multiple models (there are frameworks that open one connection per model).
*/
function __construct(){
$this->openDatabaseConnection();
}
/**
* Open the database connection with the credentials from application/config/config.php
*/
private function openDatabaseConnection(){
// set the (optional) options of the PDO connection. in this case, we set the fetch mode to
// "objects", which means all results will be objects, like this: $result->user_name !
// For example, fetch mode FETCH_ASSOC would return results like this: $result["user_name] !
// #see http://www.php.net/manual/en/pdostatement.fetch.php
$options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING);
// generate a database connection, using the PDO connector
// #see http://net.tutsplus.com/tutorials/php/why-you-should-be-using-phps-pdo-for-database-access/
$this->db = new PDO(DB_TYPE . ':host=' . DB_HOST . ';dbname=' . DB_NAME, DB_USER, DB_PASS, $options);
}
/**
* Load the model with the given name.
* loadModel("SongModel") would include models/songmodel.php and create the object in the controller, like this:
* $songs_model = $this->loadModel('SongsModel');
* Note that the model class name is written in "CamelCase", the model's filename is the same in lowercase letters
* #param string $model_name The name of the model
* #return object model
*/
public function loadModel($model_name){
require 'application/models/' . strtolower($model_name) . '.php';
// return new model (and pass the database connection to the model)
return new $model_name($this->db);
}
}
You're almost there.
If you look at the manual page for lastInsertId, it's called on the database handle - you're currently calling it on the statement.
You just need to call:
$this->db->lastInsertId();
you can try the following -
$query = "INSERT INTO song (artist, track, link) VALUES (:artist, :track, :link)";
$stmt = $pdo->prepare($query);
$params = array(
"artist" => $artist,
"track" => $track,
"link" => $link,
);
$data = $stmt->execute($params);
$insert_id = $pdo->lastInsertId();

PHP database class - Managing two connections with different privileges

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?

How to all actions under a controller as resource in Zend Acl

I am trying to follow a tutorial for Zend Auth and Zend Acl using 1.11 framework Link here!
I have setup the authentication successfully and am able to use the authentication for the controller::action pairs given in the Acl.php page. Firstly I would like to test two additional parameter on the users table that whether the user account is activated and if the user is banned by administrator before allowing access to the site. How do I implement that in this code.
Secondly I would like to know how to include all actions under one controller to a User authorization level. i.e. I have a masters controller which has numerous actions under it for various tables. Could you tell me how to restrict access to Masters controller all actions to admin role only. Without adding resources and allow resources for each action in Acl.php. Also please tell me if this logic can be extended to allow access over entire modules instead of just the controllers(by one add resource and allow resource)? If yes how?
Firstly I would like to test two additional parameter on the users
table that whether the user account is activated and if the user is
banned by administrator before allowing access to the site.
The tutorial code uses a vanilla version of Zend_Auth_Adapter_DbTable which uses a specific api for authentication. To make Zend_Auth work how you want it to is not very difficult but will require some thought as you'll need to implement Zend_Auth_Adapter_Interface. Sounds worse then it is, you only have to implement the authenticate() method. Here is an example of an auth adapter that can be used in place of Zend_Auth_Adapter_DbTable:
<?php
//some code truncated for length and relevance
class My_Auth_Adapter implements Zend_Auth_Adapter_Interface
{
protected $identity = null;
protected $credential = null;
protected $usersMapper = null;
public function __construct($username, $password, My_Model_Mapper_Abstract $userMapper = null)
{
if (!is_null($userMapper)) {
$this->setMapper($userMapper);
} else {
$this->usersMapper = new Users_Model_Mapper_User();
}
$this->setIdentity($username);
$this->setCredential($password);
}
/**
* #return \Zend_Auth_Result
*/
public function authenticate()
{
// Fetch user information according to username
$user = $this->getUserObject();
if (is_null($user)) {
return new Zend_Auth_Result(
Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND,
$this->getIdentity(),
array('Invalid username')
);
}
// check whether or not the hash matches
$check = Password::comparePassword($this->getCredential(), $user->password);
if (!$check) {
return new Zend_Auth_Result(
Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID,
$this->getIdentity(),
array('Incorrect password')
);
}
// Success!
return new Zend_Auth_Result(
Zend_Auth_Result::SUCCESS,
$this->getIdentity(),
array()
);
}
// public function setIdentity($userName)
// public function setCredential($password)
// public function setMapper($mapper)
/**
* #return object
*/
private function getUserObject()
{
return $this->getMapper()->findOneByColumn('username', $this->getIdentity());
}
/**
* #return object
*/
public function getUser()
{
$object = $this->getUserObject();
$array = array(
'id' => $object->id,
'username' => $object->username,
'role' => $object->getRoleId()
);
return (object) $array;
}
// public function getIdentity()
// public function getCredential()
// public function getMapper()
}
You can modify the auth adapter to do pretty much anything you need.
As far as your access list is concerned, the thing to remember is that you resources are defined by a string. In the case of this tutorial a resource is defined as:
$this->add(new Zend_Acl_Resource('error::error'));
where the string on the left side of the colon represents the controller and the string on the right side of the colon represents the action. it's this line in the acl plugin that tell's us what the resources are:
if(!$acl->isAllowed($user->role, $request->getControllerName() . '::' . $request->getActionName()))
you can change this definition of what your resources represent to anything that works for you.
It's very difficult to provide hard and fast rules on how to implement an ACL because it seems that every project needs something different.
Look around the web and you'll find several different implementations of a Zend Framework ACL, some of them can be very complex.
Here is one that might provide some more insight. http://codeutopia.net/blog/2009/02/06/zend_acl-part-1-misconceptions-and-simple-acls/
good luck

Unable to access variable from included file

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'].

make db connection persistent throught zend framework

I'm using zend framework. currently everytime I need to use the db I go ahead and connect to the DB:
function connect()
{
$connParams = array(
"host" => $host,
"port" => $port,
"username" => $username,
"password" => $password,
"dbname" => $dbname
);
$db = new Zend_Db_Adapter_Pdo_Mysql($connParams);
return $db
}
so I would just call the connect() function everytime I need to use the db
My question is...suppose I want to reuse $db everywhere in my site and only connect once in the very initial stage of the site load and then close the connection right before the site gets sent to the user, what would be the best practice to accomplish this?
Which file in Zend should I save $db in, what method should I use to save it (global variable?), and which file should I do the connection closing in?
If you are using the default project structure (with the application, library, tests and public folders), you should use set up the db parameters in application/configs/application.ini
Example application.ini:
[production]
resources.db.adapter = "pdo_mysql"
resources.db.params.host = "localhost"
resources.db.params.username = "testuser"
resources.db.params.dbname = "testdb"
resources.db.params.password = "testpasswd"
resources.db.isDefaultTableAdapter = true
In this way zend framework will automatically open and close the connections to the database and you can use the Zend_Db_Table or Zend_Db_Table_Abstract classes to query your tables easily, for example, to retrieve the student's data for a given SSN you could write a model (application/models/Student.php) that looks something like this:
<?php
class Model_Student extends Zend_Db_Table_Abstract
{
protected $_name = "student";
public function fetchRowsBySSN($ssn)
{
$select = $this->select();
$select->where('ssn = ?', $ssn);
return $this->fetchRow($select)->toArray();
}
}
As you can see there's no need to open/close the connection and you get an associative array with the fields and values of the student record.
Your best best may be to move all of your db connection code into a separate class in which you can set a static $db var.
protected static $_db;
public static function connect()
{
if (self::$_db == null) {
$config = Zend_Config_Xml(); // whatever you'd use
self::$_db = Zend_Db::factory($config->database);
self::$_db->setFetchMode(Zend_Db::FETCH_OBJ);
self::$_db->query('SET NAMES UTF8');
Zend_Db_Table::setDefaultAdapter(self::$_db); // optional
}
return self::$_db;
}
public static function close()
{
if (self::$_db != null) {
self::$_db->closeConnection();
}
}
According to Zend:
Normally it is not necessary to close a database connection. PHP automatically cleans up all resources and the end of a request. Database extensions are designed to close the connection as the reference to the resource object is cleaned up.
However, if you have a long-duration PHP script that initiates many database connections, you might need to close the connection, to avoid exhausting the capacity of your RDBMS server. You can use the Adapter's closeConnection() method to explicitly close the underlying database connection.

Categories