How to make class structure like PDO or ORM
$query = DB::table('users')->select('name');
$users = $query->addSelect('age')->get();
OR
$stmt = $pdo->prepare($sql);
$stmt->bindvalue(':u',intval($_SESSION['userId']),PDO::PARAM_INT);
$stmt->execute();
What is returned in to $query or $stmt?
how to design class structure like them?
Thank you
EDIT
$query = DB::table('users')->select('name');
meaning :
function select(){
//
return $this;
}
what's returned in to $query for this structure :
$query->addSelect('age')->get();
PDO is done by returning a new class (PDOStatement) with its own methods (read more about it), but it would be the same as:
<?php
class ClassOne
{
private $connection;
public function __construct($database_stuff)
{
$this->connection = $database_stuff;
}
public function prepare($sql)
{
// Code that does something with the $sql
// Then return a new class
return new ClassTwo($this);
}
}
class ClassTwo
{
private $ClassOne;
public function __construct(ClassOne $Class)
{
$this->ClassOne = $Class;
}
public function execute()
{
// Code that does something with ClassOne
}
}
# Start that initial class
$Class = new ClassOne('database:type;host=example;etc=yadayada');
# Do class one method
$query = $Class->prepare("SELECT * FROM fake_table");
# $query is now ClassTwo, so you do method from ClassTwo
$query->execute();
The ability to chain methods together is achieved because the current method returns the object back in the form of $this:
<?php
class DBClass
{
protected $connection,
$value;
public function __construct($connection)
{
$this->connection = $connection;
}
public function prepare($value)
{
$this->value = $value;
# Return the object
return $this;
}
public function execute()
{
echo $this->value;
# Return the object
return $this;
}
}
$con = new DBClass("login creds");
$con->prepare("update stuff if stuff = 'things'")->execute();
?>
Related
i have test:
class ContacsBLOTest extends TestCase
{
public function testsearch()
{
$Ctrl= new ContactsBLO;
$data=['id'=>1,'name'=>'The Manh','phone'=>'123456566','address'=>'180 cao lo','note'=>''];
$data=[(object)$data];
$mock_data=\Mockery::mock('DB');
$mock_data->shouldReceive('all')->andReturn($data);
$mock_ctrl= new ContactsBLO;
$mock_ctrl->select=$mock_data;
$result=$mock_ctrl->search('manh');
$this->assertNotNull($result);
}
and this is ContacsBLO class:
class ContactsBLO
{
public $db,$not_allow,$Validation;
public function __construct(){
$this->db=new DB;
$this->not_allow=['"','\'','%'];
$this->Validation = new ContactValidation;
}
public function search($request=null){
$length=strlen($request);
for ($i=0;$i<$length;$i++) {
$forbidden=$this->not_allow;
if(in_array($request[$i],$forbidden)){
return (['messenger'=>'We are not allow special character in your request','old_input'=>$request]);
}
else{
return $data=$this->db->select('*',$request);
}
}
}
}
DB::class(i define connect to data base and define select method:
class DB
{
public $obj = null;
public $table = 'contacts';
public function __construct(){
$dsn="mysql:host=".HOST."; dbname=".DB_NAME;
$this->obj = new \PDO($dsn, DB_USER, DB_PASS);
$this->obj->query("set names 'utf8' ");
}
public function select($row=null,$query=null) {
$sql='SELECT '.$row.' FROM '.$this->table.' '.$query;
$data = $this->obj->prepare($sql);
$data->execute();
return $data->fetchAll(\PDO::FETCH_CLASS);
}
}
But when i run xdebug and run this test, $forbidden is null,it mean mock method return real data, not mock data. i dont know why.
Anyone can help me! Please!
You never inserted your mock into your class, besides when using the new keyword to create a class instance it's difficult to mock. Your only chance in such cases is to use class alias.
To avoid all this you can pass in the database instance through the ContactsBLO constructor.
class ContacsBLOTest extends TestCase
{
public function testSearch()
{
$data = ['id'=>1,'name'=>'The Manh','phone'=>'123456566','address'=>'180 cao lo','note'=>''];
$data = json_decode(json_encode($data));
$mock_contact = \Mockery::mock(DB::class);
$mock_contact->shouldReceive('select')->andReturn($data);
$Ctrl = new ContactsBLO($mockDB);
$result = $Ctrl->search('manh');
$this->assertNotNull($result);
}
}
class ContactsBLO
{
public $db;
public $not_allow;
public $Validation;
public function __construct(DB $db) {
$this->db = $db;
$this->not_allow = ['"','\'','%'];
$this->Validation = new ContactValidation;
}
public function search($request=null){
$length=strlen($request);
for ($i=0;$i<$length;$i++) {
$forbidden = $this->not_allow;
if(in_array($request[$i],$forbidden)){
return (['messenger'=>'We are not allow special character in your request','old_input'=>$request]);
}
else{
return $data = $this->db->select('*',$request);
}
}
}
}
I tested it with this code and it worked fine. Please check if the DB class is imported at the top of your test file. You also have to append Test to all test file names and classes (see above).
I was change it to:
$mock_data=\Mockery::mock('DB');
$mock_data->shouldReceive('select')->andReturn($data);
$mock_ctrl= new ContactsBLO;
$mock_ctrl->db=$mock_data;
$result=$mock_ctrl->search();
And it is working for me, thank for all help
I'm having an issue with the PHP singleton pattern, specifically with regards to implementing a mysqli wrapper.
class DbHandler
{
private $mysqli;
private $query;
private $results = array();
private $numRows = 0;
public static $instance;
public static function getInstance() {
if (!isset(self::$instance)) {
self::$instance = new DbHandler;
}
return self::$instance;
}
public function __construct() {
$this->mysqli = new mysqli("127.0.0.1", "root", "", "improved_portal");
if ($this->mysqli->connect_error) {
die($this->mysqli->connect_error);
}
}
public function query($statement) {
if ($this->query = $this->mysqli->query($statement)) {
foreach ($this->query as $value) {
$this->results[] = $value;
}
$this->numRows = $this->query->num_rows;
return $this;
}
}
public function getResults() {
return $this->results;
}
public function getNumRows() {
return $this->numRows;
}
}
When I go to utilise the class in other objects, I seem to have an issue with the results. Instead of creating a new object each time with unique $results, it seems I am creating copies of the initial object. For example...
$object1 = DbHandler::getInstance();
$object1->query("SELECT * FROM table_a")->getResults();
$object2 = DbHandler::getInstance();
$object2->query("SELECT * FROM table_b")->getResults();
$object2 contains results from both queries, which is obviously not what I expect. The query function clearly loops through the results of the second query, and appends these to the $results property of the first object. How should I call a new instance of the DbHandler class so that each object contains unique properties?
First of all - this is not singleton pattern. As your __construct is public I can do this:
$conn1 = new DbHandler();
$conn2 = new DbHandler();
$conn3 = new DbHandler();
To prevent this - __construct must be protected/private.
Second - everytime you call query() from the same object, this function add results to results property. And this results property is used for all queries without clearing. Surely, it will hold all previous values. Function should be rewritten like:
public function query($statement) {
// clear result from previous function call
$this->results = array();
if ($this->query = $this->mysqli->query($statement)) {
foreach ($this->query as $value) {
$this->results[] = $value;
}
$this->numRows = $this->query->num_rows;
return $this;
}
}
User::updatemain($set, $where);
This gives Fatal error: Using $this when not in object context
My user class extends from Dbase class and here is user class function:
public static function activate($set, $where) {
return $this->updatemain($set, $where);
here is dbase class (some part of):
private function query($sql = null, $params = null) {
if (!empty($sql)) {
$this->_last_statement = $sql;
if ($this->_db_object == null) {
$this->connect();
}
try {
$statement = $this->_db_object->prepare($sql, $this->_driver_options);
$params = Helper::makeArray($params);
$x = 1;
if (count($params)) {
foreach ($params as $param) {
$statement->bindValue($x, $param);
$x++;
}
}
if (!$statement->execute() || $statement->errorCode() != '0000') {
$error = $statement->errorInfo();
throw new PDOException("Database error {$error[0]} : {$error[2]}, driver error code is {$error[1]}");
exit;
}
//echo $sql;
return $statement;
} catch (PDOException $e) {
echo $this->formatException($e);
exit;
}
}
}
public function updatemain($set, $where) {
return $this->query($sql, $params);
}
this is part of Dbase class
You are calling static method so there is no $this in that context.
If you want to call other static method from given class then use self::method() but if you want to call non-static method you've got problem. First you have to create new object.
When you use static methods, you can't use $this inside
public static function activate($set, $where) {
return self::updatemain($set, $where);
}
Or you have to use singelton design
EDIT
Best solution - rewrite your class to one point access to DB object. And create Model classes to DB access. See my example code below:
core AppCore
<?php
class AppCore
{
public static $config = array();
public static $ormInit = false;
public static function init($config)
{
self::$config = array_merge(self::$config, $config);
}
public static function db($table)
{
// ORM - see http://idiorm.readthedocs.org/en/latest
if (!self::$ormInit) {
ORM::configure(self::$config['db']['connection']);
ORM::configure('username', self::$config['db']['username']);
ORM::configure('password', self::$config['db']['password']);
self::$ormInit = true;
}
return ORM::for_table($table);
}
}
User model
<?php
class UserModel
{
const TABLE = 'user';
public static function findById($u_id)
{
$result = AppCore::db(self::TABLE)->where('u_id', $u_id)->find_one();
return $result ? $result->as_array() : null;
}
}
AppCore init section
AppCore::init(array(
'db' => array(
'connection' => "mysql:dbname={$db};host={$host}",
'username' => $user,
'password' => $pass
),
));
i hope it help make your code better
Singleton Class:
<?php
class db_singleton
{
const ORACLE_HOST = "SOMEIP";
const ORACLE_USER = "validuser";
const ORACLE_PASS = "validpass";
const ORACLE_DB = "SOMEIP/DBNAME";
private static $instance; // stores the oci_* instance
private function __construct() { } // block directly instantiating
private function __clone() { } // block cloning of the object
public static function call()
{
// create the instance if it does not exist
if(!isset(self::$instance))
{
// the ORACLE_* constants should be set to or
// replaced with your db connection details
self::$instance = oci_connect(self::ORACLE_USER, self::ORACLE_PASS, self::ORACLE_DB);
if(self::$instance->connect_error)
{
throw new Exception('Oracle connection failed: ' . self::$instance->connect_error);
}
}
// return the instance
return self::$instance;
}
public function __destruct() {
oci_close($instance);
}
public function queryresult($query)
{
$result_set_array =array();
$this->stmt = oci_parse($this->con, $query);
oci_execute($this->stmt);
while($row=oci_fetch_array($this->stmt,OCI_ASSOC+OCI_RETURN_NULLS))
{
$result_set_array[] = $row;
}
oci_free_statement($this->stmt);
return $result_set_array;
}
}
?>
When I try using singleton class with below code, it works perfect and fetch results.
$conn = db_singleton::call();
$stid = oci_parse($conn, 'SELECT * FROM somevalid_table');
oci_execute($stid);
while($result=oci_fetch_array($stid,OCI_ASSOC+OCI_RETURN_NULLS))
{
$result_set_array[] = $result;
}
Now, when I try extending my class using model, it throws exception
class Myclass Extends db_singleton{
public function someModel()
{
$result = parent::queryresult(" select * from somevalid_table");
return $result;
}
}
Exception:
Fatal error: Call to private db_singleton::__construct() from context 'someController'
I know that class cannot be instantiated having private constructor. __construct() functions are always called when an object is instantiated, so trying to do something like $x = new MyObject() will cause a fatal error with a private construction function.
I am using Singleton classes to prevent direct instantiation of an object. How can I overcome issue ? What would be the best solution ?
Thanks.
$x = new MyObject() will never work if your constructor is private in that class because __construct() is the first method that is invoked on object creation.
Create a public method
/**
* Singleton class
*
*/
final class UserFactory
{
/**
* Call this method to get singleton
*
* #return UserFactory
*/
public static function Instance()
{
static $inst = null;
if ($inst === null) {
$inst = new UserFactory();
}
return $inst;
}
/**
* Private ctor so nobody else can instance it
*
*/
private function __construct()
{
}
}
To use:
$fact = UserFactory::Instance();
$fact2 = UserFactory::Instance();
$fact == $fact2;
But:
$fact = new UserFactory()
PHP Class Script:
class db_singleton
{
const ORACLE_USER = "validuser";
const ORACLE_PASS = "validpass";
const ORACLE_DB = "SOMEIP/DBNAME";
private static $instance = null; // stores the oci_* instance
// private constructor
private function __construct() { } // block directly instantiating
private function __clone() { trigger_error('Clone is not allowed.', E_USER_ERROR); } // block cloning of the object
public static function getInstance()
{
// create the instance if it does not exist
if(!isset(self::$instance))
{
// the ORACLE_* constants should be set to or
// replaced with your db connection details
self::$instance = oci_connect(self::ORACLE_USER, self::ORACLE_PASS, self::ORACLE_DB);
if(self::$instance->connect_error)
{
throw new Exception('Oracle connection failed: ' . self::$instance->connect_error);
}
}
// return the instance
return self::$instance;
}
public static function queryresult($query)
{
$result_set_array =array();
$stmt = oci_parse(db_singleton::getInstance(), $query);
oci_execute($stmt);
while($row=oci_fetch_array($stmt,OCI_ASSOC+OCI_RETURN_NULLS))
{
$result_set_array[] = $row;
}
oci_free_statement($stmt);
return $result_set_array;
}
Now to prevent Fatal error: Call to private db_singleton::__construct(), I have added an empty constructor to my child class which is model class in my case. This will override the parent class constructor which is private.
class Myclass Extends db_singleton{
public function __construct() {}
public function someModel(){
$result = parent::queryresult(" select * from somevalid_table");
return $result;
}
}
Hope it helps someone.
Thanks.
We are trying to understand the best way to use mysqli/other classes in multiple custom classes so that we don't instantiate a new object every time.
Is the code below the best/correct way of doing this?
The functions are only examples.
Thank you :)
<?php
class Base {
public function __get($name) {
if($name == 'db'){
$db = new mysqli('**', '*s', '*', '*');
$this->db = $db;
return $db;
}
if($name == 'blowfish'){
$blowfish = new PasswordHash(8, true);
$this->blowfish = $blowfish;
return $blowfish;
}
}
}
class A extends Base {
public function validate($username, $password) {
$query = $this->db->query("SELECT * FROM users");
return $query->num_rows;
}
public function password($password)
{
return $this->blowfish->HashPassword($password);
}
}
class PasswordHash {
public function __construct($iteration_count_log2, $portable_hashes) { }
public function HashPassword($password) {
return $password;
}
}
$a = new A;
echo $a->validate('test','test'); // returns number rows count as expected
echo $a->password('password123'); // returns password123 as expected
?>
You are/should probably be more interested in Dependency Injection instead of creating a tight coupling of Base|A and the MySQL database.