Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Breaking Change] Refactor Caching Logic #3

Merged
merged 12 commits into from
Mar 10, 2024
5 changes: 5 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,35 @@ $cipher = new AsymmetricEncryptionCipher($publicKey, $privateKey);

## Property Readers

SecureProps comes with two implementations of property readers: a runtime one and a decorator for caching.
SecureProps provides two types of property readers to handle encrypted properties within your PHP objects efficiently: `RuntimeObjectPropertiesReader` and `CachingObjectPropertiesReader`.

### RuntimeObjectPropertiesReader

This reader inspects objects at runtime to find properties marked with the `#[Encrypted]` attribute. It uses PHP's reflection capabilities to perform its duties without requiring any caching mechanism.
The `RuntimeObjectPropertiesReader` dynamically examines objects at runtime to identify properties decorated with the `#[Encrypted]` attribute. Utilizing PHP's reflection requires no additional setup for caching and offers straightforward inspection capabilities.

### CachingObjectPropertiesReader

This reader wraps another `ObjectPropertiesReader` implementation and caches the results to improve performance. It's particularly useful for applications that repeatedly process the same object types, reducing the overhead of reflection operations. The `PSR-6` caching standard provides a flexible framework for integrating various caching backends, offering developers the freedom to choose a solution that best fits their application's scaling and performance requirements.
For enhanced performance, especially in applications that frequently deal with the same types of objects, the `CachingObjectPropertiesReader` caches property reading results. This approach reduces the computational overhead associated with reflection.

It integrates seamlessly with `PSR-6` compliant caching solutions, allowing for customizable performance optimization.

#### Quick Start Example

Combining `CachingObjectPropertiesReader` with `RuntimeObjectPropertiesReader` and a `PSR-6` compliant cache implementation:

```php
// Initialize a PSR-6 cache pool
$cache = new FilesystemAdapter(...);

// Configure the caching reader
$reader = new CachingObjectPropertiesReader(
new RuntimeObjectPropertiesReader(),
new ItemPoolCompatibleCache($cache)
);

// Set up the ObjectEncryptionService with the reader
$encryptionService = new ObjectEncryptionService($cipher, $reader);
```

## Contributing

Expand Down
25 changes: 25 additions & 0 deletions src/Cache/Cache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace IlicMiljan\SecureProps\Cache;

use IlicMiljan\SecureProps\Cache\Exception\InvalidCacheKey;
use Psr\Cache\CacheItemInterface;

