NotionCommotion Posted July 12, 2019 Share Posted July 12, 2019 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 Quote Link to comment Share on other sites More sharing options...
gizmola Posted July 12, 2019 Share Posted July 12, 2019 How about just using serialize() and unserialize(). This is what php session handling does. Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted July 12, 2019 Author Share Posted July 12, 2019 Hi Gizmola, looking to emulate the full object with its PDO connection, methods, etc. Would be great if [un]serialize() did but I expect it does not. Quote Link to comment Share on other sites More sharing options...
requinix Posted July 13, 2019 Share Posted July 13, 2019 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. Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted July 13, 2019 Author Share Posted July 13, 2019 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? Quote Link to comment Share on other sites More sharing options...
requinix Posted July 13, 2019 Share Posted July 13, 2019 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. Quote Link to comment Share on other sites More sharing options...
kicken Posted July 13, 2019 Share Posted July 13, 2019 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. Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted July 13, 2019 Author Share Posted July 13, 2019 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. Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted July 13, 2019 Author Share Posted July 13, 2019 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. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.