Can 2 singleton classes reference each other? - php

Why does this not work? Shouldn't each instance simply reference each other once?
class foo {
private static $instance;
private function __construct() {
$test = bar::get_instance();
}
public static function get_instance() {
if (empty(self::$instance)) {
self::$instance = new foo();
}
return self::$instance;
}
}
class bar {
private static $instance;
public function __construct() {
$test = foo::get_instance();
}
public static function get_instance() {
if (empty(self::$instance)) {
self::$instance = new bar();
}
return self::$instance;
}
}
$test = foo::get_instance();

You have what's known as a circular-dependency. A needs B to complete to construct, and B needs A to complete to construct. So it goes round and round forever.
Basically, what's happening is that self::$instance on each class doesn't get populated until new class() finishes. So in the constructor, you're calling the other getInstance. But every time you hit get_instance(), self::$instance is still null because the previous new never finsihed. And round and round you go. It will keep going until the end.
Instead, add it in after construction:
class foo {
private static $instance;
private function __construct() {
}
private function setBar(bar $bar) {
$this->bar = $bar;
}
public static function get_instance() {
if (empty(self::$instance)) {
self::$instance = new foo();
self::$instance->setBar(bar::get_instance());
}
return self::$instance;
}
}
class bar {
private static $instance;
public function __construct() {
}
private function setFoo(foo $foo) {
$this->foo = $foo;
}
public static function get_instance() {
if (empty(self::$instance)) {
self::$instance = new bar();
self::$instance->setFoo(foo::get_instance());
}
return self::$instance;
}
}
However, I would really suggest re-architecting your relationships and classes so that you Inject the Dependencies rather than making self-dependent singletons.

You will have an infinite recursion:
foo::get_instance(); -> foo constructor -> bar::get_instance() -> bar constructur -> foo::get_instance(); -> ...

Because neither constructor ever returns. Neither foo or bar instance is ever created, because they keep refernecing each other.

What you have here is a case of recursion. The call stack looks like this:
foo::get_instance()
foo::__construct()
bar::get_instance()
bar::__construct()
foo::get_instance()
foo::__construct()
...
The reason this is happening is that when foo::get_Instance is called from the bar constructor, it is still in the context of the initial call to foo::get_Instance and has not set the $instance value yet.
There are a number of ways you can address this, but I would first ask why you need to have them as members of each other.
If you don't need to, you would simply call foo::get_instance and bar::get_instance at the points that you need to access them.

Related

Is there a way to check if class member access on instantiation?

So in PHP 5.4 and up you can call a method on instantiation like so.
$class = new Foo()->methodName();
I would like to check when the class is instantiated if it was done so with a method at the same time.
Is it possible to check if the class was instantiated without a method at the same time and default to a method if not?
you can use instanceOf on the object for which you wanted to check the class.
Constructor of the class cannot return a value in php, so you can't use singleton incapsulated in constructor and have it looks nice. Of cource, you can make something like this
// NOT FOR USE
class Foo {
private static $instance;
public function __construct($skipIncapsulate = false) {
if (!$skipIncapsulation && !self::$instance)
{
self::instance = new self(true);
}
}
public function bar() {
// Do what you want with self::$instance
}
}
N.B. Do not use the example above, it is ugly solution, which also incapsulates unnecessary logic in controller, and while working with the class
Better use a usual singleton like this
class Foo {
private static $instance;
private function __construct()
{}
public static getInstance()
{
return self::$instance ?? self::$instance = new self();
}
}
Nevertheless you can create a new class FooManager, which will delegate the if the Foo class is defined
class FooManager {
private static $foo;
public function __construct()
{
self::$foo = self::$foo ?? self::$foo = new Foo();
}
public function getFoo()
{
return self::$foo;
}
}
// usage in your code
(new FooManager)->getFoo()->bar();
// You can add __call method to use (new FooManager)->foo->bar()
This will simplify working with your code. Incapsulating static self-instance in constructor is not the good practice

PHP Singleton: Maximum function nesting level of '100' reached, aborting