interface Cache
{
/**
* Fetches a value from the cache or computes it if not found.
*
* @template T
*
* @param string $key The cache key.
* @param (callable(CacheItemInterface):T) $callable A callable that
* computes the value if it's not found in the
* cache.
* @param int|null $ttl The time-to-live for the cache entry in seconds.
*
* @return T The cached or computed value.
* @throws InvalidCacheKey When $key is not valid.
*/
public function get(string $key, callable $callable, ?int $ttl = null): mixed;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace IlicMiljan\SecureProps\Reader\Exception;
namespace IlicMiljan\SecureProps\Cache\Exception;

use LogicException;
use Throwable;
Expand Down
35 changes: 35 additions & 0 deletions src/Cache/ItemPoolCompatibleCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace IlicMiljan\SecureProps\Cache;

use IlicMiljan\SecureProps\Cache\Exception\InvalidCacheKey;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException;

class ItemPoolCompatibleCache implements Cache
{
private CacheItemPoolInterface $cachePool;

public function __construct(CacheItemPoolInterface $cachePool)
{
$this->cachePool = $cachePool;
}

public function get(string $key, callable $callable, ?int $ttl = null): mixed
{
try {
$cacheItem = $this->cachePool->getItem($key);
} catch (InvalidArgumentException $e) {
throw new InvalidCacheKey($key, $e);
}

if (!$cacheItem->isHit()) {
$cacheItem->set($callable($cacheItem));
$cacheItem->expiresAfter($ttl);

$this->cachePool->save($cacheItem);
}

return $cacheItem->get();
}
}
91 changes: 20 additions & 71 deletions src/Reader/CachingObjectPropertiesReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,44 @@

namespace IlicMiljan\SecureProps\Reader;

use IlicMiljan\SecureProps\Reader\Exception\InvalidCacheKey;
use IlicMiljan\SecureProps\Reader\Exception\InvalidCacheValueDataType;
use IlicMiljan\SecureProps\Cache\Cache;
use IlicMiljan\SecureProps\Cache\Exception\InvalidCacheKey;
use IlicMiljan\SecureProps\Reader\Exception\ObjectPropertyNotFound;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException;
use ReflectionException;
use ReflectionObject;
use ReflectionProperty;

class CachingObjectPropertiesReader implements ObjectPropertiesReader
{
private const CACHE_TTL_DEFAULT = null;

public function __construct(
private ObjectPropertiesReader $objectPropertiesReader,
private CacheItemPoolInterface $cache
private Cache $cache,
private ?int $cacheTtl = self::CACHE_TTL_DEFAULT
) {
}

/**
* @throws InvalidCacheKey
* @throws InvalidCacheValueDataType
* @throws ObjectPropertyNotFound
*/
public function getPropertiesWithAttribute(object $object, string $attributeClass): array
{
$cachedProperties = $this->getCacheItem($this->getCacheKey($object, $attributeClass));

if ($cachedProperties->isHit()) {
$this->ensureCacheItemValueIsArray($cachedProperties);

/** @var string[] $cachedPropertiesValue */
$cachedPropertiesValue = $cachedProperties->get();

return $this->loadRuntimeReflectionProperties($object, $cachedPropertiesValue);
}

$propertiesWithAttribute = $this->objectPropertiesReader->getPropertiesWithAttribute(
$object,
$attributeClass
);

$this->updateCache(
$cachedProperties,
$this->getCacheablePropertiesArray($propertiesWithAttribute)
$propertyArray = $this->cache->get(
$this->getCacheKey($object, $attributeClass),
function (CacheItemInterface $cacheItem) use ($object, $attributeClass) {
$propertiesWithAttribute = $this->objectPropertiesReader->getPropertiesWithAttribute(
$object,
$attributeClass
);

return $this->getCacheablePropertiesArray($propertiesWithAttribute);
},
$this->cacheTtl
);

return $propertiesWithAttribute;
return $this->loadRuntimeReflectionProperties($object, $propertyArray);
}

private function getCacheKey(object $object, string $attributeClass): string
Expand All @@ -66,32 +57,15 @@ private function getCacheKey(object $object, string $attributeClass): string
*/
private function loadRuntimeReflectionProperties(object $object, array $propertyNames): array
{
$reflection = new ReflectionObject($object);

try {
return array_map(function ($propertyName) use ($reflection) {
return $reflection->getProperty($propertyName);
return array_map(function ($propertyName) use ($object) {
return new ReflectionProperty($object, $propertyName);
}, $propertyNames);
} catch (ReflectionException $e) {
throw new ObjectPropertyNotFound($object::class, $e);
}
}

/**
* @param string $cacheKey
* @return CacheItemInterface
*
* @throws InvalidCacheKey
*/
private function getCacheItem(string $cacheKey): CacheItemInterface
{
try {
return $this->cache->getItem($cacheKey);
} catch (InvalidArgumentException $e) {
throw new InvalidCacheKey($cacheKey, $e);
}
}

/**
* @param ReflectionProperty[] $properties
* @return string[]
Expand All @@ -102,29 +76,4 @@ private function getCacheablePropertiesArray(array $properties): array
return $property->getName();
}, $properties);
}

/**
* @param CacheItemInterface $cacheItem
* @return void
*
* @throws InvalidCacheValueDataType
*/
private function ensureCacheItemValueIsArray(CacheItemInterface $cacheItem): void
{
$cachedValue = $cacheItem->get();

if (is_array($cachedValue)) {
return;
}

throw new InvalidCacheValueDataType(gettype($cachedValue), 'array');
}

private function updateCache(CacheItemInterface $cacheItem, mixed $data): void
{
$cacheItem->set($data);
$cacheItem->expiresAfter(3600);

$this->cache->save($cacheItem);
}
}
31 changes: 0 additions & 31 deletions src/Reader/Exception/InvalidCacheValueDataType.php

This file was deleted.

Loading
Loading