I am trying to implement the singleton pattern in php like described here in Example #2:
http://www.php.net/singleton
When I run the example code
$singleton = Example::singleton(); // prints "Creating new instance."
echo $singleton->increment(); // 0
echo $singleton->increment(); // 1
$singleton = Example::singleton(); // reuses existing instance now
echo $singleton->increment(); // 2
echo $singleton->increment(); // 3
it allways ends with Fatal Error 'Clone is not allowed.' directly after 'Creating new instance.'
I would expect that there is no reason for php to call the __clone-method.
In another real-life project of mine I want to have a singleton PlayerManager that holds Player-objects in an array (loaded only once in __construct) and has functions like GetPlayers() or GetPlayersByID($id).
In my script I write something like
$pm = PlayerManager::GetInstance();
$p1 = $pm->GetPlayerByID(0);
echo $p1->SomeNumber; //100
$p1->SomeNumber = 200;
$p2 = $pm->GetPlayerByID(0);
echo $p2->SomeNumber; //100 and not 200, as I would expect
Can someome give me some hints how to implement the PlayerManager using the Singleton pattern correct? I'm not sure if it is only a problem with the singleton or also a problem with returning object references...
I'm not quiet sure why you're getting that error (post your singleton class if you want help with that). Though I always preferred this version to the one you're using, it's a bit simpler: http://www.talkphp.com/advanced-php-programming/1304-how-use-singleton-design-pattern.html
So with the above, your code would look like:
class Counter
{
$CurrentValue = 0;
// Store the single instance of Database
private static $m_pInstance;
private function __construct() { }
public static function getInstance()
{
if (!self::$m_pInstance)
{
self::$m_pInstance = new Counter();
}
return self::$m_pInstance;
}
public function increment ($by)
{
$this->CurrentValue += $by;
return $this->CurrentValue;
}
public function getValue ()
{
return $this->CurrentValue;
}
}
And to use:
$counter = Counter::getInstance();
echo $counter->increment(); // 0
echo $counter->increment(); // 1
$counter = null;
$counter = Counter::getInstance(); // reuses existing instance now
echo $counter->increment(); // 2
echo $counter->increment(); // 3
Tell me how that works out for you.
Related
We have PHP code in production that sometimes fails with "Call to member function on null", although the same code path executes fine several times before that in one invocation. We have a test that reproduces the error consistently at the same run of the loop.
I already proved that the object gets created correctly in the factory even if it gets returned as null. The factory method must not return null in any case, as indicated in the DocBlock. This question is not related to nullable return types or something like that.
The process does not exceed memory or runtime limitations and I already tried turning off the garbage collector, but no luck. The error happens both in PHP 7.0 and 7.3 on Debian, did not try on other versions or operating systems.
I am not allowed to paste the real code here, but I wrote a simple mockup to explain in more detail. Please keep in mind that this demo code will not result in the error, it is just meant to show the general structure of the program that runs into this fault.
// Three placeholder classes with common methods
class Bender
{
public function common()
{
echo "Bend, bend!" . PHP_EOL;
}
}
class Clamper
{
public function common()
{
echo "Clamp, clamp!" . PHP_EOL;
}
}
class Worker
{
public function common()
{
echo "Work, work!" . PHP_EOL;
}
}
// abstract class with static factory to produce objects
abstract class MomCorp
{
/**
* Factory to create one of several objects
*
* #param string $name
* #return Bender|Clamper|Worker
*/
public static function factory($name)
{
$type = self::managementDecision($name);
switch ($type)
{
case "bender":
$robot = new Bender();
break;
case "clamper":
$robot = new Clamper();
break;
default:
$robot = new Worker();
}
// optional QA works flawlessly here, object is fine all the time!
// $robot->common();
return $robot;
}
public static function managementDecision($name)
{
// irrelevant magic happens on $name here
return "bender";
}
}
foreach (['Rodriguez', 'Angle-ine', 'Flexo', 'Billie'] as $newName)
{
echo "$newName: ";
// The next two lines break after some loops - why?
// The perfectly functional object in factory gets returned as null
$bot = MomCorp::factory($newName);
$bot->common();
}
// SAMPLE OUTPUT
// Rodriguez: Bend, bend!
// Angle-ine: Bend, bend!
// Flexo: Bend, bend!
// Billie: Call to a member function common() on null
Has anyone experienced the same and has any hints on what might cause such an error and how to fix it?
I have a class which is meant to "load" an another class, however I haven't been able to get it to work.
Error Message
Fatal error: Call to undefined method stdClass::echoString() in C:\Program Files (x86)\EasyPHP-DevServer-14.1VC11\data\localweb\classes\example.php on line 5
Code
My code is broken up into three main sections:
api.php - the class to load the other classes.
API/exampleExternalAPI.php - (multiple files) the classes that api.php loads
example.php - the file that uses the main class (api.php)
If it helps these files can be downloaded from my dropbox
api.php
<?php
/* Config */
define('pathToAPIs','API/');
/* Autoload Function */
spl_autoload_register(function($className){
$namespace=str_replace("\\","/",__NAMESPACE__);
$className=str_replace("\\","/",$className);
$class=pathToAPIs.(empty($namespace)?"":$namespace."/")."{$className}.php";
include_once($class);
});
class api {
private $listOfAPIs;
public $APIs;
public function __construct($setAPI = null){
$this->updateListOfAPIs();
if (isset($setAPI)){
return $this->setAPI($setAPI);
}
}
public function setAPIs($setAPIs){
$this->APIs = null; // clears a previous call to this method
if (!is_array($setAPIs)){ // if not an array
$setAPIs = array($setAPIs); // make array
}
foreach ($setAPIs as $setAPIType){
if(in_array($setAPIType,$this->listOfAPIs)){
$array[$setAPIType] = new $setAPIType;
}
}
$this->APIs = json_decode(json_encode($array), FALSE); // convert array of required api objects to an object
return $this->APIs;
}
public function getListOfAPIs($update = false){
if ($update){
$this->updateListOfAPIs();
}
return $this->listOfAPIs;
}
private function updateListOfAPIs(){
$this->listOfAPIs = null; // clears a previous call to this method
$it = new FilesystemIterator(pathToAPIs);
foreach ($it as $fileinfo){
$filename = pathinfo($fileinfo->getFilename(), PATHINFO_FILENAME); // removes extension
$this->listOfAPIs[]= $filename;
}
}
public function __call($method,$args){
}
}
API/exampleExternalAPI.php
<?php
class exampleExternalAPI {
public function echoString($string){
echo $string;
}
}
example.php
<?php
require_once 'api.php';
$api = new api();
$api->setAPIs('exampleExternalAPI');
$api->APIs->exampleExternalAPI->echoString('string');
Background Info
(may give some insight to my madness)
I'm working on a project where I need to connect to lots of external APIs.
So I decided to creating a class to look after all my communications with external APIs ( not sure if best way - new to Object Oriented Programming).
I'm not entirely sure what problem you're trying to solve, but if your APIs is a simple stdClass instance it should work as expected:
public function setAPIs($setAPIs)
{
$this->APIs = new stdClass; // clears a previous call to this method
if (!is_array($setAPIs)) { // if not an array
$setAPIs = array($setAPIs); // make array
}
foreach ($setAPIs as $setAPIType) {
if (in_array($setAPIType, $this->listOfAPIs)) {
$this->APIs->{$setAPIType} = new $setAPIType;
}
}
return $this->APIs;
}
I have a class similar to this
class x {
function __construct($file){
$this->readData = new splFileObject($file);
}
function a (){
//do something with $this->readData;
}
function b(){
//do something with $this->readData;
}
}
$o = new x('example.txt');
echo $o->a(); //this works
echo $o->b(); //this does not work.
it seems if which ever method called first only works, if they are called together only the first method that is called will work. I think the problem is tied to my lack of understand how the new object gets constructed.
The construct is loaded into the instance of the class. And you're instantiating it only once. And accessing twice. Are different actions. If you want to read the file is always taken, should create a method that reads this file, and within all other trigger this method.
I tested your code and it worked normal. I believe it should look at the logs and see if any error appears. If the file does not exist your code will stop.
Find for this error in your apache logs:
PHP Fatal error: Uncaught exception 'RuntimeException' with message 'SplFileObject::__construct(example.txt): failed to open stream
Answering your comment, this can be a way:
<?php
class x {
private $defaultFile = "example.txt";
private function readDefaultFile(){
$file = $this->defaultFile;
return new splFileObject($file);
}
function a (){
$content = $this->readDefaultFile();
return $content ;
}
function b(){
$content = $this->readDefaultFile();
return $content ;
}
}
$o = new x();
echo $o->a();
echo $o->b();
Both methods will return an object splFile.
What would be a good way (along with any pros and cons) of initializing an instance of a PHP class with another object of the same class (ideally in PHP 4.x)?
Here in initialize() is essentially what I'd like to be able to do (example is extremely simplified from my use-case, see below):
$product = new Product('Widget');
$product2 = new Product('Widget #2');
$product->initialize($product2);
echo $product->name; // echos "Widget #2"
class Product {
var $name;
function __constructor($name) {
$this->name = $name;
}
function initialize($product) {
// I know this cannot be done this way in PHP.
// What are the alternatives and their pros & cons?
$this = $product;
}
}
I know this may not be "good programming practice"; with 20+ years programming experience on other languages I know a bit about what's good and what's not. So hopefully we won't get hung up on if doing this makes sense or not. I have a use-case working with some open-source code that I can't change so please just bear with me on my need for it. I'm actually trying to create an OOP wrapper around some really ugly array code buried deep in the core of WordPress.
I'm trying to write it so in future versions they can move away from the ugly array-based code because everyone will be using the new API that otherwise fully encapsulated these nasty arrays. But to make it work elegantly I need to be able to do the above (in PHP 4.x) and I don't want to write code that just copies the properties.
Thanks in advance for your help.
UPDATE
Many of you are suggesting clone but unless I misunderstand that doesn't address the question. clone makes a copy; that's not the crux of the question. I'm instead trying to get the constructed object to "become" the object passed in. At this point I'm assuming there isn't a way to do that based on the fact that 0 out of 5 answers have suggested anything but I'll wait a bit longer before selecting a best in case it was simply that my questions was unclear.
In PHP 5, object cloning might be more relevant:
http://php.net/manual/en/language.oop5.cloning.php
You can define a special __clone method.
In PHP 4 and 5, you can copy properties via:
function copy($obj)
{
foreach (get_object_vars($obj) as $key => $val)
{
$this->$key = $val;
}
}
However, you wrote "I don't want to write code that just copies the properties," and I'm not exactly sure what you mean by that.
Preferred way of doing this is to use clone keyword and to implement appropriate __clone() method if needed as mentioned by other posters. Another trick way of doing this (con: slow, pros: can be stored, sent over network and works identical in php4/5) is to serialize an object and then unserialize to create new copies of it with identical variable values.
Example:
$productCopy = unserialize(serialize($product));
EDIT: Sorry, misunderstood what you were asking for. You will have to initialize variables of the object being constructed with passed in object's variables inside of the constructor. You can't return a reference to another object from the constructor.
Example:
public function __construct($name, $object = null) {
if($object) {
foreach(get_object_vars($object) as $k => $v) {
$this->$k = $v;
}
} else {
$this->name = $name;
}
}
class Product {
var $name;
function __construct($value) {
if (is_a($value, 'Product')) {
$this->name = $value->name;
} else {
$this->name = $value;
}
}
}
Similarly, you can use instanceof instead of is_a if you prefer (depending on your PHP version).
Now you can pass a Product instance OR a name to the construct.
$product = new Product('Something');
$clone = new Product($product);
This is the best way of doing it so far:
http://www.blrf.net/howto/51_PHP__How_to_control_object_instances_in_PHP_.html
Don't use "new", instead use a static function that returns the instance you want.
I have done the following:
class MyClass {
private static $_instances;
public static function get($id) {
if (!self::$_instances) self::$_instances = Array();
$class = get_called_class();
if (!array_key_exists($class, self::$_instances)) self::$_instances[$class] = Array();
if (!is_numeric($id) || $id == '') throw new Exception('Cannot instantiate a non-numeric ID.');
if (array_key_exists($id, self::$_instances[$class])) return self::$_instances[$class][$id];
else {
self::$_instances[$class][$id] = new static($id);
return self::$_instances[$class][$id];
}
}
function __construct($id=false) {
// new instance code...
// I use $id=false to create new a db table row not load an old one
}
}
Usage:
// New instance
$a = new MyClass();
$a = new MyClass;
// MyClass with $id = 1
$b = MyClass::get(1);
$c = MyClass::get(1);
$d = new MyClass(1);
$b and $c point to the same object, while $d is a new one.
Caveats:
Garbage collection will no longer apply as your instances are stored in a static array
You'll have to change your code to use MyClass::get
Notes in my code:
New instances are called with "new static" instead of "new self" to use late static bindings.
You can set your constructor to private. This will break all your old code if you use "new", but will ensure you don't get double instances or more. You'll have to change a bit in the get function's arguments and code to allow $id=false or $id=-1 or whatever.
maybe
$product = new Product('Widget');
$product2 = new Product(null, $product);
echo $product2->name; // echos "Widget #2"
class Product {
var $name;
function __constructor($name, $product = null) {
$this->name = !empty($name) ? $name : $product->name;
}
}
Adding another answer due to it being radically different.
$product = new Product('Widget');
$product2 = new Product('Widget #2');
$product =& $product2;
echo $product->name; // echos "Widget #2"
That should work.
I am trying to assign a variable to a class in PHP, however I am not getting any results?
Can anyone offer any assistance? The code is provided below. I am trying to echo the URL as shown below, by first assigning it to a class variable.
class PageClass {
var $absolute_path = NULL;
function get_absolute_path(){
$url = $this->absolute_path;
echo $url;
}
}
$page = new PageClass();
$page->absolute_path = "http://localhost:8888/smile2/organic/";
$page->get_absolute_path(); //this should echo the URL as defined above - but does not
It also works for me.
Take a look at a live example of your code here.
However, there are a few things you should change about your class.
First, Garvey does make a good point that you should not be using var. That's the older PHP4, less OOP conscious version. Rather declare each variable public or private. In fact, you should declare each function public or private too.
Generally, most classes have private variables, since you usually only want to change the variables in specific ways. To achieve this control you usually set several public methods to allow client functions to interact with your class only in restricted predetermined ways.
If you have a getter, you'd probably want a setter, since these are usually used with private variables, like I described above.
A final note is that functions named get usually return a value. If you want to display a value, it is customary to use a name like display_path or show_path:
<?php
class PageClass
{
private $absolute_path = NULL;
public function set_absolute_path($path)
{
$this->absolute_path = $path;
}
public function display_absolute_path()
{
echo $this->absolute_path;
}
}
$page = new PageClass();
$page->set_absolute_path("http://localhost:8888/smile2/organic/");
$page->display_absolute_path();
// The above outputs: http://localhost:8888/smile2/organic/
// Your variable is now safe from meddling.
// This:
// echo $this->absolute_path;
// Will not work. It will create an error like:
// Fatal error: Cannot access private property PageClass::$absolute_path on ...
?>
Live Example Here
There's a section on classes and objects in the online PHP reference.
class PageClass {
public $absolute_path = NULL;
function get_absolute_path(){
$url = $this->absolute_path;
return $url;
}
}
$page = new PageClass();
$page->absolute_path = "http://localhost:8888/smile2/organic/";
echo $page->get_absolute_path();
Works fine for me.
Have you checked that the script and esp. the code in question is executed at all?
E.g. add some unconditional debug-output to the script. Or install a debugger like XDebug to step through the code and inspect variables.
<?php
class PageClass {
var $absolute_path = NULL; // old php4 declaration, see http://docs.php.net/oop5
function get_absolute_path() { // again old php4 declaration
$url = $this->absolute_path;
echo "debug: "; var_dump($url);
echo $url;
}
}
$page = new PageClass();
$page->absolute_path = "http://localhost:8888/smile2/organic/";
echo "debug: page->get_absolute_path\n";
$page->get_absolute_path();