I am writing a custom PHP Application from scratch and for some classes I use the singleton pattern, because I need some information to be calculated one time and them I just use them.
Today I wrote a big part of my application and when I tested it all in all, it threw me the following error:
Maximum function nesting level of '100' reached, aborting.
I did some tests and found that error is generated by something like this:
File index.php
class Foo
{
public function __construct()
{
if(!class_exists('Bar', false))
{
require 'Bar.php';
}
$bar = new Bar;
$bar->doSomething();
}
public function showSomeInformation()
{
// information
}
}
function F()
{
static $instance = null;
if(is_null($instance))
{
$instance = new Foo;
}
return $instance;
}
F();
File Bar.php
class Bar
{
public function doSomething()
{
F()->showSomeInformation();
}
}
To my mind it is valid because F() was called before and it should have the instance of Foo in the static variable, and I believe it should somehow work, but it doesn't.
I feel lost now. How can I make it to work or at least how can I change something to have similar behavior?
The $instance value remains null all the time. Why? Well have a look what happens before you assign the instance to $instance.
Before $instance has any different value you call $bar->doSomething(); again.
This means you run F() again but $instance is still null. Now you instantiate Foo again but guess what $instance is still null.
Try this:
<?php class Foo
{
static $instance = null;
public function __construct()
{
if(!class_exists('Bar', false))
{
require 'Bar.php';
}
self::$instance = $this;
$bar = new Bar;
$bar->doSomething(self::$instance);
}
public function showSomeInformation()
{
// information
}
}
class Bar
{
public function doSomething($instance)
{
F($instance)->showSomeInformation();
}
}
function F($instance = null)
{
if(is_null($instance))
{
$instance = new Foo;
}
return $instance;
}
F();
If you use the singleton pattern, make sure the class itself keeps track of whether it got initialized or not. Letting an external source handle this can cause a lot of problems (as you just experienced).
The infinite recursion happens in the constructor of foo:
function F()
{
static $instance = null;
if(is_null($instance))
{
$instance = new Foo;
echo("Never reached\n");
if( is_null($instance) )
{
echo("Still null!\n");
}
}
return $instance;
}
The first F(); call will not create a Foo. It will call the constructor for Foo, which will call F() before returning the Foo object, but F() calls the constructor on yet another Foo, which will do the same, forever, so no new Foo will ever return from F(). So we run out of stack.
Here's an example of a sane singleton-pattern in PHP: Design Patterns
I don't like the singleton pattern and I guess, if you have a bit experience and begin with unit testing, you will also hate it. But the pattern does not work like you tried it.
class MySingletonClass
{
private static $instance;
// Make the constructor protected, to prevent direct instantiation
protected function __construct() {}
/**
* #return MySingletonClass
*/
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new self(); // new static() would we valid too, relevant for extending (late state binding)
}
return self::$instance;
}
public function getSomething()
{
if (!$this->calculated) {
$this->calculated = $this->calculateSomething();
}
return $this->calculated;
}
}
echo MySingletonClass::getInstance()->getSomething();

PHP Don't allow object to instantiate more than once

I have an abstract class that is inherited by a number of other classes. I'd like to have it so that instead of re-instantiating (__construct()) the same class each time, to have it only initialize once, and utilize the properties of the previously inherited classes.
I'm using this in my construct:
function __construct() {
self::$_instance =& $this;
if (!empty(self::$_instance)) {
foreach (self::$_instance as $key => $class) {
$this->$key = $class;
}
}
}
This works - sort of, I'm able to get the properties and re-assign them, but within this, I also want to call some other classes, but only one time.
Any suggestions for a better way to go about doing this?
Thats a Singleton construct:
class MyClass {
private static $instance = null;
private final function __construct() {
//
}
private final function __clone() { }
public final function __sleep() {
throw new Exception('Serializing of Singletons is not allowed');
}
public static function getInstance() {
if (self::$instance === null) self::$instance = new self();
return self::$instance;
}
}
I made the constructor and __clone() private final to hinder people from cloning and directly instanciating it. You can get the Singleton instance via MyClass::getInstance()
If you want an abstract base-singleton class have a look at this: https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/SingletonFactory.class.php
You're referring to the Singleton pattern:
class Foo {
private static $instance;
private function __construct() {
}
public static function getInstance() {
if (!isset(static::$instance)) {
static::$instance = new static();
}
return static::$instance;
}
}

How can I create a singleton in PHP?

