1
0
mirror of https://github.com/flextype/flextype.git synced 2025-08-06 05:07:41 +02:00

feat(entries): improvements for entries fetch logic

- fetch method decomposed into smaller private methods.
- improvements and bug fixes for nested entries fetch logic.
- improvements for nested fetch requests tests.
- updates for fixtures
This commit is contained in:
Awilum
2022-05-03 22:20:28 +03:00
parent 19772c6d9a
commit 9065dba5c4
17 changed files with 260 additions and 223 deletions

View File

@@ -209,227 +209,251 @@ class Entries
{
// Setup registry.
$this->registry()->set('methods.fetch', [
'collection' => $this->getCollectionOptions($id),
'params' => [
'id' => $id,
'options' => $options,
],
'result' => null,
]);
'collection' => $this->getCollectionOptions($id),
'params' => [
'id' => $id,
'options' => $options,
],
'result' => null,
]);
// Run event
emitter()->emit('onEntriesFetch');
// Check if `result` contains data to return then return existing result.
if (! is_null($this->registry()->get('methods.fetch.result'))) {
return $this->registry()->get('methods.fetch.result');
}
// Fetch collection or single
if ($this->registry()->has('methods.fetch.params.options.collection') &&
strings($this->registry()->get('methods.fetch.params.options.collection'))->isTrue()) {
return $this->fetchCollection($this->registry()->get('methods.fetch.params.id'),
$this->registry()->get('methods.fetch.params.options'));
} else {
return $this->fetchSingle($this->registry()->get('methods.fetch.params.id'),
$this->registry()->get('methods.fetch.params.options'));
}
}
/**
* Fetch single.
*
* @param string $id Unique identifier of the entry.
* @param array $options Options array.
*
* @return mixed Returns mixed results from APIs or default is an instance of The Collection class with founded items.
*
* @access public
*/
private function fetchSingle(string $id, array $options = [])
{
// Setup registry.
$this->registry()->set('methods.fetch', [
'collection' => $this->getCollectionOptions($id),
'params' => [
'id' => $id,
'options' => $options,
],
'result' => null,
]);
// Run event
emitter()->emit('onEntriesFetchSingle');
// Check if `result` contains data to return.
if (! is_null($this->registry()->get('methods.fetch.result'))) {
return $this->registry()->get('methods.fetch.result');
}
// Single fetch helper
$single = function ($id, $options) {
// Get Cache ID for current requested entry
$entryCacheID = $this->getCacheID($this->registry()->get('methods.fetch.params.id'), 'single');
// 1. Try to get current requested entry from the cache.
if (cache()->has($entryCacheID)) {
// Setup registry.
$this->registry()->set('methods.fetch', [
'collection' => $this->getCollectionOptions($id),
'params' => [
'id' => $id,
'options' => $options,
],
'result' => null,
]);
// Fetch entry from cache and apply `filterCollection` filter for fetch result
$this->registry()->set('methods.fetch.result',
filterCollection(cache()->get($entryCacheID),
$this->registry()->get('methods.fetch.params.options.filter', [])));
// Run event
emitter()->emit('onEntriesFetchSingle');
// Check if `result` contains data to return.
if (! is_null($this->registry()->get('methods.fetch.result'))) {
return $this->registry()->get('methods.fetch.result');
}
// Get Cache ID for current requested entry
$entryCacheID = $this->getCacheID($this->registry()->get('methods.fetch.params.id'), 'single');
emitter()->emit('onEntriesFetchSingleCacheHasResult');
// 1. Try to get current requested entry from the cache.
if (cache()->has($entryCacheID)) {
// Fetch entry from cache and apply `filterCollection` filter for fetch result
$this->registry()->set('methods.fetch.result',
filterCollection(cache()->get($entryCacheID),
$this->registry()->get('methods.fetch.params.options.filter', [])));
// Return result from the cache.
return collection($this->registry()->get('methods.fetch.result'));
}
// 2. Try to get requested entry from the filesystem
if ($this->has($this->registry()->get('methods.fetch.params.id'))) {
// Run event
emitter()->emit('onEntriesFetchSingleCacheHasResult');
// Return result from the cache.
return collection($this->registry()->get('methods.fetch.result'));
}
// Get entry file location
$entryFile = $this->getFileLocation($this->registry()->get('methods.fetch.params.id'));
// 2. Try to get requested entry from the filesystem
if ($this->has($this->registry()->get('methods.fetch.params.id'))) {
// Get entry file location
$entryFile = $this->getFileLocation($this->registry()->get('methods.fetch.params.id'));
// Try to get requested entry from the filesystem.
$entryFileContent = filesystem()->file($entryFile)->get(true);
if ($entryFileContent === false) {
// Run event
emitter()->emit('onEntriesFetchSingleNoResult');
return collection($this->registry()->get('methods.fetch.params.result'));
}
// Decode entry file content
$this->registry()->set('methods.fetch.result', serializers()->{$this->registry()->get('methods.fetch.collection')['serializer']}()->decode($entryFileContent));
// Try to get requested entry from the filesystem.
$entryFileContent = filesystem()->file($entryFile)->get(true);
if ($entryFileContent === false) {
// Run event
emitter()->emit('onEntriesFetchSingleHasResult');
// Get the result.
$result = $this->registry()->get('methods.fetch.result');
// Apply `filterCollection` filter for fetch result
$this->registry()->set('methods.fetch.result', filterCollection($result, $this->registry()->get('methods.fetch.params.options.filter', [])));
// Set cache state
$cache = $this->registry()->get('methods.fetch.result.cache.enabled',
registry()->get('flextype.settings.cache.enabled'));
// Save entry data to cache
if ($cache) {
cache()->set($entryCacheID, $this->registry()->get('methods.fetch.result'));
}
// Return entry fetch result
return collection($this->registry()->get('methods.fetch.result'));
emitter()->emit('onEntriesFetchSingleNoResult');
return collection($this->registry()->get('methods.fetch.params.result'));
}
// Decode entry file content
$this->registry()->set('methods.fetch.result', serializers()->{$this->registry()->get('methods.fetch.collection')['serializer']}()->decode($entryFileContent));
// Run event
emitter()->emit('onEntriesFetchSingleNoResult');
emitter()->emit('onEntriesFetchSingleHasResult');
// Get the result.
$result = $this->registry()->get('methods.fetch.result');
// Apply `filterCollection` filter for fetch result
$this->registry()->set('methods.fetch.result', filterCollection($result, $this->registry()->get('methods.fetch.params.options.filter', [])));
// Set cache state
$cache = $this->registry()->get('methods.fetch.result.cache.enabled',
registry()->get('flextype.settings.cache.enabled'));
// Save entry data to cache
if ($cache) {
cache()->set($entryCacheID, $this->registry()->get('methods.fetch.result'));
}
// Return entry fetch result
return collection($this->registry()->get('methods.fetch.result'));
};
}
if ($this->registry()->has('methods.fetch.params.options.collection') &&
strings($this->registry()->get('methods.fetch.params.options.collection'))->isTrue()) {
// Run event
emitter()->emit('onEntriesFetchSingleNoResult');
// Setup registry.
$this->registry()->set('methods.fetch', [
'collection' => $this->getCollectionOptions($id),
'params' => [
'id' => $id,
'options' => $options,
],
'result' => null,
]);
// Return entry fetch result
return collection($this->registry()->get('methods.fetch.result'));
}
/**
* Fetch collection.
*
* @param string $id Unique identifier of the entry.
* @param array $options Options array.
*
* @return mixed Returns mixed results from APIs or default is an instance of The Collection class with founded items.
*
* @access public
*/
private function fetchCollection(string $id, array $options = [])
{
// Setup registry.
$this->registry()->set('methods.fetch', [
'collection' => $this->getCollectionOptions($id),
'params' => [
'id' => $id,
'options' => $options,
],
'result' => null,
]);
emitter()->emit('onEntriesFetchCollection');
// Check if `result` contains data to return.
if (! is_null($this->registry()->get('methods.fetch.result'))) {
return $this->registry()->get('methods.fetch.result');
}
// Determine if collection exists
if (! $this->has($this->registry()->get('methods.fetch.params.id'))) {
// Run event
emitter()->emit('onEntriesFetchCollection');
// Check if `result` contains data to return.
if (! is_null($this->registry()->get('methods.fetch.result'))) {
return $this->registry()->get('methods.fetch.result');
}
// Determine if collection exists
if (! $this->has($this->registry()->get('methods.fetch.params.id'))) {
// Run event
emitter()->emit('onEntriesFetchCollectionNoResult');
// Return entries array
return collection($this->registry()->get('methods.fetch.result'));
}
// Find entries in the filesystem.
$entries = find($this->getDirectoryLocation($this->registry()->get('methods.fetch.params.id')),
$this->registry()->has('methods.fetch.params.options.find') ?
$this->registry()->get('methods.fetch.params.options.find') :
[]);
// Walk through entries results
if ($entries->hasResults()) {
$data = [];
foreach ($entries as $currenEntry) {
$currentEntryID = strings($currenEntry->getPath())
->replace('\\', '/')
->replace(PATH['project'] . $this->options['directory'] . '/', '')
->trim('/')
->toString();
// Set collection options
$this->registry()->set('methods.fetch.collection', $this->getCollectionOptions($currentEntryID));
if ($currenEntry->getType() !== 'file' || $currenEntry->getFilename() !== $this->registry()->get('methods.fetch.collection.filename') . '.' . $this->registry()->get('methods.fetch.collection.extension')) {
continue;
}
$data[$currentEntryID] = $single($currentEntryID, [])->toArray();
}
// Re-init registry.
$this->registry()->set('methods.fetch', [
'collection' => $this->getCollectionOptions($id),
'params' => [
'id' => $id,
'options' => $options,
],
'result' => $data,
]);
// Run event
emitter()->emit('onEntriesFetchCollectionHasResult');
// Process filter `only` for collection
// after process we need to unset $options['filter']['only']
// to avoid it's running inside filterCollection() helper.
if ($this->registry()->has('methods.fetch.params.options.filter.only')) {
$data = [];
foreach ($this->registry()->get('methods.fetch.result') as $key => $value) {
$data[$key] = collection($value)->only($this->registry()->get('methods.fetch.params.options.filter.only'))->toArray();
}
$this->registry()->delete('methods.fetch.params.options.filter.only');
$this->registry()->set('methods.fetch.result', $data);
}
// Process filter `except` for collection
// after process we need to unset $options['filter']['except']
// to avoid it's running inside filterCollection() helper.
if ($this->registry()->has('methods.fetch.params.options.filter.except')) {
$data = [];
foreach ($this->registry()->get('methods.fetch.result') as $key => $value) {
$data[$key] = collection($value)->except($this->registry()->get('methods.fetch.params.options.filter.except'))->toArray();
}
$this->registry()->delete('methods.fetch.params.options.filter.except');
$this->registry()->set('methods.fetch.result', $data);
}
// Apply filter `filterCollection` for fetch result data.
$this->registry()->set('methods.fetch.result',
filterCollection($this->registry()->get('methods.fetch.result'),
$this->registry()->has('methods.fetch.params.options.filter') ?
$this->registry()->get('methods.fetch.params.options.filter') : []));
return collection($this->registry()->get('methods.fetch.result'));
} else {
// Run event
emitter()->emit('onEntriesFetchCollectionNoResult');
// Return entries array
return collection($this->registry()->get('methods.fetch.result'));
}
emitter()->emit('onEntriesFetchCollectionNoResult');
// Return entries array
return collection($this->registry()->get('methods.fetch.result'));
}
// Fetch single entry.
return $single($id, $options);
// Find entries in the filesystem.
$entries = find($this->getDirectoryLocation($this->registry()->get('methods.fetch.params.id')),
$this->registry()->has('methods.fetch.params.options.find') ?
$this->registry()->get('methods.fetch.params.options.find') :
[]);
// Walk through entries results
if ($entries->hasResults()) {
$data = [];
foreach ($entries as $currenEntry) {
$currentEntryID = strings($currenEntry->getPath())
->replace('\\', '/')
->replace(PATH['project'] . $this->options['directory'] . '/', '')
->trim('/')
->toString();
// Set collection options
$this->registry()->set('methods.fetch.collection', $this->getCollectionOptions($currentEntryID));
if ($currenEntry->getType() !== 'file' || $currenEntry->getFilename() !== $this->registry()->get('methods.fetch.collection.filename') . '.' . $this->registry()->get('methods.fetch.collection.extension')) {
continue;
}
$data[$currentEntryID] = $this->fetchSingle($currentEntryID, [])->toArray();
}
// Re-init registry after single fetch calls.
$this->registry()->set('methods.fetch', [
'collection' => $this->getCollectionOptions($id),
'params' => [
'id' => $id,
'options' => $options,
],
'result' => $data,
]);
// Run event
emitter()->emit('onEntriesFetchCollectionHasResult');
// Process filter `only` for collection
// after process we need to unset $options['filter']['only']
// to avoid it's running inside filterCollection() helper.
if ($this->registry()->has('methods.fetch.params.options.filter.only')) {
$data = [];
foreach ($this->registry()->get('methods.fetch.result') as $key => $value) {
$data[$key] = collection($value)->only($this->registry()->get('methods.fetch.params.options.filter.only'))->toArray();
}
$this->registry()->delete('methods.fetch.params.options.filter.only');
$this->registry()->set('methods.fetch.result', $data);
}
// Process filter `except` for collection
// after process we need to unset $options['filter']['except']
// to avoid it's running inside filterCollection() helper.
if ($this->registry()->has('methods.fetch.params.options.filter.except')) {
$data = [];
foreach ($this->registry()->get('methods.fetch.result') as $key => $value) {
$data[$key] = collection($value)->except($this->registry()->get('methods.fetch.params.options.filter.except'))->toArray();
}
$this->registry()->delete('methods.fetch.params.options.filter.except');
$this->registry()->set('methods.fetch.result', $data);
}
// Apply filter `filterCollection` for fetch result data.
$this->registry()->set('methods.fetch.result',
filterCollection($this->registry()->get('methods.fetch.result'),
$this->registry()->has('methods.fetch.params.options.filter') ?
$this->registry()->get('methods.fetch.params.options.filter') : []));
return collection($this->registry()->get('methods.fetch.result'));
} else {
// Run event
emitter()->emit('onEntriesFetchCollectionNoResult');
// Return entries array
return collection($this->registry()->get('methods.fetch.result'));
}
// Return entries array
return collection($this->registry()->get('methods.fetch.result'));
}
/**

View File

@@ -52,17 +52,17 @@ emitter()->addListener('onEntriesFetchSingleHasResult', static function (): void
break;
}
// Modify
// Modify data
foreach (entries()->registry()->get('methods.fetch.result.entries.fetch') as $field => $body) {
$result = isset($body['result']) && in_array($body['result'], ['toArray', 'toObject']) ? $body['result'] : $resultTo;
$result = isset($body['result']) && in_array($body['result'], ['toArray', 'toObject']) ? $body['result'] == 'toObject' ? 'copy' : 'toArray' : $resultTo;
$data[$field] = entries()->fetch($body['id'], isset($body['options']) ? $body['options'] : []);
$data[$field] = ($data[$field] instanceof Collection) ? $data[$field]->{$result}() : $data[$field];
}
$result = collection($original['result'])->merge($data)->toArray();
// Remove entries field
if (boolval(entries()->registry()->get('methods.fetch.collection.fields.entries.dump')) === false) {
// Remove entries field when dump === false
if (boolval(arrays($result)->get('entries.dump', entries()->registry()->get('methods.fetch.collection.fields.entries.dump'))) === false) {
unset($result['entries']);
}

View File

@@ -1,8 +0,0 @@
title: Macroable
entries:
fetch:
table:
id: table
options:
method: fetchExtraData
hello: world

View File

@@ -1,7 +1,9 @@
title: Bikes
entries:
dump: false
fetch:
items:
result: toArray
id: shop/catalog/bikes
options:
collection: true

View File

@@ -1,7 +1,9 @@
title: GT
entries:
dump: false
fetch:
items:
result: toArray
id: shop/catalog/bikes/gt
options:
collection: true

View File

@@ -1,8 +1,10 @@
title: GT
price: 2000
entries:
entries:
dump: false
fetch:
discounts_available:
result: toArray
id: shop/discounts
options:
collection: true

View File

@@ -1,8 +1,10 @@
title: Sensor
price: 1700
entries:
dump: false
fetch:
discounts_available:
result: toArray
id: shop/discounts
options:
collection: true

View File

@@ -1,7 +1,9 @@
title: Norco
entries:
dump: false
fetch:
items:
result: toArray
id: shop/catalog/bikes/norco
options:
collection: true

View File

@@ -1,8 +1,10 @@
title: Rage
price: 2100
entries:
dump: false
fetch:
discounts_available:
result: toArray
id: shop/discounts
options:
collection: true

View File

@@ -1,7 +1,10 @@
title: Catalog
entries:
dump: false
fetch:
bikes:
result: toArray
id: shop/catalog/bikes
snowboards:
result: toArray
id: shop/catalog/snowboards

View File

@@ -1,7 +1,9 @@
title: Burton
entries:
dump: false
fetch:
items:
result: toArray
id: shop/catalog/snowboards/burton
options:
collection: true

View File

@@ -1,8 +1,10 @@
title: Burton Family Tree Hometown Hero Camber Splitboard
price: 849.95
entries:
dump: false
fetch:
discounts_available:
result: toArray
id: shop/discounts
options:
collection: true

View File

@@ -1,7 +1,9 @@
title: Snowboards
entries:
dump: false
fetch:
items:
result: toArray
id: shop/catalog/snowboards
options:
collection: true

View File

@@ -1,7 +1,9 @@
title: Discounts
entries:
dump: false
fetch:
items:
result: toArray
id: shop/discounts
options:
collection: true

View File

@@ -1,7 +1,10 @@
title: Shop
entries:
dump: false
fetch:
catalog:
id: shop/catalog
result: toArray
discounts:
id: shop/discounts
id: shop/discounts
result: toArray

View File

@@ -57,9 +57,11 @@ test('fetch entry', function () {
expect(entries()->create('foo', []))->toBeTrue();
expect(entries()->create('foo/bar', []))->toBeTrue();
expect(entries()->create('foo/zed', []))->toBeTrue();
expect(entries()->fetch('foo'))->toBeInstanceOf(Arrays::class);
expect(count(entries()->fetch('foo')->toArray()) > 0)->toBeTrue();
expect(count(entries()->fetch('foo')->toArray()))->toEqual(10);
expect(count(entries()->fetch('foo', ['collection' => false])->toArray()))->toEqual(10);
expect(count(entries()->fetch('foo', ['collection' => true])->toArray()))->toEqual(2);
});

View File

@@ -16,10 +16,11 @@ test('EntriesField for blog', function () {
entries()->create('blog/post-2', serializers()->frontmatter()->decode(filesystem()->file(ROOT_DIR . '/tests/fixtures/entries/blog/post-2/post.md')->get()));
$blog = entries()->fetch('blog');
$posts = entries()->fetch('blog', ['collection' => true]);
$this->assertEquals(11, $blog->count());
})->skip();
$this->assertEquals(2, $posts->count());
});
test('EntriesField for shop', function() {
filesystem()
@@ -27,7 +28,12 @@ test('EntriesField for shop', function() {
->copy(ROOT_DIR . '/project/entries/shop');
$shop = entries()->fetch('shop');
})->skip();
$this->assertEquals('Shop', $shop['title']);
$this->assertEquals('Catalog', $shop['catalog']['title']);
$this->assertEquals('Bikes', $shop['catalog']['bikes']['title']);
$this->assertEquals('Discounts', $shop['discounts']['title']);
});
test('EntriesField for catalog', function () {
@@ -70,7 +76,7 @@ test('EntriesField for catalog', function () {
$banner = entries()->fetch('banner');
$this->assertEquals('Banner', $banner['title']);
$this->assertEquals('banner', $banner['id']);
})->skip();
});
test('EntriesField for albmus', function () {
entries()->create('root', serializers()->yaml()->decode(filesystem()->file(ROOT_DIR . '/tests/fixtures/entries/root/entry.yaml')->get()));
@@ -86,7 +92,7 @@ test('EntriesField for albmus', function () {
$root = entries()->fetch('root');
$this->assertEquals(15, $root->count());
})->skip();
});
test('EntriesField for long nested entries', function () {
entries()->create('level1', serializers()->yaml()->decode(filesystem()->file(ROOT_DIR . '/tests/fixtures/entries/level1/entry.yaml')->get()));
@@ -100,17 +106,4 @@ test('EntriesField for long nested entries', function () {
$this->assertEquals('level1/level2', $level['root']['id']);
$this->assertEquals('level1/level2/level3', $level['root']['root']['id']);
$this->assertEquals('level1/level2/level3/level4', $level['root']['root']['root']['id']);
})->skip();
test('EntriesField for macroable fetch entries', function () {
entries()->create('macroable', serializers()->yaml()->decode(filesystem()->file(ROOT_DIR . '/tests/fixtures/entries/macroable/entry.yaml')->get()));
entries()::macro('fetchExtraData', function ($id, $options) {
return ['id' => $id, 'options' => $options];
});
$macroable = entries()->fetch('macroable');
$this->assertEquals('table', $macroable['table']['id']);
$this->assertEquals('world', $macroable['table']['options']['hello']);
})->skip();
});