Your best bet is to use two different design patterns, singleton and static proxy
First we'll set up our existing interface and service (I added a __toString for demo purposes)
interface MyInterface
{
public function set(array $data): bool;
}
class MyService implements MyInterface
{
protected $data;
public function set(array $data): bool
{
try
{
$this->data = $data;
return true;
}
catch (\Throwable $th)
{
return false;
}
}
public function __toString()
{
return json_encode($this->data, JSON_PRETTY_PRINT);
}
}
The first piece of the puzzle, a static singleton factory. All this really does is hold a single instance of our service class. When you call getInstance, the instance is returned. We create the instance the first time the method is called.
/**
* Manage a singleton instance of MyService
*/
class MyServiceSingletonFactory
{
/**
* @var MyService|null Singleton instance
*/
private static ?MyService $instance;
/**
* @var bool initialization status
*/
private static bool $initialized = false;
/**
* Return the singleton instance, create if it has not been initialized
* @return MyService
*/
public static function getInstance(): MyService
{
/*
* We can't access a typed static property before it's initialized,
* so we need to track init with a boolean instead of relying on instanceof
*/
if (!self::$initialized)
{
self::$instance = new MyService();
self::$initialized = true;
}
return self::$instance;
}
}
This is all we really need in order to get static access to a service instance, but for maximum convenience, we can add a static proxy layer.
This pattern has become known as a facade because Laravel calls what are essentially static proxies facades, but traditionally the facade pattern has been used to provide a simplified interface to a complex set of classes, and this isn't that.
We'll implement an interface to enforce parity with MyInterface, although it isn't strictly necessary.
/**
* This isn't really necessary because we don't pass instances of these,
* but it does enforce the implementation of a uniform interface
*/
interface MyServiceProxyInterface
{
public static function set(array $data): bool;
public static function print(): string;
}
/**
* Provides a static interface to MyService
*/
class MyServiceProxy implements MyServiceProxyInterface
{
/**
* Pass through to the set method on the singleton instance of MyService
* @param array $data
* @return bool
*/
public static function set(array $data): bool
{
return MyServiceSingletonFactory::getInstance()->set($data);
}
public static function print(): string
{
return MyServiceSingletonFactory::getInstance()->__toString();
}
}
Now we have everything we need, let's run this code through its paces.
$data = [
'foo' => 'bar',
'wombats' => true
];
MyServiceSingletonFactory::getInstance()->set($data);
// Verify that our data is set
echo MyServiceSingletonFactory::getInstance() . PHP_EOL;
// Uses the same instance, will print the data we set previously
echo MyServiceProxy::print() . PHP_EOL;
$data2 = [
'foo' => 'baz',
'wombats' => false
];
// Update via the static proxy
MyServiceProxy::set($data2);
// Inspect updated data
echo MyServiceProxy::print() . PHP_EOL;
/**
* Requires an instance of MyService
* @param MyService $myService
* @return void
*/
function useMyService(MyService $myService): void
{
$myService->set([
'foo' => 'tree',
'wombats' => null
]);
}
// We can send the instance to something that requires a MyService instance...
useMyService(MyServiceSingletonFactory::getInstance());
// ...and our static singleton instance will reflect any changes made
echo MyServiceProxy::print() . PHP_EOL;