Skip to content

Commit

Permalink
Merge pull request #14 from microsoft/dev
Browse files Browse the repository at this point in the history
Release 0.4.1
  • Loading branch information
Ndiritu authored Nov 14, 2022
2 parents 1f49fb4 + 018dd34 commit 520c696
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 63 deletions.
4 changes: 2 additions & 2 deletions src/Store/BackingStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function get(string $key);
* Will trigger subscription callbacks
*
* @param string $key The key to store and retrieve information.
* @param mixed|null $value The value to be $associated with the given key.
* @param mixed $value The value to be $associated with the given key.
*/
public function set(string $key, $value): void;

Expand All @@ -37,7 +37,7 @@ public function enumerateKeysForValuesChangedToNull(): iterable;

/**
* Creates a subscription to any data change happening.
* @param callable $callback Callback to be invoked on data changes where the first parameter is the data key, the second the previous value and the third the new value.
* @param callable(string, mixed, mixed):void $callback Callback to be invoked on data changes where the first parameter is the data key, the second the previous value and the third the new value.
* @param string|null $subscriptionId
* @return string The subscription ID to use when removing the subscription
*/
Expand Down
11 changes: 10 additions & 1 deletion src/Store/BackingStoreFactorySingleton.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ abstract class BackingStoreFactorySingleton
*/
private static ?BackingStoreFactory $instance = null;

/**
* One time initialisation of the backing store factory
*
* @param BackingStoreFactory $backingStoreFactory
*/
public static function setInstance(BackingStoreFactory $backingStoreFactory): void {
self::$instance = self::$instance ?? $backingStoreFactory;
}