I'm using PDT and Aptana on Eclipse Indigo with PHP 5.3 and I want to create a singleton in a class.
By singleton, I mean I want to just have one instance of that object, and for other objects or classes to get that single instance via a function that returns that object (so this would mean I'm trying to create an object within the class that defines that object, ie: creating objA within the class objA)
I understand you can't just go a head and do this:
public $object = new Object();
with in a class definition, you have to define it in the constructor.
How can I go ahead and do this? I'm coming from Java, so it could be I'm confusing some basic stuff. Any help is greatly appreciated. Here's the code:
<?php
class Fetcher{
private static $fetcher = new Fetcher(); //this is where I get the unexpected "new" error
static function getFetcherInstance(){
return $this->$fetcher;
}
}
?>
Solved! Thanks for all the help guys!
try this:
<?php
class myclass{
private static $_instance = null;
public static function getInstance() {
if (self::$_instance === null) {
self::$_instance = new myclass();
}
return self::$_instance;
}
}
?>
and call it with:
<?php
$obj = myclass::getInstace();
?>
You cannot assign a class property in PHP like that. It must be a scalar, or array value, or the property must be set in a method call.
protected static $fetcher;
static function getFetcherInstance(){
if (!self::$fetcher) {
self::$fetcher = new Fetcher();
}
return self::$fetcher;
}
Also, notice that I did not use $this->, as that only works for object instances. To work with static values you need to use self:: when working within the class scope.
You might want to just read common design patterns on the php site. There are pretty good examples with good documentation:
http://www.php.net/manual/en/language.oop5.patterns.php
Else, a singleton is simply a method that returns one single instance of itself:
class MySingletonClass {
private static $mySingleton;
public function getInstance(){
if(MySingletonClass::$mySingleton == NULL){
MySingletonClass::$mySingleton = new MySingletonClass();
}
return MySingletonClass::$mySingleton;
}
}
Building on #periklis answer you might want separate singletons for different application scopes. For example, lets say you want a singleton of a database connection - fine. But what if you have TWO databases you need to connect too?
<?php
class Singleton
{
private static $instances = array();
public static function getInstance($name = 'default')
{
if ( ! isset(static::$instances[$name]))
{
static::$instances[$name] = new static();
}
return static::$instances[$name];
}
}
Class DB extends Singleton {}
$db_one = DB::getInstance('mysql');
$db_two = DB::getInstance('pgsql');
Alse define __clone method
class Fetcher {
protected static $instance;
private function __construct() {
/* something */
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Fetcher();
}
return self::$instance;
}
private function __clone() {
/* if we want real singleton :) */
trigger_error('Cannot clone', E_USER_ERROR);
}
}
Basically implementing a singleton pattern means writing a class with a private constructor and a static method to build itself. Also check PHP site for it: http://www.php.net/manual/en/language.oop5.php and http://it2.php.net/manual/en/book.spl.php
class A {
protected $check;
private function __construct($args) {
}
static public function getSingleton($args) {
static $instance=null;
if (is_null($instance)) {
$instance=new A();
}
return $instance;
}
public function whoami() {
printf("%s\n",spl_object_hash($this));
}
}
$c=A::getSingleton("testarg");
$d=A::getSingleton("testarg");
$c->whoami(); // same object hash
$d->whoami(); // same object hash
$b= new A("otherargs"); // run time error
<?php
class MyObject {
private static $singleInstance;
private function __construct() {
if(!isset(self::$singleInstance)) {
self::$singleInstance = new MyObject;
}
}
public static function getSingleInstance() {
return self::$singleInstance;
}
}
?>
class MyClass {
private static $instance;
public static function getInstance() {
if( !isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
}
Then call get instance using
MyClass::getInstance();

Singleton isn't returning existing instance

This is how I've set up my Singleton
<?php
class MySingleton
{
private static $instance;
private static $you;
private function __construct()
{
$this->you = "foo";
}
public static function singleton()
{
if (!isset(self::$instance)) {
$className = __CLASS__;
self::$instance = new $className;
}
return self::$instance;
}
public function getYou()
{
return $this->you;
}
public function setYou($val)
{
$this->you = $val;
}
}
?>
In file1.php, I do this:
require_once('session.php');
$session = MySingleton::singleton();
$session->setYou('bar');
echo $session->getYou(); //echoes 'bar'
In file1.php, I have a hyperlink to file2.php, where I have this code:
require_once('session.php');
$session = MySingleton::singleton();
echo ($session->getYou()); //prints 'foo' which gets set in the constructor
It seems it is creating a new instance for file2, which is why $you has the default value of foo. Where am I going wrong? Why don't I get the instance I was using in file1.php?
A singleton in PHP is only valid for the current request (as HTTP is stateless).
If you want to preserve the state of that object, save it in a session:
class MySingleton
{
private static $instance;
private static $you;
private function __construct()
{
$this->you = "foo";
}
public static function singleton()
{
session_start();
if (!($_SESSION['MyInstance'] instanceof MySingleton)) {
$className = __CLASS__;
$_SESSION['MyInstance'] = new $className;
}
return $_SESSION['MyInstance'];
}
public function getYou()
{
return $this->you;
}
public function setYou($val)
{
$this->you = $val;
}
}
Singletons are meant to be "single" during ONE request. For everything else you will have to serialize the object or use Sessions.
Static data only works within a single PHP program.
If your two scripts are running as separate pages they will not share any state.
Singletons in PHP are meant to return the same instance of the object during the same execution of the program, not between different pages o page loads. What you need is to store the object in the session (sending it to "sleep") and retrieving it later ("waking it up"), but beware, they are still different instances, one a clone of the other, and updating une will not update the other.

Categories