Skip to content

Commit

Permalink
[5.x] Augmentation performance improvements (#9636)
Browse files Browse the repository at this point in the history
Co-authored-by: Jason Varga <jason@pixelfear.com>
  • Loading branch information
JohnathonKoster and jasonvarga authored May 2, 2024
1 parent 38ad724 commit d0f29dd
Show file tree
Hide file tree
Showing 24 changed files with 407 additions and 45 deletions.
6 changes: 6 additions & 0 deletions src/Contracts/Data/Augmentable.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@

interface Augmentable extends \JsonSerializable
{
public function augmented(): Augmented;

public function augmentedValue($key);

public function toAugmentedArray($keys = null);

public function toDeferredAugmentedArray($keys = null);

public function toDeferredAugmentedArrayUsingFields($keys, $fields);

public function toAugmentedCollection($keys = null);

public function toShallowAugmentedArray();
Expand Down
8 changes: 8 additions & 0 deletions src/Contracts/Data/BulkAugmentable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Statamic\Contracts\Data;

interface BulkAugmentable
{
public function getBulkAugmentationReferenceKey(): ?string;
}
89 changes: 72 additions & 17 deletions src/Data/AbstractAugmented.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ abstract class AbstractAugmented implements Augmented
protected $data;
protected $blueprintFields;
protected $relations = [];
private $fieldtype;

public function __construct($data)
{
Expand All @@ -36,39 +37,53 @@ public function select($keys = null)
$keys = $this->filterKeys(Arr::wrap($keys ?: $this->keys()));

foreach ($keys as $key) {
$arr[$key] = $this->get($key);
$arr[$key] = $this->transientValue($key);
}

return (new AugmentedCollection($arr))->withRelations($this->relations);
}

private function transientValue($key)
{
$fields = $this->blueprintFields();

$callback = function (Value $value) use ($key, $fields) {
$this->fieldtype = $fields->get($key)?->fieldtype();
$deferred = $this->get($key);
$this->fieldtype = null;

$value->setFieldtype($deferred->fieldtype());
$value->setAugmentable($deferred->augmentable());

return $deferred->raw();
};

return new Value($callback, $key);
}

abstract public function keys();

public function get($handle): Value
{
$method = Str::camel($handle);

if ($this->methodExistsOnThisClass($method)) {
$value = $this->$method();

return $value instanceof Value
? $value
: new Value($value, $method, null, $this->data);
}

if (method_exists($this->data, $method) && collect($this->keys())->contains(Str::snake($handle))) {
return $this->wrapValue($this->data->$method(), $handle);
$value = $this->wrapAugmentedMethodInvokable($method, $handle);
} elseif (method_exists($this->data, $method) && collect($this->keys())->contains(Str::snake($handle))) {
$value = $this->wrapDataMethodInvokable($method, $handle);
} else {
$value = $this->wrapDeferredValue($handle);
}

return $this->wrapValue($this->getFromData($handle), $handle);
return $value->resolve();
}

protected function filterKeys($keys)
private function filterKeys($keys)
{
return array_diff($keys, $this->excludedKeys());
}

protected function excludedKeys()
private function excludedKeys()
{
return Statamic::isApiRoute()
? config('statamic.api.excluded_keys', [])
Expand All @@ -93,19 +108,52 @@ protected function getFromData($handle)
return $value;
}

protected function wrapValue($value, $handle)
private function wrapDeferredValue($handle)
{
$fields = $this->blueprintFields();
return new Value(
fn () => $this->getFromData($handle),
$handle,
$this->fieldtype($handle),
$this->data
);
}

private function wrapAugmentedMethodInvokable(string $method, string $handle)
{
return new Value(
fn () => $this->$method(),
$handle,
null,
$this->data,
);
}

private function wrapDataMethodInvokable(string $method, string $handle)
{
return new Value(
fn () => $this->data->$method(),
$handle,
$this->fieldtype($handle),
$this->data
);
}

protected function wrapValue($value, $handle)
{
return new Value(
$value,
$handle,
optional($fields->get($handle))->fieldtype(),
$this->fieldtype($handle),
$this->data
);
}

protected function blueprintFields()
private function fieldtype($handle)
{
return $this->fieldtype ?? optional($this->blueprintFields()->get($handle))->fieldtype();
}

public function blueprintFields()
{
if (! isset($this->blueprintFields)) {
$this->blueprintFields = (method_exists($this->data, 'blueprint') && $blueprint = $this->data->blueprint())
Expand All @@ -116,6 +164,13 @@ protected function blueprintFields()
return $this->blueprintFields;
}

public function withBlueprintFields($fields)
{
$this->blueprintFields = $fields;

return $this;
}

public function withRelations($relations)
{
$this->relations = $relations;
Expand Down
12 changes: 12 additions & 0 deletions src/Data/AugmentedCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ public function withoutEvaluation()
return $this;
}

public function all()
{
return collect($this->items)
->map(fn ($item) => $item instanceof Value ? $item->resolve() : $item)
->all();
}

public function deferredAll()
{
return parent::all();
}

public function toArray()
{
return $this->map(function ($value) {
Expand Down
111 changes: 111 additions & 0 deletions src/Data/BulkAugmentor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

namespace Statamic\Data;

use Statamic\Contracts\Data\Augmentable;
use Statamic\Contracts\Data\BulkAugmentable;

class BulkAugmentor
{
private $isTree = false;
private $originalValues = [];
private $augmentedValues = [];

private function getAugmentationReference($item)
{
if ($item instanceof BulkAugmentable && $key = $item->getBulkAugmentationReferenceKey()) {
return $key;
}

return 'Ref::'.get_class($item).spl_object_hash($item);
}

public static function make($items)
{
return (new static)->augment($items);
}

public static function tree($tree)
{
return (new static)->augmentTree($tree);
}

/**
* @param array<Augmentable> $items
* @return $this
*/
private function augment($items)
{
$count = count($items);

$referenceKeys = [];
$referenceFields = [];

for ($i = 0; $i < $count; $i++) {
$item = $items[$i];
$reference = $this->getAugmentationReference($item);

if (! $this->isTree) {
$this->originalValues[$i] = $item;
}

if (array_key_exists($reference, $referenceKeys)) {
continue;
}

$augmented = $item->augmented();
$referenceKeys[$reference] = $augmented->keys();
$referenceFields[$reference] = $augmented->blueprintFields();
}

for ($i = 0; $i < $count; $i++) {
$item = $items[$i];
$reference = $this->getAugmentationReference($item);
$fields = $referenceFields[$reference];
$keys = $referenceKeys[$reference];

$this->augmentedValues[$i] = $item->toDeferredAugmentedArrayUsingFields($keys, $fields);
}

return $this;
}

private function augmentTree($tree)
{
$this->isTree = true;

if (! $tree) {
return $this;
}

$items = [];

for ($i = 0; $i < count($tree); $i++) {
$item = $tree[$i];

$items[] = $item['page'];
$this->originalValues[$i] = $item;
}

return $this->augment($items);
}

public function map(callable $callable)
{
$items = [];

for ($i = 0; $i < count($this->originalValues); $i++) {
$original = $this->originalValues[$i];
$augmented = $this->augmentedValues[$i];

$items[] = call_user_func_array($callable, [$original, $augmented, $i]);
}

return collect($items);
}

public function toArray()
{
return $this->augmentedValues;
}
}
20 changes: 18 additions & 2 deletions src/Data/HasAugmentedInstance.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,34 @@ public function augmentedValue($key)
return $this->augmented()->get($key);
}

public function toAugmentedCollection($keys = null)
private function toAugmentedCollectionWithFields($keys, $fields = null)
{
return $this->augmented()
->withRelations($this->defaultAugmentedRelations())
->withBlueprintFields($fields)
->select($keys ?? $this->defaultAugmentedArrayKeys());
}

public function toAugmentedCollection($keys = null)
{
return $this->toAugmentedCollectionWithFields($keys);
}

public function toAugmentedArray($keys = null)
{
return $this->toAugmentedCollection($keys)->all();
}

public function toDeferredAugmentedArray($keys = null)
{
return $this->toAugmentedCollectionWithFields($keys)->deferredAll();
}

public function toDeferredAugmentedArrayUsingFields($keys, $fields)
{
return $this->toAugmentedCollectionWithFields($keys, $fields)->deferredAll();
}

public function toShallowAugmentedCollection()
{
return $this->augmented()->select($this->shallowAugmentedArrayKeys())->withShallowNesting();
Expand All @@ -39,7 +55,7 @@ public function toShallowAugmentedArray()
return $this->toShallowAugmentedCollection()->all();
}

public function augmented()
public function augmented(): Augmented
{
return $this->runHooks('augmented', $this->newAugmentedInstance());
}
Expand Down
16 changes: 14 additions & 2 deletions src/Entries/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Statamic\Contracts\Auth\Protect\Protectable;
use Statamic\Contracts\Data\Augmentable;
use Statamic\Contracts\Data\Augmented;
use Statamic\Contracts\Data\BulkAugmentable;
use Statamic\Contracts\Data\Localization;
use Statamic\Contracts\Entries\Entry as Contract;
use Statamic\Contracts\Entries\EntryRepository;
Expand Down Expand Up @@ -43,7 +44,6 @@
use Statamic\Facades\Collection;
use Statamic\Facades\Site;
use Statamic\Facades\Stache;
use Statamic\Fields\Value;
use Statamic\GraphQL\ResolvesValues;
use Statamic\Revisions\Revisable;
use Statamic\Routing\Routable;
Expand All @@ -53,7 +53,7 @@
use Statamic\Support\Str;
use Statamic\Support\Traits\FluentlyGetsAndSets;

class Entry implements Arrayable, ArrayAccess, Augmentable, ContainsQueryableValues, Contract, Localization, Protectable, ResolvesValuesContract, Responsable, SearchableContract
class Entry implements Arrayable, ArrayAccess, Augmentable, BulkAugmentable, ContainsQueryableValues, Contract, Localization, Protectable, ResolvesValuesContract, Responsable, SearchableContract
{
use ContainsComputedData, ContainsData, ExistsAsFile, FluentlyGetsAndSets, HasAugmentedInstance, Localizable, Publishable, Revisable, Searchable, TracksLastModified, TracksQueriedColumns, TracksQueriedRelations;

Expand All @@ -78,6 +78,7 @@ class Entry implements Arrayable, ArrayAccess, Augmentable, ContainsQueryableVal
protected $withEvents = true;
protected $template;
protected $layout;
private $augmentationReferenceKey;
private $computedCallbackCache;
private $siteCache;

Expand All @@ -92,6 +93,17 @@ public function id($id = null)
return $this->fluentlyGetOrSet('id')->args(func_get_args());
}

public function getBulkAugmentationReferenceKey(): ?string
{
if ($this->augmentationReferenceKey) {
return $this->augmentationReferenceKey;
}

$dataPart = implode('|', $this->data->keys()->sort()->all());

return $this->augmentationReferenceKey = 'Entry::'.$this->blueprint()->namespace().'::'.$dataPart;
}

public function locale($locale = null)
{
return $this
Expand Down
Loading

0 comments on commit d0f29dd

Please sign in to comment.