/**
* We use the getter method since PHP doesn't support instantiating an instance
* outside a method.
Expand All @@ -22,4 +31,4 @@ public static function getInstance(): ?BackingStoreFactory {
}
return self::$instance;
}
}
}
22 changes: 6 additions & 16 deletions src/Store/BackingStoreParseNodeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,14 @@ class BackingStoreParseNodeFactory extends ParseNodeProxyFactory{
public function __construct(ParseNodeFactory $concrete) {
parent::__construct($concrete,
static function ($x) {
if (is_a($x, BackedModel::class)) {
$backedModel = $x;
$backingStore = $backedModel->getBackingStore();

if (!is_null($backingStore)) {
$backingStore->setIsInitializationCompleted(false);
}
}
if ($x instanceof BackedModel && $x->getBackingStore()) {
$x->getBackingStore()->setIsInitializationCompleted(false);
}
},
static function ($x) {
if (is_a($x, BackedModel::class)) {
$backedModel = $x;
$backingStore = $backedModel->getBackingStore();

if (!is_null($backingStore)) {
$backingStore->setIsInitializationCompleted(true);
}
}
if ($x instanceof BackedModel && $x->getBackingStore()) {
$x->getBackingStore()->setIsInitializationCompleted(true);
}
}
);
}
Expand Down
31 changes: 8 additions & 23 deletions src/Store/BackingStoreSerializationWriterProxyFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,28 @@ class BackingStoreSerializationWriterProxyFactory extends SerializationWriterPro
*/
public function __construct(SerializationWriterFactory $concreteSerializationWriterFactory){
$onBeforeObjectSerialization = static function (Parsable $model) {
if (is_a($model, BackedModel::class)) {
$backedModel = $model;
$backingStore = $backedModel->getBackingStore();
if ($backingStore !== null) {
$backingStore->setReturnOnlyChangedValues(true);
}
if ($model instanceof BackedModel && $model->getBackingStore()) {
$model->getBackingStore()->setReturnOnlyChangedValues(true);
}
};

$onAfterObjectSerialization = static function (Parsable $model) {
if (is_a($model, BackedModel::class)) {
$backedModel = $model;
$backingStore = $backedModel->getBackingStore();

if ($backingStore !== null) {
$backingStore->setReturnOnlyChangedValues(false);
$backingStore->setIsInitializationCompleted(true);
}
if ($model instanceof BackedModel && $model->getBackingStore()) {
$model->getBackingStore()->setReturnOnlyChangedValues(false);
$model->getBackingStore()->setIsInitializationCompleted(true);
}
};

$onStartObjectSerialization = static function (Parsable $model, SerializationWriter $serializationWriter) {
if (is_a($model, BackedModel::class)) {
$backedModel = $model;

$backingStore = $backedModel->getBackingStore();

if ($backingStore !== null) {
$keys = $backingStore->enumerateKeysForValuesChangedToNull();
if ($model instanceof BackedModel && $model->getBackingStore()) {

$keys = $model->getBackingStore()->enumerateKeysForValuesChangedToNull();
foreach ($keys as $key) {
$serializationWriter->writeNullValue($key);
}
}
}
};
parent::__construct($concreteSerializationWriterFactory, $onBeforeObjectSerialization,
$onAfterObjectSerialization, $onStartObjectSerialization);
}
}
}
81 changes: 60 additions & 21 deletions src/Store/InMemoryBackingStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,26 @@ class InMemoryBackingStore implements BackingStore
{

private bool $isInitializationCompleted = true;
private bool $returnOnlyChangedValues;
private bool $returnOnlyChangedValues = false;

/**
* @var array<string,array|mixed|array<string,mixed>> $store;
* @var array<string, array<mixed>> $store
*/
private array $store = [];

/** @var array<string, callable> $subscriptionStore */
private array $subscriptionStore = [];

/**
* @param string $key
* @return mixed
*/
public function get(string $key) {
$wrapper = (array)($this->store[$key] ?? null);
$this->checkCollectionSizeChanged($key);
$wrapper = $this->store[$key] ?? null;
if (is_null($wrapper)) {
return null;
}
return $this->getValueFromWrapper($wrapper);
}

Expand All @@ -32,33 +37,49 @@ public function get(string $key) {
*/
public function set(string $key, $value): void
{
$valueToAdd = [$this->isInitializationCompleted, $value];
$this->store[$key] = $valueToAdd;
$oldValue = $this->store[$key];
$oldValue = $this->store[$key] ?? null;
$valueToAdd = is_array($value) ? [$this->isInitializationCompleted, $value, count($value)] : [$this->isInitializationCompleted, $value];

// Dirty track changes if $value is a model and its properties change
if (!array_key_exists($key, $this->store)) {
if ($value instanceof BackedModel && $value->getBackingStore()) {
$value->getBackingStore()->subscribe(fn ($propertyKey, $oldVal, $newVal) => $this->set($key, $value));
}
if (is_array($value)) {
array_map(function ($item) use ($key, $value) {
if ($item instanceof BackedModel && $item->getBackingStore()) {
$item->getBackingStore()->subscribe(fn ($propertyKey, $oldVal, $newVal) => $this->set($key, $value));
}
}, $value);
}
}

$this->store[$key] = $valueToAdd;
foreach ($this->subscriptionStore as $callback) {
$callback($key, $oldValue[1], $value);
$callback($key, $oldValue[1] ?? null, $value);
}
}

/**
* @return array<string,mixed>
* Enumerate the values in the store based on $returnOnlyChangedValues
*
* @return array<string, mixed> key value pairs
*/
public function enumerate(): array {
$result = [];

foreach ($this->store as $key => $value) {
$value = (array)$value;
$val = $this->getValueFromWrapper($value);

if ($val === null) {
$this->checkCollectionSizeChanged($key);
if (!$this->returnOnlyChangedValues || $value[0]) {
$result[$key] = $value[1];
}
}
return $result;
}

/**
* Adds a callback to subscribe to events in the store
*
* @param callable $callback
* @param string|null $subscriptionId
* @return string
Expand All @@ -72,14 +93,16 @@ public function subscribe(callable $callback, ?string $subscriptionId = null): s
}

/**
* De-register the callback with the given subscriptionId
*
* @param string $subscriptionId
*/
public function unsubscribe(string $subscriptionId): void {
unset($this->subscriptionStore[$subscriptionId]);
}

/**
*
* Empties the store
*/
public function clear(): void {
$this->store = [];
Expand Down Expand Up @@ -114,31 +137,47 @@ public function getReturnOnlyChangedValues(): bool {
}

/**
* Returns a list of keys in the store that have changed to null
*
* @return iterable<string>
*/
public function enumerateKeysForValuesChangedToNull(): iterable {
$result = [];

foreach ($this->store as $key => $val) {
if (is_array($val) && $val[1] === null && $val[0]) {
if ($val[1] === null && $val[0]) {
$result []= $key;
}
}
return $result;
}

/**
* @param array<mixed>|null $wrapper
* @return mixed|null
* Returns value from $wrapper based on $returnOnlyChangedValues configuration
*
* @param array<mixed> $wrapper
* @return mixed
*/
public function getValueFromWrapper(?array $wrapper) {
if (!is_array($wrapper)) {
return null;
}
private function getValueFromWrapper(array $wrapper) {
$hasChangedValue = $wrapper[0];
if (!$this->returnOnlyChangedValues || $hasChangedValue) {
return $wrapper[1];
}
return null;
}
}

/**
* Checks if collection of values has changed in size. If so, dirty tracks the change by calling set()
*
* @param string $key
* @return void
*/
private function checkCollectionSizeChanged(string $key): void {
$wrapper = $this->store[$key] ?? null;
if ($wrapper && is_array($wrapper[1])) {
if (sizeof($wrapper[1]) != $wrapper[2]) {
$this->set($key, $wrapper[1]);
}
}
}
}
Loading

0 comments on commit 520c696

Please sign in to comment.