There is a public library, and there is a class that can have only one instance in one PHP process, so it's Singleton. The problem is that initialization of this class require some configuration arguments and I can't find good issue to pass them in class constructor.
The only issue I found is:
public static function init($params) {
if(self::$instance) {
throw new Exception(__CLASS__ . ' already initialized');
}
$class = __CLASS__;
self::$instance = new $class($params);
}
public static function getInstance() {
if(!self::$instance) {
throw new Exception(__CLASS__ . ' is not initialized');
}
return self::$instance;
}
But I don't think that it's so really good.Is there any other ideas?
Thanks!
There is example of bad, but working issue:
if(!defined('PSEOUDSINGLETON_PARAM')) {
define('PSEOUDSINGLETON_PARAM', 'default value');
}
class PseoudoSingleton {
protected function __construct($param1 = PSEOUDSINGLETON_PARAM) {
// ...
}
public static function getInstance() {
if(!self::$instance) {
$class = __CLASS__;
self::$instance = new $class();
}
return self::$instance;
}
}
/* on library/utility level */
class nonSingletonService { public function __construct($options){} }
/* on application logic level, so, knows all context */
function getSingleton(){
static $inst;
if (!$inst) $inst=new nonSingletonService(calculateParameters());
return $inst;
}
Sample Singleton implementation:
class MySingleton
{
private static $_INSTANCE = null;
private function __construct()
{
// put initialization code here
}
public static function getInstance()
{
if(self::$_INSTANCE === null) self::$_INSTANCE = new MySingleton();
return self::$_INSTANCE;
}
}
Note that constructor is private, so it can only be called from the class itself.
References to the instance can be only obtained by getInstance call, which creates the object on first call, any subsequent calls will return references to an existing object.
Why are you checking it twice?
Just do the following:
private static function init($params) {
$class = __CLASS__;
self::$instance = new $class($params);
}
public static function getInstance($params) {
if(!self::$instance) {
self::init($params);
}
return self::$instance;
}
That way, you know that you only need to check once and you know you only call init() if the instance is not initialized.
Related
I have an extended class with an overriden method doSomething().
For some reason the inherited class' method never runs only the base one.
class cDemoClass {
public static function getInstance() {
static $instance = null;
if ($instance === null)
$instance = new cDemoClass();
return $instance;
}
private function __construct() {
}
protected function doSomething() {
echo 'do something';
}
public function call_me() {
$this->doSomething();
}
}
class cDemoClassEx extends cDemoClass {
protected function doSomething() {
echo 'do something differently';
}
}
$baseclass = cDemoClass::getInstance();
$baseclass->call_me();
echo '<br/>';
$extendedclass = cDemoClassEx::getInstance();
$extendedclass->call_me();
result:
do something
do something
The second one should be "do something differently" at least that's what I'm expecting.
Can anyone tell me what I'm doing wrong? Thanks
In this case, you need using late static binding (5.3+). Change in parent method getInstance line :
$instance = new cDemoClass();
to
$instance = new static();
You will get:
do something
do something differently
Read more about this feature here: http://www.php.net/manual/en/language.oop5.late-static-bindings.php
Because cDemoClassEx::getInstance(); is still returning new cDemoClass();. You have to also overwrite the getInstance() method:
class cDemoClass {
public static function getInstance() {
static $instance = null;
if ($instance === null)
$instance = new cDemoClass();
return $instance;
}
private function __construct() {
}
protected function doSomething() {
echo 'do something';
}
public function call_me() {
$this->doSomething();
}
}
class cDemoClassEx extends cDemoClass {
public static function getInstance() {
static $instance = null;
if ($instance === null)
$instance = new cDemoClassEx();
return $instance;
}
private function __construct() {
}
protected function doSomething() {
echo 'do something differently';
}
}
$baseclass = cDemoClass::getInstance();
$baseclass->call_me();
echo '<br/>';
$extendedclass = cDemoClassEx::getInstance();
$extendedclass->call_me();
You have to override with the cDemoClassEx::getInstance() and change this line
$instance = new cDemoClass();
into
$instance = new cDemoClassEx();
You will also need to declare the cDemoClass::__construct() as protected or simply override it in cDemoClassEx.
I have a class with a private constructor, to prevent direct instantiation.
class MyClass {
private static $instance;
private function __construct() {
}
public static function getInstance() {
if (isset(self::$instance)) {
return self::$instance;
} else {
$c = __CLASS__;
self::$instance = new $c;
return self::$instance;
}
}
}
I extend it
class ExtendedClass Extends MyClass {
//cannot touch parent::$instance, since it's private, so must overwrite
private static $instance;
//calling parent::getInstance() would instantiate the parent,
//not the extension, so must overwrite that too
public static function getInstance() {
if (isset(self::$instance)) {
return self::$instance;
} else {
$c = __CLASS__;
self::$instance = new $c;
return self::$instance;
}
}
}
When I call
$myInstance=ExtendedClass::getInstance();
In PHP 5.4.5 I get
PHP Fatal error: Call to private MyClass::__construct() from context
'ExtendedClass'
But In PHP 5.1.6, everything works as expected
What is happening here?
Also: I did not write MyClass, I don't have the ability to make the constructor protected, If I did that would solve the problem, but I can't.
It is the bug. You could fix your code like this (PHP > PHP5.3):
class MyClass {
private static $instance;
private function __construct() {
}
static function getInstance() {
if (isset(self::$instance)) {
return self::$instance;
} else {
self::$instance = new static();
return self::$instance;
}
}
}
class ExtendedClass Extends MyClass {
}
We have a class that holds a public array called $saved that contains lots of data required to share between methods (example below)...
class Common {
public $saved = array();
public function setUser($data) {
$this->saved['user_data'] = $data;
}
public function getUserID() {
return $this->saved['user_data']['id'];
}
}
There are literally thousands of lines of code that work like this.
The problem is that new instance of classes that extend Common are being made within some methods so when they access $saved it does not hold the same data.
The solution is to make $saved a static variable, however I can't change all of the references to $this->saved so I want to try and keep the code identical but make it act static.
Here is my attempt to make $this->saved calls static...
class PropertyTest {
private $data = array();
public function __set($name, $value) {
$this->data[$name] = $value;
}
public function __get($name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return null;
}
public function __isset($name) {
return isset($this->data[$name]);
}
public function __unset($name) {
unset($this->data[$name]);
}
}
class Common {
public $saved;
private static $_instance;
public function __construct() {
$this->saved = self::getInstance();
}
public static function getInstance() {
if (self::$_instance === null) {
self::$_instance = new PropertyTest();
self::$_instance->foo = array();
}
return self::$_instance->foo;
}
}
This doesn't quite work when setting a variable it doesn't seem to stay static (test case below)...
class Template extends Common {
public function __construct() {
parent::__construct();
$this->saved['user_data'] = array('name' => 'bob');
$user = new User();
}
}
class User extends Common {
public function __construct() {
parent::__construct();
$this->saved['user_data']['name'] .= " rocks!";
$this->saved['user_data']['id'] = array(400, 10, 20);
}
}
$tpl = new Template();
print_r($tpl->saved['user_data']);
$this->saved is empty when User gets initialized and doesn't seem to be the same variable, the final print_r only shows an array of name => bob.
Any ideas?
First of all, I have to say that, IMO, it is not that good to use an instance's property as a class's property ($saved is not declared as static but its value is shared with all instance).
Here is a working version http://codepad.org/8hj1MOCT, and here is the commented code. Basically, the trick is located in using both ArrayAccess interface and the singleton pattern.
class Accumulator implements ArrayAccess {
private $container = array();
private static $instance = null;
private function __construct() {
}
public function getInstance() {
if( self::$instance === null ) {
self::$instance = new self();
}
return self::$instance;
}
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
class Common {
public $saved = null;
public function __construct() {
// initialize the "saved" object's property with the singleton
// that variable can be used with the array syntax thanks to the ArrayAccess interface
// so you won't have to modify your actual code
// but also, since it's an object, this local "$this->saved" is a reference to the singleton object
// so any change made to "$this->saved" is in reality made into the Accumulator::$instance variable
$this->saved = Accumulator::getInstance();
}
public function setUser($data) {
$this->saved['user_data'] = $data;
}
public function getUser() {
return $this->saved['user_data'];
}
}
class Template extends Common {
// you can redeclare the variable or not. Since the property is inherited, IMO you should not redeclare it, but it works in both cases
// public $saved = null;
public function __construct() {
// maybe we can move this initialization in a method in the parent class and call that method here
$this->saved = Accumulator::getInstance();
}
}
I think there are a number of issues with this implementation that could well come back to bite you. However, in your current implementation your contructing a new instance (albeit through a static call) every time.
Instead use getInstance() as your singleton hook, and make your __construct private, as you'll only be accessing it from with the context of the Common class.
Like so:
class Common {
public $saved;
private static $_instance;
private function __construct() {
}
public static function getInstance() {
if (self::$_instance === null) {
self::$_instance = new self();
... any other modifications you want to make ....
}
return self::$_instance;
}
}
And don't ever run parent::_construct(), instead always use the getInstance() method.
You might also want to ditch the idea of extending this singleton class. This is really a bad antipattern and could cost you a number of issues in the long run. Instead just maintain a Common class that other classes can read / write to. As its a singleton you don't need to worry about injection.
I seem to have solved the problem, by making $this->saved a reference to a static variable it works...
class Common {
private static $savedData = array();
public $saved;
public function __construct() {
$this->saved =& self::$savedData;
}
}
Such as in this code:
class C{
function static getInstance(){
// here
}
}
$c = new c;
print_r(C::getInstance()); // should be $c
or at least using
print_r($c::getInstance()); // should be $c
Ummm... no, because by definition, there is no current class instance. The method getInstance() can be called from anywhere, and no instance of class C needs to even exist.
This would be the wrong way to create a singleton, but you could do this:
class C {
private static $instance;
public static function getInstance(){
return self::$instance;
}
public function __construct() {
self::$instance = $this;
}
}
$c = new c;
print_r(C::getInstance()); // should be $c
I'm not sure what you're trying to do, but this is not the way to do it.
Update:
A much better approach would be to do the following:
class C
{
private static $instance;
public static function getInstance()
{
if (!is_null(self::$instance)) return self::$instance;
self::$instance = new self;
return self::$instance;
}
private function __construct()
{
// Whatever
}
}
$c = new C; // This will not work since __construct() is private
$c1 = C::getInstance();
$c2 = C::getInstance();
echo ($c1 == $c2 ? 'yes' : 'no'); // yes
in PHP 5.3 you have some magic methods like __invoke() that should do what you want for your singletons.
Read more here: http://br2.php.net/manual/en/language.oop5.magic.php#object.invoke
<?php
class CallableClass
{
public function __invoke()
{
return this;
}
}
$obj = new CallableClass;
var_dump($obj);
Weird trouble. I've used singleton multiple times but this particular case just doesn't want to work. Dump says that instance is null.
define('ROOT', "/");
define('INC', 'includes/');
define('CLS', 'classes/');
require_once(CLS.'Core/Core.class.php');
$core = Core::getInstance();
var_dump($core->instance);
$core->settings(INC.'config.php');
$core->go();
Core class
class Core
{
static $instance;
public $db;
public $created = false;
private function __construct()
{
$this->created = true;
}
static function getInstance()
{
if(!self::$instance) {
self::$instance = new Core();
} else {
return self::$instance;
}
}
public function settings($path = null)
{
...
}
public function go()
{
...
}
}
Error code
Fatal error: Call to a member function settings() on a non-object in path
It's possibly some stupid typo, but I don't have any errors in my editor. Thanks for the fast responses as always.
You need to always return the singleton object from the singleton method, here you are not because you have an else statement, so the first invocation of getInstance will not return anything:
static function getInstance()
{
if(!self::$instance) {
self::$instance = new Core();
} else {
return self::$instance;
}
}
Your singleton method should look like this:
static function getInstance()
{
if(!self::$instance) {
self::$instance = new Core();
}
return self::$instance;
}
Also, having an instance variable denoting whether an object was created is pretty much useless, because you can just compare if(self::$instance !== NULL) and you're good to go.
getInstance should always return a value -- needs to change like this:
static function getInstance()
{
if(!self::$instance) {
self::$instance = new Core();
}
return self::$instance;
}
In addition to needing to change your getInstance() method to:
static function getInstance() {
if(!self::$instance) {
self::$instance = new Core();
}
return self::$instance;
}
...you're also trying to dereference $instance from the instance itself in the following call:
var_dump($core->instance);
You should either be checking:
var_dump($core);
or
var_dump(Core::$instance);
...which, after the $core = Core::getInstance() call, should be the same object.