I'm trying to accomplish the following syntax in my code:
$data = new Data();
$user = $data -> user -> get(1);
$product = $data -> product -> get(1);
By using:
class Data {
public $user = null;
public $product = null;
public $a = null;
...
function __construct() {
this -> user = new User();
this -> product = new Product();
this -> a = new A();
...
}
}
The problem with the code is that I will have lots of unused instances inside the data class because I will not use them all in specific scenarios. How can I prevent this?
At a very basic level, you can do something like this, you define a getter for the user property, and the object only gets instantiated when you call it for the first time.
class Data {
protected $user = null;
public function user()
{
if ($this->user === null) {
$this->user = new User();
}
return $this->user;
}
}
You could use aggregation, which means that you pass an object into the class, that way the class is getting either null or the object and you save resources by not initializing everything at once. Here's a decent post about it (not mine).
It's basically this:
class Test {
public $a = '';
public function __construct($object) {
$this->a = $object;
}
}
I would say you could try something like this:
class ThisOne{
protected $user = null;
public function user()
{
if ($this->user === null) {
$this->user = new User();
}
return $this->user;
}
}
The getter only gives you an object the first time it is called!
Related
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;
}
}
I have something like this:
class MyParent {
protected static $object;
protected static $db_fields;
public function delete() {
// delete stuff
}
public static function find_by_id($id = 0) {
global $database;
$result_array = self::find_by_sql("SELECT * FROM " . static::$table_name . " WHERE id=" . $database -> escape_value($id) . " LIMIT 1");
return !empty($result_array) ? array_shift($result_array) : false;
}
public static function find_by_sql($sql = "") {
global $database;
// Do Query
$result_set = $database -> query($sql);
// Get Results
$object_array = array();
while ($row = $database -> fetch_array($result_set)) {
$object_array[] = self::instantiate($row);
}
return $object_array;
}
private static function instantiate($record) {
$object = self::$object;
foreach ($record as $attribute => $value) {
if (self::has_attribute($attribute)) {
$object -> $attribute = $value;
}
}
return $object;
}
}
class TheChild extends MyParent {
protected static $db_fields = array('id', 'name');
protected static $table_name = "my_table";
function __construct() {
self::$object = new TheChild;
}
}
$child= TheChild::find_by_id($_GET['id']);
$child->delete();
I get this: Call to undefined method stdClass::delete() referring to the last line above. What step am I missing for proper inheritance?
You never actually instanciate the TheChild class, which should be done by
$var = new TheChild();
except in TheChild constructor itself.
So, the static $object field is never affected (at least in your example), so affecting a field to it (the line $object -> $attribute = $value; ) causes the creation of an stdClass object, as demonstrated in this interactive PHP shell session:
php > class Z { public static $object; }
php > Z::$object->toto = 5;
PHP Warning: Creating default object from empty value in php shell code on line 1
php > var_dump(Z::$object);
object(stdClass)#1 (1) {
["toto"]=>
int(5)
}
This object does not have a delete method.
And as said before, actually creating a TheChild instance will result in an infinite recursion.
What you want to do is this, probably:
class TheChild extends MyParent {
protected static $db_fields = array('id', 'name');
protected static $table_name = "my_table";
function __construct() {
self::$object = $this;
}
}
Edit: Your updated code shows a COMPLETE different Example:
class MyParent {
protected static $object;
public function delete() {
// delete stuff
}
}
class TheChild extends MyParent {
function __construct() {
self::$object = new TheChild;
}
}
$child = new TheChild;
$child->delete();
Calling "Child's" Constructor from within "Child's" Constructor will result in an infinite loop:
function __construct() {
self::$object = new TheChild; // will trigger __construct on the child, which in turn will create a new child, and so on.
}
Maybe - i dont know what you try to achieve - you are looking for:
function __construct() {
self::$object = new MyParent;
}
ALSO note, that the :: Notation is not just a different Version for -> - it is completely different. One is a Static access, the other is a access on an actual object instance!
I am currently using something like
class User{
/* #var Contacts*/
public $contacts = array();
}
$userObj = new User();
$userObj->contacts[] = new Contact(...);
$userObj->contacts[] = new Contact(...);
Tough we can document the type of variable using phpDocumentor, is it also possible to restrict other types of objects to be assigned to the contacts array
$userObj->contacts[] = 2.3 //should be considered as invalid
Not how it works in php
Here is what you can do instead
class User{
/* #var Contacts*/
private $contacts = array();
public function setContacts(Contact $contact){
$this->contacts[] = $contacts;
}
}
No you can use it like so
$userObj = new User();
$userObj->setContacts(new Contact(...));
And the following will cause an error
$userObj->setContacts(2.3);
Declare $contacts as private and use getter and setter methods.
Class User{
private $contacts = array();
function addContact($contact) {
if (is_object($contact) && get_class($contact) == "Contact") {
$this->contacts[] = $contact;
} else {
return false;
// or throw new Exception('Invalid Parameter');
}
}
function getContacts() {
return $this->contacts;
}
}
I've never worked before with polymorphism. I just heard about it when this question came up.
I have a little backend with 2 permissions. Admin/Normal User. Depending on the permission, i want to display a different navigation, less or more options on the forms etc. But i don't want to create a form for each permission but rather disable the elements i don't need etc.
How would i go with that?
At the moment, i'm using something like that: (Which isn't really polymorphism)
<?php
class My_Resources_ResourceLoader extends Zend_Application_Resource_ResourceAbstract {
public $templateForm = null;
public $customerForm = null;
function init() {
$permission = 'admind';
if($permission == 'admin') {
$this->templateForm = new Application_Form_newTemplate;
} else {
$form = new Application_Form_newTemplate;
$form->removeElement('newTemplate_customer');
$this->templateForm = $form;
}
return $this;
}
}
And in my controller e.g.
<?php
$bootstrap = $this->getInvokeArg('bootstrap');
$xx = $bootstrap->getResource('ResourceLoader');
$this->view->test = $xx->templateForm;
The roles never gonna change. This will probably be okay but isn't the very best solution. What would be a better approach to this?
I've thrown away the approach above and now use real polymorphism like this:
at Application/Model got an interface like:
And 2 Classes like:
<?php
class Application_Model_TemplateUser implements Application_Model_TemplateInterface {
private $table = null;
private $row = null;
private $id = null;
private $formValues = null;
function __construct() {}
public function exist() {}
public function save() {}
public function getCustomerId($name) {}
public function update() {}
public function getForm() {
$form = new Application_Form_newTemplate;
$form->removeElement('newTemplate_customer');
return $form;
}
}
And
<?php
class Application_Model_TemplateAdmin implements Application_Model_TemplateInterface {
private $table = null;
private $row = null;
private $id = null;
private $formValues = null;
function __construct() {}
public function exist() {}
public function save() {}
public function getCustomerId($name) {}
public function update() {}
public function getForm() {
return new Application_Form_NewTemplate();
}
}
In my Controller i do:
<?php
$permission = 'User'; //TODO: Get from Session
$class = 'Application_Model_Template' . $permission;
$xx = new $class;
$form = $xx->getForm();
$this->view->test = $form;
This are just examples. But i think like that I'm really on a better way. Maybe i'm going to use abstract classes since i'm using Zend_Db-Table_Row, which is always the same for updating a row, so it would make more sense using a abstract class instead of an interface.
Nice article about Polymorphism in PHP: http://net.tutsplus.com/tutorials/php/understanding-and-applying-polymorphism-in-php/
In this little example below in PHP what would be a good way to be able to create the variables in the user class and then be able to use them on any page where I create a user object?
<?PHP
//user.class.php file
class User
{
function __construct()
{
global $session;
if($session->get('auto_id') != ''){
//set user vars on every page load
$MY_userid = $session->get('auto_id'); //user id number
$MY_name = $session->get('disp_name');
$MY_pic_url = $session->get('pic_url');
$MY_gender = $session->get('gender');
$MY_user_role = $session->get('user_role');
$MY_lat = $session->get('lat');
$MY_long = $session->get('long');
$MY_firstvisit = $session->get('newregister');
}else{
return false;
}
}
}
?>
<?PHP
// index.php file
require_once $_SERVER['DOCUMENT_ROOT'].'/classes/user.class.php';
$user = new User();
//how should I go about making the variables set in the user class available on any page where I initiate the user class?
// I know I can't use
// echo $MY_pic_url;
// 1 way I can think of is to have the class return an array but is there another way?
?>
To elaborate on Lance' answer; if the point of the class is to be nothing more than an container for the data, in stead of doing something with the data you're pretty safe. But a good principal of OOP is to stick to encapsulation. Encapsulation means, amongst other things, that you hide the inner details of your object from the outside and only let the outside access the fields through it's interface methods.
Let's say you don't want the fields in the User object to be altered from the outside, but only accessed, then you'ld be better of with something like the following:
class User
{
private $_userId;
// and a bunch of other fields
public function __construct( $data )
{
// do something with the data
}
public function getUserId()
{
return $this->_userId;
}
// and a bunch of other getters to access the data
}
In all honesty, you could use magic methods like __set and __get to simulate what you want and catch any unwanted altering in the __set method.
Furthermore, I wouldn't use the session as a global variable. You should pass the session object as an argument to it's constructor (like I illustrated in the example). This enforces loose coupling. Because now your User objects are tied to the global session object, but with passing it to the constructor any data could be passed in. This makes the class more flexible.
Edit:
Here's an example of how you could pass an object (for instance your session object) to the constructor. One thing to keep in mind is that, the way your session object is designed, it still, somewhat, enforces tight coupling, because it mandates getting properties through the get() method.
class User
{
public function __construct( $data )
{
$this->_id = $data->get( 'id' );
$this->_firstname = $data->get( 'firstname' );
// etc
}
}
// usage
$session = new YourSessionObject();
$user = new User( $session );
You have a few options at hand to propagate loose coupling, and making you User object a little more flexible.
Mandate that the data for you User object is provided as:
distinct arguments
class User
{
protected $_id;
protected $_firstname;
// etc;
public function __construct( $id, $firstname, /* etc */ )
{
$this->_id = $id;
$this->_firstname = $firstname;
// etc
}
}
// usage
$session = new YourSessionObject();
$user = new User( $session->get( 'id' ), $session->get( 'firstname' ), /* etc */ );
array
class User
{
protected $_fields = array(
'id' => null,
'firstname' => null,
// etc
);
// dictate (type hint) that the argument should be an array
public function __construct( array $data )
{
foreach( $data as $field => $value )
{
if( array_key_exists( $field, $this->_fields )
{
$this->_fields[ $field ] = $value;
}
}
}
}
// usage
$session = new YourSessionObject();
$array = /* fill this array with your session data */;
$user = new User( $array );
implementing some interface
// objects that implement this interface need to implement the toArray() method
interface Arrayable
{
public function toArray();
}
class User
{
protected $_fields = array(
'id' => null,
'firstname' => null,
// etc
);
// dictate (type hint) that the argument should implement Arrayable interface
public function __construct( Arrayable $data )
{
// get the data by calling the toArray() method of the $data object
$data = $data->toArray();
foreach( $data as $field => $value )
{
if( array_key_exists( $field, $this->_fields )
{
$this->_fields[ $field ] = $value;
}
}
}
}
class YourSessionObject implements Arrayable
{
public function toArray()
{
/* this method should return an array of it's data */
}
}
// usage
$session = new YourSessionObject();
$user = new User( $session );
etc
There are a few other options, but this should give you some ideas. Hope this helps.
Make them public members:
class user
{
public $first_name;
function __construct()
{
$this->first_name = $_SESSION['first_name'];
}
}
$user = new user();
echo $user->first_name;
Sidenote: the constructor has no return value, i.e. return false does not have the effect you probably intended.
Either use public properties or protected properties+accessor methods.
Or store the $session in your object and then "delegate" each query for a property to that $session object.
class User
{
protected $session;
function __construct($session)
{
$this->session = $session;
}
function get($name) {
if( ''==$this->session->get('auto_id')) {
throw new Exception('...');
}
return $this->session->get($name);
}
}
$user = new User($session);
echo $user->get('disp_name');
Or use the "magic" __get() method, e.g.
class User
{
protected static $names = array(
'auto_id', 'disp_name', 'pic_url', 'gender',
'user_role', 'lat', 'long', 'newregister'
);
protected $properties = array();
function __construct()
{
global $session;
if($session->get('auto_id') != '') {
foreach(self::$names as $n) {
$this->properties[$n] = $session->get($n);
}
}
else {
throw new Exception('...');
}
}
function __get($name) {
return isset($this->properties[$name]) ? $this->properties[$name] : null;
}
}
$user = new User;
echo $user->disp_name;
Use attributes to store it.
<?PHP
//user.class.php file
class User
{
public $MY_userid;
public $MY_name;
public $MY_pic_url;
public $MY_gender;
public $MY_user_role;
public $MY_lat;
public $MY_long;
public $MY_firstvisit;
function __construct()
{
global $session;
if($session->get('auto_id') != ''){
//set user vars on every page load
$this->MY_userid = $session->get('auto_id'); //user id number
$this->MY_name = $session->get('disp_name');
$this->MY_pic_url = $session->get('pic_url');
$this->MY_gender = $session->get('gender');
$this->MY_user_role = $session->get('user_role');
$this->MY_lat = $session->get('lat');
$this->MY_long = $session->get('long');
$this->MY_firstvisit = $session->get('newregister');
}else{
return false;
}
}
}
?>
You can also save the user object in the $_SESSION variable after you have created it initially.
<?PHP
//user.class.php file
class User
{
function __construct()
{
var $MY_userid;
var $MY_name;
var $MY_pic_url;
var $MY_gender;
var $MY_user_role;
var $MY_lat;
var $MY_long;
var $MY_firstvisit;
global $session;
if($session->get('auto_id') != ''){
//set user vars on every page load
$this->MY_userid = $session->get('auto_id'); //user id number
$this->MY_name = $session->get('disp_name');
$this->MY_pic_url = $session->get('pic_url');
$this->MY_gender = $session->get('gender');
$this->MY_user_role = $session->get('user_role');
$this->MY_lat = $session->get('lat');
$this->MY_long = $session->get('long');
$this->MY_firstvisit = $session->get('newregister');
}else{
return false;
}
}
}
?>
<?PHP
// index.php file
require_once $_SERVER['DOCUMENT_ROOT'].'/classes/user.class.php';
$user = new User();
print $user->MY_name;
?>