Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
matrix:
php:
- 8.1
- 8.2

steps:
- name: Checkout code
Expand Down
12 changes: 6 additions & 6 deletions composer.lock

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

11 changes: 11 additions & 0 deletions src/MapperConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ class MapperConfig
*/
public bool $allowUninitializedFields = true;

/**
* This configures how the class mapper cache key is generated.
* Using a different key type can make sure the mapper does not fail when the class changes, however it can be a bit
* slower than other types.
* - fqcn: creates a hash based on the namespaced class name but does not check for changes
* - md5: creates an md5 hash from the file. This will change if the class changes, but is also slower than fqcn
* - modified: creates an md5 hash from the file's modification time.
* @phpstan-var 'fqcn'|'md5'|'modified'
*/
public string $classCacheKeySource = 'fqcn';

/**
* This is the directly where generated mappers will be stored.
* It is recommended to prune this directory on every deploy to prevent old mappers from being used.
Expand Down
9 changes: 9 additions & 0 deletions src/Objects/ObjectMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Jerodev\DataMapper\Types\DataType;
use Jerodev\DataMapper\Types\DataTypeCollection;
use Jerodev\DataMapper\Types\DataTypeFactory;
use ReflectionClass;

class ObjectMapper
{
Expand Down Expand Up @@ -46,6 +47,14 @@ public function map(DataType|string $type, array|string $data): ?object
}

$functionName = self::MAPPER_FUNCTION_PREFIX . \md5($class);
if ($this->mapper->config->classCacheKeySource === 'md5' || $this->mapper->config->classCacheKeySource === 'modified') {
$reflection = new ReflectionClass($class);
$functionName = match ($this->mapper->config->classCacheKeySource) {
'md5' => self::MAPPER_FUNCTION_PREFIX . \md5_file($reflection->getFileName()),
'modified' => self::MAPPER_FUNCTION_PREFIX . \md5(\filemtime($reflection->getFileName())),
};
}

$fileName = $this->mapperDirectory() . \DIRECTORY_SEPARATOR . $functionName . '.php';
if (! \file_exists($fileName)) {
\file_put_contents(
Expand Down
52 changes: 52 additions & 0 deletions tests/Objects/ObjectMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,58 @@

class ObjectMapperTest extends TestCase
{
/** @test */
public function it_should_generate_cache_key_based_on_config(): void
{
$classname = 'A' . \uniqid();
$fqcn = '\Jerodev\DataMapper\Tests\_Mocks\\' . $classname;
$filename = __DIR__ . '/../_Mocks/' . $classname . '.php';
$classContent = <<<PHP
<?php

namespace Jerodev\DataMapper\Tests\_Mocks;

final class {$classname}
{
public int \$id;
}
PHP;

\file_put_contents($filename, $classContent);

$config = new MapperConfig();
$config->classMapperDirectory = '{$TMP}/' . $classname;

$mapper = new Mapper($config);
$result = $mapper->map($fqcn, ['id' => '8']);
$this->assertSame(8, $result->id);

// Calling the mapper again should reuse the mapper function
$mapper->map($fqcn, ['id' => '10']);
$mapper->map($fqcn, ['id' => '11']);
$mapper->map($fqcn, ['id' => '0']);
$this->assertCount(1, \glob(\sys_get_temp_dir() . '/' . $classname . '/*'));

// Changing the file content should also not change the mapper function
\file_put_contents($filename, \str_replace('final ', '', $classContent));
$mapper->map($fqcn, ['id' => 8]);
$this->assertCount(1, \glob(\sys_get_temp_dir() . '/' . $classname . '/*'));

// Changing to md5 cache key should create a new mapper
$config->classCacheKeySource = 'md5';
$result = $mapper->map($fqcn, ['id' => '65536']);
$this->assertSame(65536, $result->id);
$this->assertCount(2, \glob(\sys_get_temp_dir() . '/' . $classname . '/*'));

// Changing the file contents should now also create a new mapper
\file_put_contents($filename, \str_replace('class', 'final class', $classContent));
$mapper->map($fqcn, ['id' => 8]);
$this->assertCount(3, \glob(\sys_get_temp_dir() . '/' . $classname . '/*'));

// Cleanup afterward
\unlink($filename);
}

/** @test */
public function it_should_let_classes_map_themselves(): void
{
Expand Down