I'm running into a problem when passing an object across different classes and trying to have only one instance of it instead of multiple clones.
TLDR version:
If I have objects A->B->C, where C gets passed A by way of B as a parameter on creation, will C->A->B access the original B that had created it, or a copy of that B? How many copies of B are there in the system memory now?
Slightly more detailed version:
Let's say I have a (perhaps overly convoluted) nested class structure for handling a server-based request. The first step is to instantiate an object of class Session, and then within it, create an object $handler of class Handler. However, as $handler will need to have access to the internal attributes of $session (and multiple other objects created within it, like $user or $database, whose purposes ought to be self-explanatory), I then pass it on as a parameter:
class Session {
public $handler;
public function __construct() {
$this->handler = new Handler( $this );
//DO STUFF HERE
}
}
The Handler class inherits the session like this:
class Handler {
private $session;
public function __construct( Session $inherited_session ) {
$this->session = $inherited_session;
}
}
Side note: $session is set to private to avoid even the slightest chance of infinite loops down the line, of the $this->session->handler->session->handler variety.
Now, according to my understanding and all the research I've done, PHP passes objects by reference, so the $this->session object within this Handler ought to be accessing the same object in the system memory as the original session? Not a copy of itself?
Yet here's my problem. Suppose now I create a third-level nested object within the Handler class, of class Dashboard, and want to pass the original $session to it (not, mind it, just the Handler object itself). Off we go, so we put this somewhere within the Handler class:
$dashboard = new Dashboard( $this->session );
The Dashboard constructor inherits the session in exactly the same way as Handler did:
class Dashboard {
private $session;
public function __construct( Session $inherited_session ) {
$this->session = $inherited_session;
}
}
However, it doesn't seem able to access the instance of Handler that had called it, and by now it appears that we have multiple copies of $session and $handler floating about - and I'd very much like to understand why, because it contradicts everything I understand about references.
Here's an example of this pathological behaviour - say we have a variable in Handler:
public $temp_var;
that the constructor of Handler assigns a value:
$this->temp_var = '123';
and then we try accessing it from within the Dashboard class as $this->session->handler->temp_var. That returns NULL. Why? Did $dashboard inherit a copy of $session on initialisation that doesn't have an initialised ->handler to call on? How can I make it so there is only one (unique) object of each class, and updating the internal (public) variables of $handler would get correctly passed on to $dashboard->session->handler? Or am I just making some obvious / idiotic mistake somewhere and completely not seeing it?
Note #1: any variable set in $session as $this->var is correctly accessible from $dashboard->session->var, so double-level nesting works as expected; it's triple-level one that doesn't.
Note #2: I've already thought of simply passing $handler as a parameter to all of its nested objects along with $session (and if no solution exists, this is what I'll have to do), but it doesn't solve the original problem of $this->session->handler being somehow and inexplicably different from the original $handler within its nested objects.
Oh, and my thanks to anyone who managed to read through all this!
As far as I understand, you're dealing with composition not with inheritance here.
So, you have a Session which is passed to a Handler, the Handler and Dashboard both "know" the session through composition (keeping a reference on the private variable).
I don't understand why do you need this sort of circular reference, but if you want to access the Handler from Dashboard, why not passing the Handler to it?
Besides that, it looks like you are storing the handler in a local scoped variable (I've been away from PHP the last two years, but...)
class Session {
public function __construct() {
// isnt $this->handler = new Handler( $this ) ??
$handler = new Handler( $this );
}
}
From a conceptual point of view, there's not "nesting", only references, so I dont think that the "three levels" does anything to do with that.
Hope it helps!
Here's an example I've modified the Session and privacy just to verify that the $session is still the same
Related
In my current application I have a number of objects that are required repeatedly
To save overhead of instantiating the same object over and over again, I keep an array of 'known' objects.
I can check the input data against the array, and - if already set - use the existing object, else go ahead to instantiate the new object and add the pointer reference to the relevant known objects array.
In most use cases, I can check prior to instantiating the class:
if(array_key_exists($identifier,$known_ClassObjects)){
$object = $known_ClassObjects[$identifier];
} else {
$object = new Class($params);
}
However, in some cases I can only identify that the object I am instantiating already exists once already inside it.
In that case I would like to be able to do one of 2 things:
return the OTHER (pre-existing) object instead of this one, e.g.
class Test{
public function __construct($params){
//apply processing to $params, resulting in $identifier
if(array_key_exists($identifier, $known_ClassObjects)){ //$known_ClassObjects is global
return $known_ClassObjects[$identifier];
} else {
//continue __construct() logic
return $this;
}
}
}
However, PHP ALWAYS returns the current object, even with code return $other_object;
'Internally Clone' the current object from the found one [of the same class, obv] so that when it returns, it has the correct relevant properties populated.
NOTE: including any parent/child class properties
-> I want to make this object EXACTLY the same as the found one.
So, if there was a PHP function clone_from(), it would work something like:
if(array_key_exists($identifier,$known_ClassObjects)){
$this->clone_from ($known_ClassObjects[$identifier]);
} else {
//continue with __construct()
}
return $this;
Given that 1. doesn't work and 2. doesn't seem to exist, I have only been able to do this in very 'hacky' ways: iterating through all properties of source object and setting all properties of current object.
However, this has obvious issues esp. with extended parent/child classes etc. which then requires things like reflection classes.
This seems like there SHOULD be a really simple solution, but I have been unable to find one
What you actually could do is using either a Singleton Pattern or a Factory pattern - in both cases, the creation of objects is controlled by some piece of code and you can decide, which object to return. Singleton already is a special form of a Factory pattern.
Consider this code
class Singleton {
protected static $instance;
protected function __construct() {
}
public static function instance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
The constructor is protected which will prevent a object construction from "outside" via new. However, there is also a static function instance with which somebody can request an object instance from the factory method.
$obj = Singleton::instance();
So, the (internal) object is created only once, and then delivered afterwards until the script ends.
However, PHP ALWAYS returns the current object, even with code return $other_object;
Constructor is not a regular function. You do not return from it. If you are in __construct() you are already constructing the new object. Before proceed, I strongly recommend you read at least: https://www.php.net/manual/en/language.oop5.decon.php
You most likely need a helper method or factory that would deal with that instead. Putting the logic you tried to place into your constructor is wrong.
Also you missed to clarify what the $identifier really is. But if that's object's property, then you can expose it (i.e. via getter) and have it read for your comparison/whatever by other code.
I have come across a really clever way of doing this. But I only used it once so I can't remember where I found the actual snippet.
I have a class called;
Worksheet {
private $settings
...
funnction GetSettings (){}
...
}
This class has methods, private properties and may extend from an existing class. At runtime I create an object from the class, "process it" and save it in a session so that it can be used by the visitor in a session. At each request I have to get the object from the session feed it back to my processor so that the object properties can be updated (Usually made of multi-dimensional arrays).
I have a simple method in my Processor class that creates the original prototype and mounts the session object to the prototype.
class Processor {
public function Mount ($worksheetObject){
$this->NewWorksheetPrototype (); //Recreate worksheet from prototype.
require ('includes/mount.php'); //This will go through the provided Object and mount it on to the processor.
}
}
Of course I don't want to go through the entire object recreate the properties which may contain lots of multi-dimensional arrays. Is there a reliable way of doing this? I know I can do this
$obj_merged = (object) array_merge((array) $obj1, (array) $obj2);
...but the methods will not be merged.
It is not possible to save an object in a session with the methods attached? I am working in a Symfony2 environment if this helps.
Provided you're on PHP 5.4 or greater, you could try something like this:
trait MergeableTrait{
protected $mergedObject;
public function merge($object2){
$this->mergedObject;
}
public function __get($name){
if(property_exists($this->mergedObject, $name)){
return $this->mergedObject->{'$name'}; //Pretty sure that's how it works.....
}
throw new RuntimeException("{$name} is not an available property");
}
public function __call($name, $arguments){
if(method_exists($this->mergedObject, $name)){
return call_user_func_array([$this->mergedObject, $name], $arguments);
}
throw new RuntimeException("{$name} is not a callable method");
}
}
I haven't tested it, but it should give you a good idea on how to do this. Provided your concrete classes don't have __call or __get methods defined, you can add the merge behavior like so:
class Worksheet{
use MergeableTrait;
...
}
...or wherever you need it.
Then you can do something like this:
$ws = new Worksheet();
$proc = new Processor();
$ws->merge($proc);
This is not tested, nor would I recommend doing things this way as it makes things much harder to debug
Also keep in mind that this won't take into account the visibility of your methods and properties. I would recommend perhaps expanding the checks to include some basic reflection, but overall, this should give you a head start.
I created a class implementing ArrayAccess and I added it a function to prevents WRITE actions:
$Obj->Add("key","something");
$Obj->Add("key2","something2");
$Obj->SetReadOnly(); // sets read only property
unset($Obj["key2"]); // throws error, object is readonly
But, i want to prevent unsetting object too:
unset($Obj);
I can do it?I hear suggestions.
Thanks for help!.
I can't imagine any situation where you would really want to do this. I can also imagine doing this will cause you serious problems at script termination when all objects are destroyed. The PHP manual says the following on throwing exceptions in destructors:
Note:
Attempting to throw an exception from a destructor (called in the time
of script termination) causes a fatal error.
The above statement implies that you can throw an exception if you're not in the script termination phase, so maybe the following is possible.
public function __destruct ()
{
if ($this -> isReadOnly ())
{
throw new Exception ('Class is read-only');
}
}
However, as the manual points out, this will trigger a fatal error during script shutdown.
I honestly can't see any point to wanting to prevent object destruction. It should be up to the programmer to manage object lifetimes.
unset() does not actually destruct an object, if that's what you're trying to prevent.
An object will only be destructed when all references to it have been unset or are no longer in scope. Even then it won't happen until the garbage collector runs.
So if you have some code that you are worried will molest your object, you've already done a good job of making it immutable with your read-only logic.
Let's say you have
$Obj = gotMyObjectSomehow();
and you need to pass it to a some other code you don't want to unset $Obj. As long as that code is called inside a function, there's nothing to be concerned about. If you call
someFunction($Obj);
and let's say that function unsets the parameter it's passed in
function someFunction($anObj) {
unset($anObj);
}
then your original $Obj variable will still be set.
The function creates a second variable referencing the original object and uses that in its own scope.
You can't control unsetting variable names, because those names are not technically a part of the object referenced. Consider the following:
$a = new MyObject();
$b = $a;
Now you have two references to the same object. There is no difference between using $a and $b, because in PHP objects are always used by reference (i.e. you don't have to do $b =& $a in the second line). So both $a and $b are essentially the same object; unsetting $a will not destroy the object, as well as unsetting $b won't destroy it; all references need to be unset before the object is destroyed.
I don't think you can do what you're asking for - it's not possible to prevent a variable being unset like that.
However, a comment of yours above set me thinking. You said:
.... idea if you want to prevent unsets system variables in a thirdparty extensions
So if I understand you right, your aim here is to ensure that while the thirdparty code (ie your software) is in use, all the variables associated with it remain in place?
Now you haven't specified much about what variables there are in this system. We see one object in the question, but presumably there must be more than that? I'm guessing you've got a bunch of things that tie together, right? It would help in these sorts of questions to provide a bit more context; the actual thing that you're asking for isn't possible, but with a bit of understanding about what you want to achieve, we could come up with alternatives.
Okay. So my suggestion: create your objects as Singletons. This is often frowned on by purists, but might work well for this situation, depending on exactly what you're doing. The beauty here is that you can encapsulate all access to the object inside class methods, meaning that the developer using your code doesn't have access to the master copy of the object in order to unset it.
A singleton works like this:
<?php
class mySingletonClass {
private static $masterObject=null;
public static function getInstance() {
if(!isset(self::$masterObject)) {
self::$masterObject = new self;
}
return self::$masterObject;
}
private function __construct() {
//your existing constructor, as it already exists, but marked as private.
}
//...and all the other methods as you already have them.
}
The class constructor method is private, so can only be accessed from methods within the class. Therefore, you can no longer do new classname(). The only way you can get an object of this class is to get it from the static getInstance() method. And the key thing here is that this method always returns the same copy of the object.
$obj = mySingletonClass::getInstance();
unset($obj);
$obj = mySingletonClass::getInstance(); //will give the exact same object as the first time.
You can unset it if you want, but the original object is still there and can still be accessed. Any of your other classes can use that getInstance() method to get the same copy of the same object from anywhere in the program. It's an indestructible global variable.
Singletons are often used for a program's main database connection object, but it might be a useful pattern for you here.
I hope that helps. It's about the only way I can think of to get close to what you want.
I have a singleton class that I am using as part of a CAPTCHA system to generate the code and image etc.
I have one script included on the html form that loads the class singleton, generates the code and image, and outputs it to the browser. I then have another script to validate and process the form. This loads the class singleton to retrieve the instance that was created previously, and calls the function to validate the code.
The problem I'm having is that when I'm validating the form the code that was generated on the form has changed or is completely absent when I come to validate it!
I haven't started or stored anything in the php session, but a session is created on the page the form is loaded in. Is the instance of the singleton somehow linked to that session? If it's a named session or something?
OR...have I completely misunderstood how singleton classes work? In which case can anyone tell me how I can retrieve the instance of the class that is created on the html form page to use again to validate the code in the form processing script? - And maybe tell me how I should be using singletons!
Many thanks.
Singletons exist for the duration of the request, not the duration of the session.
The idea of a singleton is to provide access to the same object across all included scripts, without having to use any explicit initialisation logic.
So, the first call to $foo = MyObject::singleton() creates a new MyObject, but the second call will simply return that object instead of creating a new one. This is incredibly useful for classes that access external resources such as databases and files.
OR...have I completely misunderstood how singleton classes work?
Partially. Since PHP has no ASP.NET alike application variables, objects in PHP live as long as the request does, unless serialized (for example in a session).
As a solution to your problem: save the captcha code (or the captcha class, which is a bit of overkill imho) in a session variable, like $_SESSION['captcha'].
No, it's not.
You'd have to serialize your singleton object and store it to the session when your code execution ends. When the next page is displayed, you can unserialize the object from your session.
PHP serializes/unserializes objects automatically when you assign them to a session.
This only works correctly under the precondition that your singleton does not use link identifiers to external resources.
Here is an example implementation taken from the comments in PHP docs
class SessionSingleton {
/**
* Returns an instance of the singleton class.
* #return object The singleton instance
*/
public static function _instance()
{
// Start a session if not already started
Session::start();
if ( false == isset( $_SESSION[ self::$_singleton_class ] ) )
{
$class = self::$_singleton_class;
$_SESSION[ self::$_singleton_class ] = new $class;
}
return $_SESSION[ self::$_singleton_class ];
}
/**
* Destroy the singleton object. Deleting the session variable in the
* destructor does not make sense since the destructor is called every
* time the script ends.
*/
public static function _destroy()
{
$_SESSION[ self::$_singleton_class ] = null;
}
/**
* Initialize the singleton object. Use instead of constructor.
*/
public function _initialize( $name )
{
// Something...
}
/**
* Prevent cloning of singleton.
*/
private function __clone()
{
trigger_error( "Cloning a singleton object is not allowed.", E_USER_ERROR );
}
private static $_singleton_class = __CLASS__;
}
I'm currently creating blog system, which I hope to turn into a full CMS in the future.
There are two classes/objects that would be useful to have global access to (the mysqli database connection and a custom class which checks whether a user is logged in).
I am looking for a way to do this without using global objects, and if possible, not passing the objects to each function every time they are called.
You could make the objects Static, then you have access to them anywhere. Example:
myClass::myFunction();
That will work anywhere in the script. You might want to read up on static classes however, and possibly using a Singleton class to create a regular class inside of a static object that can be used anywhere.
Expanded
I think what you are trying to do is very similar to what I do with my DB class.
class myClass
{
static $class = false;
static function get_connection()
{
if(self::$class == false)
{
self::$class = new myClass;
}
return self::$class;
}
// Then create regular class functions.
}
What happens is after you get the connection, using $object = myClass::get_connection(), you will be able to do anything function regularly.
$object = myClass::get_connection();
$object->runClass();
Expanded
Once you do that static declarations, you just have to call get_connection and assign the return value to a variable. Then the rest of the functions can have the same behavior as a class you called with $class = new myClass (because that is what we did). All you are doing is storing the class variable inside a static class.
class myClass
{
static $class = false;
static function get_connection()
{
if(self::$class == false)
{
self::$class = new myClass;
}
return self::$class;
}
// Then create regular class functions.
public function is_logged_in()
{
// This will work
$this->test = "Hi";
echo $this->test;
}
}
$object = myClass::get_connection();
$object->is_logged_in();
You could pass the currently global objects into the constructor.
<?php
class Foo {
protected $m_db;
function __construct($a_db) {
$this->m_db = $a_db;
}
}
?>
I recently revamped my framework in preparation for the second version of our company's CMS. I undid a huge amount of the things I made static in order to replace them with normal objects. In so doing, I created a huge amount of flexibility that used to rely on me going through and hacking into core files. I now only use static constructs when the only alternative is global functions, which is only related to low-level core functionality.
I'm going to show a few lines of my bootstrap.php file (all of my requests get sent through that file, but you can achieve the same result by including it at the top of every file) to show you what I mean. This is an pretty hefty version of what you'd probably use in your situation, but hopefully the idea is helpful. (This is all slightly modified.)
//bootstrap.php
...
// CONSTRUCT APPLICATION
{
$Database = new Databases\Mysql(
Constant::get('DATABASE_HOST'),
Constant::get('DATABASE_USER'),
Constant::get('DATABASE_PASSWORD'),
Constant::get('DATABASE_SCHEMA')
);
$Registry = new Collections\Registry;
$Loader = new Loaders\Base;
$Debugger = new Debuggers\Dummy; // Debuggers\Console to log debugging info to JavaScript console
$Application = new Applications\Base($Database, $Registry, $Loader, $Debugger);
}
...
As you can see, I have all kind of options for creating my application object, which I can provided as an argument in the constructor to other objects to give them access to these "global" necessities.
The database object is self-explanatory. The registry object acts as a container for object I may want to access elsewhere in the application. The loader acts as a utility for loading other resources like template files. And the debugger is there to handle debug output.
I can, for example, change the database class that I instantiate and, voila I have a connection to a SQLite database. I can change the class of the debugger (as noted) and now all of my debug info will be logged to my JavaScript console.
Okay, now back to the issue. How do you give other objects access to all of this? You simply pass it in an argument to the constructor.
// still bootstrap.php
...
// DISPATCH APPLICATION
{
$Router = new Routers\Http($Application);
$Router->routeUri($_SERVER['REQUEST_URI']);
}
...
Not only that, but my Router (or whatever object I construct with it) is more flexible, too. Now I can just instantiate my application object differently, and my Router will behave differently accordingly.
Well, if you already have some object by which you refer to the blog system, you can compose these objects into that, so that they're $blog->db() and $blog->auth() or whatever.