Jump to content

Creating a digital twin of an object


NotionCommotion

Recommended Posts

I have a class which is injected with various objects and those objects contain other objects where some of them contain content from various APIs, user data, etc.  I would like to obtain a persistent copy of one of these sub-objects so I may modify my class and test it without having to recreate it each time.  Anyway existing tools to automate this process?  One thought is using reflection to access all the properties and recursively go over each which somehow creates some JSON file which can act as the input to some factory class to create the same instance?  Or maybe some custom function which somehow creates some executable which reflects the bits and bytes in the PHP object?  Thanks

Link to comment
Share on other sites

You cannot serialize (which is what you need to do) resources like database connections.

Which is why methods like __sleep/wakeup and serialize/unserialize exist: for you to customize the behavior before, during, and after the serialization process. So you could serialize the information needed to restore a connection once the object unserializes.

Link to comment
Share on other sites

13 hours ago, requinix said:

You cannot serialize (which is what you need to do) resources like database connections.

Which is why methods like __sleep/wakeup and serialize/unserialize exist: for you to customize the behavior before, during, and after the serialization process. So you could serialize the information needed to restore a connection once the object unserializes.

Ah, so I can do much of what I wished with [un]serialize.  I had no idea, and had previously thought they were just similar to json_encode/json_decode.  This topic is brand new to me and I have never looked into _sleep() and __wakeup()  or the Serializable interface.  Any recommendations on where to start learning about it?

 

 

Link to comment
Share on other sites

The official docs. It's not like these are particularly complicated.

Go for Serializable and serialize/unserialize. They work with serialized strings, so there's more work than with __sleep's array, but the principle is the same: serialize all the data you need plus the information needed to recreate the connection (and not the connection itself), then you recreate the connection when unserializing.

Link to comment
Share on other sites

As an aside that's related to your end goal, if you want to be able to cache/specify the state of external resources for testing, then the ideal thing to do is make sure you have a service that is responsible for fetching those resources, then create a mock version of that service that can return what you want instead of actually fetching the resource.

For example, if you're contacting an API that returns the current weather and want to use that to get the current temperature you're code may look like:

interface WeatherService {
    public function getCurrentConditions(string $location) : WeatherConditions;
}

class WeatherConditions {
    public $currentTemperature;
    public $windSpeed;

    public function __construct($temp, $wind){
        $this->currentTemperature = $temp;
        $this->windSpeed = $wind;
    }
}


class MyController {
    private $weatherService;

    public function __construct(WeatherService $weather){
        $this->weatherService = $weather;
    }

    public function whatIsTheWeather(){
        $conditions = $this->weatherService->getCurrentConditions($_GET['location']);

        return sprintf('It is currently %0.2f degrees outside with wind moving at %0.2f mph'
            , $conditions->currentTemperature, $conditions->windSpeed);
    }
}


For your production code, you'd have an implementation of WeatherService that reaches out to your API endpoint to get the actual current conditions for the given location.

For testing however, you can make a simple mock object that returns static data and inject that instead of your real service. For example:

class MockWeatherService implements WeatherService {
    private $conditions = [];

    public function __construct(){
        $this->conditions = [
            'london' => new WeatherConditions(72,7)
            , 'new york' => new WeatherConditions(82, 5)
            , 'san diego' => new WeatherConditions(67, 10)
            , 'paris' => new WeatherConditions(7, 12)
        ];
    }
    
    public function getCurrentConditions(string $location) : WeatherConditions{
        if (isset($this->conditions[$location])){
            return $this->conditions[$location];
        }
        
        throw new \RuntimeException('Location not found');
    }
}

That way you don't need do anything particularly special in your code to be able to test it with static data.  Instead all you do is adjust your dependency injection configuration to inject the mock service instead of the real service.

 

Link to comment
Share on other sites

Wow, I was totally unaware I could so easily persist an object with methods.  I think being able to do so will provide much opportunity which I previously thought wasn't even an option.

Per the docs, there are many built-in PHP objects which cannot be serialized.  Is there a list?

I see that closure too is not allowed as the following closure injected into some sub-object resulted in error:   function(int $rpcId){unset($this->stack[$rpcId]);}

Also, while all the same instance, I have a PDO instance referenced by many sub-objects as well as multiple instances of PDOStatement (this is a server app and maybe I am wrong but I felt it would be advantageous to create the prepared statements in the constructor so they do not need to be recreated.  Valid?).

It appears that I might want to add __sleep and __wake to each of my classes and unset PDO upon sleep and restore upon wake.  Is this the purpose of these two magic methods and they should actually be used with serialize/unserialize and not instead?  For closure, I guess I can just rewrite my classes to instead use a small class (other options?).

But what can be done for objects which contains PDO or closurer which have been injected into my object that were created by a 3rd party class imported via composer?

Quote

 

Note:

Note that many built-in PHP objects cannot be serialized. However, those with this ability either implement the Serializable interface or the magic __sleep() and __wakeup() methods. If an internal class does not fulfill any of those requirements, it cannot reliably be serialized.

 

 

Link to comment
Share on other sites

1 hour ago, kicken said:

As an aside that's related to your end goal, if you want to be able to cache/specify the state of external resources for testing, then the ideal thing to do is make sure you have a service that is responsible for fetching those resources, then create a mock version of that service that can return what you want instead of actually fetching the resource.

That way you don't need do anything particularly special in your code to be able to test it with static data.  Instead all you do is adjust your dependency injection configuration to inject the mock service instead of the real service.

Thanks kicken,  I think I need to restructure a few things so I can better implement this approach.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.