Hi I'd like to ask if my implementation of a model class is at least correct to OOP style.
I declare it as an abstract so that it can only be used on extending the
class
sample code :
<?php
/**
* class model handles dbconfig and some common query transaction
* i declare it as an abstract so that it can only be used on extending the
*class
*/
abstract class Model
{
//db config
protected $sHost = "localhost";
protected $sUser = "root";
protected $sPass = " ";
protected $sDb = "test";
protected $sEngine = "MySQL";
protected $conn ;
//constructor
public function __construct()
{
$this->conn = new mysqli($this->sHost, $this->sUser, $this->sPass , $this->sDb);
}
protected function db_query_list($sSql){
if ($resultset = $this->conn->query($sSql)) {
if ($resultset->num_rows > 0) {
$data = array();
while( $row = $resultset->fetch_assoc() ) {
$data[] = array_change_key_case($row);
}
}else {
$data = false;
}
} else {
$data = false;
}
$resultset->close();
return $data;
}
protected function execute_query($SQL) {
$run = $this->conn->query($this->sEngine);
return $run;
}
}
Then on implementation i extend model in CustomerModel
<?php
require "Model.php";
class CustomerModel extends Model
{
public function __construct()
{
parent::__construct();
}
public function getAllCustomer()
{
$sSql = "SELECT *
FROM t_classification_header
";
return $this->db_query_list($sSql);
}
}
Notice that i use parent::__construct();.
I'm new in OOP any help would be my pleasure
Any comments and suggestions are welcome .
Thank you
1) Create separated class, maybe even singleton, for actually interaction with DB and pass it as argument of model constructor. Why? If you work with 5 customers than your code will create 5 connections to DB. (Dependency Injection / composition)
2) Don't do return $this->db_query_list($sSql);. Wrap results into some CustomersList and/or wrap each row into Customer.
3) I would create class Customers($DB) with methods all(), byId($id), etc. instead of CustomerModel. Seriously, we're not a photo agency and do not work with models. Yes, the class is a data model, but why bother with this code? For us, this is a concrete Customer, with specific data and behavior.
Related
I'm building a small and simple PHP content management system and have chosen to adopt an MVC design pattern.
I'm struggling to grasp how my models should work in conjunction with the database.
I'd like to separate the database queries themselves, so that if we choose to change our database engine in the future, it is easy to do so.
As a basic concept, would the below proposed solution work, is there a better way of doing things what are the pitfalls with such an approach?
First, I'd have a database class to handle all MySQL specific pieces of code:
class Database
{
protected $table_name;
protected $primary_key;
private $db;
public function __construct()
{
$this->db = DatabaseFactory::getFactory()->getConnection();
}
public function query($sql)
{
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll();
}
public function loadSingle($id)
{
$sql = "SELECT * FROM $this->table_name WHERE $this->primary_key = $id";
return $this->query($sql);
}
public function loadAll()
{
$sql = "SELECT * FROM $this->table_name";
return $this->query($sql);
}
}
Second, I'd have a model, in this case to hold all my menu items:
class MenuItemModel
{
public $menu_name;
public $menu_url;
private $data;
public function __construct($data)
{
$this->data = $data;
$this->menu_name = $data['menu_name'];
$this->menu_url = $data['menu_url'];
}
}
Finally, I'd have a 'factory' to pull the two together:
class MenuItemModelFactory extends Database
{
public function __construct() {
$this->table_name = 'menus';
$this->primary_key = 'menu_id';
parent::__construct();
}
public function loadById($id)
{
$data = parent::loadSingle($this->table_name, $this->primary_key, $id);
return new MenuItemModel($data);
}
public function loadAll()
{
$list = array();
$data = parent::loadAll();
foreach ($data as $row) {
$list[] = new MenuItemModel($row);
}
return $list;
}
}
Your solution will work of course, but there are some flaws.
Class Database uses inside it's constructor class DatabaseFactory - it is not good. DatabaseFactory must create Database object by itself. However it okay here, because if we will look at class Database, we will see that is not a database, it is some kind of QueryObject pattern (see link for more details). So we can solve the problem here by just renaming class Database to a more suitable name.
Class MenuItemModelFactory is extending class Database - it is not good. Because we decided already, that Database is just a query object. So it must hold only methods for general querying database. And here you mixing knowledge of creating model with general database querying. Don't use inheritance. Just use instance of Database (query object) inside MenuItemModelFactory to query database. So now, you can change only instance of "Database", if you will decide to migrate to another database and will change SQL syntax. And class MenuItemModelFactory won't change because of migrating to a new relational database.
MenuItemModelFactory is not suitable naming, because factory purpose in DDD (domain-driven design) is to hide complexity of creating entities or aggregates, when they need many parameters or other objects. But here you are not hiding complexity of creating object. You don't even "creating" object, you are "loading" object from some collection.
So if we take into account all the shortcomings and correct them, we will come to this design:
class Query
{
protected $table_name;
protected $primary_key;
private $db;
public function __construct()
{
$this->db = DatabaseFactory::getFactory()->getConnection();
}
public function query($sql)
{
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll();
}
public function loadSingle($id)
{
$sql = "SELECT * FROM $this->table_name WHERE $this->primary_key = $id";
return $this->query($sql);
}
public function loadAll()
{
$sql = "SELECT * FROM $this->table_name";
return $this->query($sql);
}
}
class MenuItemModel
{
public $menu_name;
public $menu_url;
private $data;
public function __construct($data)
{
$this->data = $data;
$this->menu_name = $data['menu_name'];
$this->menu_url = $data['menu_url'];
}
}
class MenuItemModelDataMapper
{
public function __construct() {
$this->table_name = 'menus';
$this->primary_key = 'menu_id';
$this->query = new Query();
}
public function loadById($id)
{
$data = $this->query->loadSingle($this->table_name, $this->primary_key, $id);
return new MenuItemModel($data);
}
public function loadAll()
{
$list = array();
$data = $this->query->loadAll();
foreach ($data as $row) {
$list[] = new MenuItemModel($row);
}
return $list;
}
}
Also consider reading this:
DataMapper pattern
Repository pattern
DDD
I am trying to make a base class ... tiny framework if you will just for practice
So I start with example of child class because it has less code !!
class User extends Base {
public $id ;
public $username ;
public $email ;
public $password ;
function __construct(){
$this->table_name = 'users';
$this->set_cols(get_class_vars('User'));
}
}
$u = new User;
$u->username = 'jason';
$u->email = 'j#gmail.com';
$u->insert();
Here is my Base class
class Base {
protected $table_name ;
protected $table_columns ;
protected function set_cols($cols){
unset($cols['table_name']);
unset($cols['table_columns']);
$this->table_columns = array_keys($cols);
}
public function insert(){
$colums = $values = array();
foreach($this->table_columns as $col )
{
if(!$this->$col) continue ;
$values[] = $this->$col ;
$colums[] = $col ;
}
$values = implode(',' , $values);
$colums = implode(',' , $colums);
echo $sql = "INSTER INTO ".$this->table_name ." ($colums)
VALUES ($values) ";
}
}
Here is the problem , I want to make filter or get method (basically reading from database) static and then return an array of objects from database data
class Base{
static function filter($conditions =array()){
$query_condition = $conditions ; // some function to convert array to sql string
$query_result = "SELECT * FROM ".$this->table_name ." WHERE $query_condition ";
$export = array();
$class = get_called_class();
foreach($query_result as $q )
{
$obj = new $class;
foreach($this->table_columns as $col )
$obj->$col = $q[$col];
$export[] = $obj;
}
return $export;
}
}
$users = User::filter(['username'=>'jason' , 'email'=>'j#gmail.com']);
Here is the problem , with filter as static function __construct in User class will not get called and table_columns, table_name will be empty
also in the filter method I can't access them anyway because they are not static ... I can make a dummy User object in the filter method and solve this problems but somehow it doesn't feel right
Basically I have a design problem any suggestion is welcomed
The problem is that the static object is not really "created" when you run statically.
If you want the constructor to run, but still in a static sort of way, you need a "singleton". This is where the object is created once and then you can re-use. You can mix this technique in a static and non-static way (as you're actually creating a "global" object that can be shared).
An example is
class Singleton {
private static $instance;
public static function getInstance() {
if (null === static::$instance) {
self::$instance = new static();
}
return self::$instance;
}
}
$obj = Singleton::getInstance();
Each time this gets the same instance and remembers state from before.
If you want to keep your code base with as few changes as possible, you can create yourself an "initialized" variable statically - you just need to remember to call it in each and every function. While it sounds great, it's even worse than a Singleton as it still remembers state AND you need to remember the init each time. You can, however, use this mixed with static and non-static calls.
class notASingletonHonest {
private static $initialized = false;
private static function initialize() {
if (!self::$initialized) {
self::$initialized = true;
// Run construction stuff...
}
}
public static function functionA() {
self::$initialize();
// Do stuff
}
public static function functionB() {
self::$initialize();
// Do other stuff
}
}
But read a bit before you settle on a structure. The first is far better than the second, but even then if you do use it, ensure that your singleton classes can genuinely be ran at any time without reliance on previous state.
Because both classes remember state, there are many code purists that warn you not to use singletons. You are essentially creating a global variable that can be manipulated without control from anywhere. (Disclaimer - I use singletons, I use a mixture of any techniques required for the job.)
Google "php Singleton" for a range of opinions and more examples or where/where not to use them.
I agree with a lot of your premises in your code and design. First - User should be a non static class. Second - Base base should have a static function that acts a factory for User objects.
Lets focus on this part of your code inside the filter method
1 $query_result = "SELECT * FROM ".$this->table_name ." WHERE $query_condition ";
2 $export = array();
3
4
5 $class = get_called_class();
6 foreach($query_result as $q )
7 {
8 $obj = new $class;
9
10 foreach($this->table_columns as $col )
11 $obj->$col = $q[$col];
12
13 $export[] = $obj;
14
15 }
The issue is that lines 1 and 10 are trying to use this and you want to know the best way to avoid it.
The first change I would make is to change protected $table_name; to const TABLE_NAME like in this comment in the php docs http://php.net/manual/en/language.oop5.constants.php#104260. If you need table_name to be a changeable variable, that is the sign of bad design. This will allow you change line 1 to:
$class = get_called_class()
$query_result = "SELECT * FROM ". $class::TABLE_NAME . "WHERE $query_condition";
To solve the problem in line 10 - I believe you have two good options.
Option 1 - Constructor:
You can rewrite your constructor to take a 2nd optional parameter that would be an array. Your constructor would then assign all the values of the array. You then rewrite your for loop (lines 6 to 15) to:
foreach($query_result as $q)
{
$export[] = new $class($q);
}
And change your constructor to:
function __construct($vals = array()){
$columns = get_class_vars('User');
$this->set_cols($columns);
foreach($columns as $col)
{
if (isset($vals[$col])) {
$this->$col = $vals[$col];
}
}
}
Option 2 - Magic __set
This would be similar to making each property public, but instead of direct access to the properties they would first run through a function you have control over.
This solution requires only adding a single function to your Base class and a small change to your current loop
public function __set($prop, $value)
{
if (property_exists($this, $prop)) {
$this->$prop = $value;
}
}
and then change line 10-11 above to:
foreach($q as $col => $val) {
$obj->$col = $val
}
Generally it is a good idea to seperate the logic of storing and retrieving the data and the structure of the data itself in two seperate classes. A 'Repository' and a 'Model'. This makes your code cleaner, and also fixes this issue.
Of course you can implement this structure in many ways, but something like this would be a great starting point:
class Repository{
private $modelClass;
public function __construct($modelClass)
{
$this->modelClass = $modelClass;
}
public function get($id)
{
// Retrieve entity by ID
$modelClass = $this->modelClass;
return new $$modelClass();
}
public function save(ModelInterface $model)
{
$data = $model->getData();
// Persist data to the database;
}
}
interface ModelInterface
{
public function getData();
}
class User implements ModelInterface;
{
public int $userId;
public string $userName;
public function getData()
{
return [
"userId" => $userId,
"userName" => $userName
];
}
}
$userRepository = new Repository('User');
$user = $userRepository->get(2);
echo $user->userName; // Prints out the username
Good luck!
I don't think there is anything inherently wrong with your approach. That said, this is the way I would do it:
final class User extends Base {
public $id ;
public $username ;
public $email ;
public $password ;
protected static $_table_name = 'users';
protected static $_table_columns;
public static function getTableColumns(){
if( !self::$_table_columns ){
//cache this on the first call
self::$_table_columns = self::_set_cols( get_class_vars('User') );
}
return self::$_table_columns;
}
public static function getTableName(){
return self::$_table_name;
}
protected static function _set_cols($cols){
unset($cols['_table_name']);
unset($cols['_table_columns']);
return array_keys($cols);
}
}
$u = new User;
$u->username = 'jason';
$u->email = 'j#gmail.com';
$u->insert();
And then the base class, we can use Late Static Binding here static instead of self.
abstract class Base {
abstract static function getTableName();
abstract static function getTableColumns();
public function insert(){
$colums = $values = array();
foreach( static::getTableColumns() as $col ){
if(!$this->$col) continue ;
$values[] = $this->$col ;
$colums[] = $col ;
}
$values = implode(',' , $values);
$colums = implode(',' , $colums);
echo $sql = "INSERT INTO ". static::getTableName() ." ($colums) VALUES ($values) ";
}
static function filter($conditions =array()){
$query_condition = $conditions ; // some function to convert array to sql string
$query_result = "SELECT * FROM ".static::getTableName() ." WHERE $query_condition ";
$export = array();
$columns = static::getTableColumns(); //no need to call this in the loop
$class = get_called_class();
foreach($query_result as $q ){
$obj = new $class;
foreach( $columns as $col ){
$obj->$col = $q[$col];
}
$export[] = $obj;
}
return $export;
}
}
Now on the surface this seems trivial but consider this:
class User extends Base {
public $id ;
public $username ;
public $email ;
public $password ;
final public static function getTableName(){
return 'users';
}
final public static function getTableColumns(){
return [
'id',
'username',
'email',
'password'
];
}
}
Here we have a completely different implementation of those methods from the first Users class. So what we have done is force implementation of these values in the child classes where it belongs.
Also, by using methods instead of properties we have a place to put custom logic for those values. This can be as simple as returning an array or getting the defined properties and filtering a few of them out. We can also access them outside of the class ( proper like ) if we need them for some other reason.
So overall you weren't that far off, you just needed to use static Late Static Binding, and methods instead of properties.
http://php.net/manual/en/language.oop5.late-static-bindings.php
-Notes-
you also spelled Insert wrong INSTER.
I also put _ in front of protected / private stuff, just something I like to do.
final is optional but you may want to use static instead of self if you intend to extend the child class further.
the filter method, needs some work yet as you have some array to string conversion there and what not.
I have a PHP class that extends another class, but i only get the MySQL to work at the extended class not the first class. Anyone knows what the problem can be? I can't seem to figure it out at all right now :S
# Vote class.
class vote {
public $newsID;
private $db;
# Construct.
public function __construct() {
global $_database;
$this->db = $_database;
}
# Vote Up.
public function voteUp() {
return '';
}
public function voteScore($newsID) {
$vote = mysqli_fetch_object($this->db->query("SELECT * FROM ".PREFIX."news WHERE newsID='".$newsID."' LIMIT 1"))->vote;
return '<span class="BigFontSize" style="position:absolute; top: 37px;right: 14px;">'.$vote.'</span>';
}
public function voteDown() {
return '';
}
}
# News class.
class news extends vote {
public $countNews;
private $db;
# Construct.
public function __construct() {
global $_database;
$this->db = $_database;
}
# Count News.
public function countNews() {
return $this->db->query("SELECT * FROM ".PREFIX."news ORDER BY date DESC")->num_rows;
}
# Print the news.
public function GetNews() {
$newsArray = array();
$sql = $this->db->query("SELECT * FROM ".PREFIX."news ORDER BY date DESC");
while ($rad = $sql->fetch_array()) {
$newsArray[] = array('headline' => $rad['headline'], 'content' => $rad['content'], 'date' => $rad['date'], 'poster' => $rad['userID'], 'published' => $rad['published'], 'intern' => $rad['intern'], 'newsID' => $rad['newsID']);
}
return $newsArray;
}
}
It's the vote class that doesnt have the functioning database. Am i missing something?
Just remove the constructor from news and it will inherit the vote constructor. You will have to make the $db class var in vote protected instead of private and remove it from news altogether. There is absolutely no gain from each having it's own reference to the same $database since it will still be the same instance.
While we are on the subject of better code design, do not use GLOBAL in PHP, EVER.
PHP global in functions
http://smartik.ws/2014/07/do-not-use-php-global-variables-never/
Instead of accessing a global connection object in the constructor as you are doing, use Dependency Injection, ie pass it into the constructor as a parameter:
# Vote class.
class vote {
public $newsID;
private $db;
# Construct.
public function __construct($_database)
{
$this->db = $_database;
}
}
$_database = new Database();
$news = new News($_database);
In the long term you will find that this is the prefered practice for very good reasons. It will also mark you out as a professional not an amateur. http://tutorials.jenkov.com/dependency-injection/index.html
I'm having a bit of trouble in designing my classes in php.
As you can see in my Code, i want to have one Class instance and having more classes as children which "talk" from one to another. im getting the logged user and get all his information stored to a variable. In my other Classes i recently need to get this UserData.
Any help and Ideas are welcome :)
class Factory
{
private $UserData;
public function Factory()
{
DB::connect();
$this->getLoggedUserData( $_SERVER['REMOTE_USER'] );
}
private function getLoggedUserData( $user )
{
$result = DB::query( "SELECT * FROM users WHERE user='$user' LIMIT 1" );
$this->UserData = $result->fetch_assoc();
}
public function getMyTasks()
{
// how to call that class, without instancing it over and over again
MyOtherClass -> getMyTasks();
}
}
class MyOtherClass
{
public function getMyTasks()
{
// how to access the "global" variable
$result = DB::query( "SELECT * FROM tasks WHERE userID=" . $UserData['userID'] . " LIMIT 1" );
// doSomething ($result);
}
}
class DB
{
private static $mysqli;
public static function connect()
{
$mysqli = new mysqli(MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB);
if ($mysqli->connect_error) {
die('Connect Error (' . $mysqli->conect_errno . ')' . $mysqli->connect_error);
}
mysqli_set_charset($mysqli, 'utf8');
self::$mysqli = $mysqli;
}
public static function query( $query )
{
$result = self::$mysqli->query( $query );
if ( self::$mysqli->error ) {
error_log("QUERY ERROR: " . self::$mysqli->error);
error_log("QUERY: " . $query);
}
return $result;
}
}
$Factory = new Factory();
OK, here goes a simple trivial approach to your problem
Mind you, this is not complete. Gimme some feedback if this is closing in on what you'd expect
your classes changed a bit
<?php
class Factory {
private $UserData;
private $UserTask;
public function Factory() {
DB::connect();
$this->getLoggedUserData($_SERVER['REMOTE_USER']);
}
private function getLoggedUserData($user) {
$result = DB::query('SELECT * FROM users WHERE user="'.$user.'" LIMIT 1');
$this->UserData = $result->fetch_assoc();
}
public function getMyTasks() {
// how to call that class, without instancing it over and over again
if (!isset($this->UserTask)) $this->UserTask = new MyOtherClass($this->UserData);
return $this->UserTask->getMyTasks();
}
}
class MyOtherClass {
private $UserData;
public function __construct($userData) {
$this->userData = $userData;
}
public function getMyTasks() {
// how to access the "global" variable
$task = DB::query('SELECT * FROM tasks WHERE userID='.$this->UserData['userID'].' LIMIT 1');
return $this->performTask($task);
}
public function performTask($task) {/* doSomething(); */}
}
// usage is not complete, waiting for some extra input
$factory = new Factory();
$taskResults = $factory->getMyTasks();
Any input on how to improve this is very welcome
edit following comments
Let's take a look at how you can solve the problem of having to share instances between different "apps" in your code
the singleton approach: an instance is created on the first call, all subsequent calls are passed the single instance
the registry pattern: an object created at the start of the script picks up all initialized requirements and stores them. If any "app" needs the basic set of services (it's not standalone), then pass the registry object to it's initializer/constructor.
I hope I understood your comments well enough, if not feel free to ask and correct me
Hard to say what would be best for you when i dont know more about the scale of your application etc.
Anyway the simplest way is something like this:
$otherClass = new MyOtherClass();
$Factory = new Factory($otherClass);
Class Factory
class Factory
{
private $UserData;
private someClass;
public function Factory(&$someClass)
{
$this->someClass = $someClass;
DB::connect();
$this->getLoggedUserData( $_SERVER['REMOTE_USER'] );
}
...
Usage
$this->someClass->getMyTasks();
But in case you only want access to the methods/variables of the parent, then yes extend the class.
I am a learner, I have a class db to help me connect and fetch results in mySQL.
$set = $db->get_row("SELECT * FROM users");
echo $set->name;
this way i use echo results outside a class.
Now i have created another class name user and it has this function
public function name() {
global $db;
$set = $db->get_row("SELECT * FROM users");
$this->name = $set->name;
}
after initializing the class user, when i try to echo $user->name i dont get expected results.
Note i have declared above var $name; in class user
I'm pretty concerned by several things I see here
The method name name() is terribly uncommunicative as to what the method is supposed to do. Remember, methods are actions - try to give them some sort of verb in their name.
Usage of global in a class (or even usage of global period) when you should be using aggregation or composition.
You don't show any execution examples, but I can only assume you never actually call User::name(), which is why your test is failing
Here's some code that addresses these concerns.
<?php
class DB
{
/* Your methods and stuff here */
}
class User
{
protected $db;
protected $name;
public function __construct( DB $db )
{
$this->db = $db;
}
public function getName()
{
if ( is_null( $this->name ) )
{
$set = $this->db->get_row( "SELECT * FROM users" );
$this->name = $set->name;
}
return $this->name;
}
}
$db = new DB();
$user = new User( $db );
echo $user->getName();
class DB
{
public function get_row($q)
{
# do query and store in object
return $object;
}
}
class User
{
public $name;
public function __construct()
{
$this->name();
}
public function name() {
global $db;
$set = $db->get_row("SELECT * FROM users");
echo "<pre>".print_r($set)."</pre>"; # make sure $set is returning what you expected.
$this->name = $set->name;
}
}
$db = new DB();
$user = new User();
echo $user->name;
I am very much sorry, i figured out that problem was on my part, i was using cookies and had two cookies set which were giving problems :(