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

[5.x] Augmentation performance improvements #9636

Merged
merged 45 commits into from May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
fe2c56f
Makes method-backed augmented values lazy/deferrable
JohnathonKoster Feb 22, 2024
1df8569
A tiny, but not nothing difference
JohnathonKoster Feb 22, 2024
af03d7e
Merge branch '4.x' into deferred-method-augmentation
JohnathonKoster Feb 22, 2024
d220d16
🧹
JohnathonKoster Feb 23, 2024
9cf6cd1
Reduce calls to blueprintFields
JohnathonKoster Feb 26, 2024
20c376a
Use deferred augmentation in more places internally
JohnathonKoster Feb 28, 2024
e7eafa9
Delay the resolution of regular values
JohnathonKoster Mar 2, 2024
7b7cfa4
Refactors shared logic across deferred values
JohnathonKoster Mar 2, 2024
b10b6c5
Need to account for relationships
JohnathonKoster Mar 2, 2024
ee66acd
Adds transient values
JohnathonKoster Mar 2, 2024
eedbb88
Refactor select to allow for known fields to be supplied
JohnathonKoster Mar 2, 2024
86d22d3
Adds the Augmentor 50000; uses it inside Antlers augmentation
JohnathonKoster Mar 2, 2024
24d7878
Some key adjustments
JohnathonKoster Mar 2, 2024
477541a
Update nav tag to use bulk augmentor
JohnathonKoster Mar 2, 2024
2ede1a2
Restore a familiar experience to dump, dd, and its friends
JohnathonKoster Mar 2, 2024
f5eee7c
Ensure fieldtype materializes values
JohnathonKoster Mar 3, 2024
9c67cf1
Merge branch 'master' into deferred-all-augmentation
jasonvarga Mar 12, 2024
41aa519
Merge branch 'master' into deferred-all-augmentation
JohnathonKoster Mar 13, 2024
a529524
Merge branch 'master' into fork/deferred-all-augmentation
jasonvarga Apr 17, 2024
f07e257
Merge branch 'master' into deferred-all-augmentation
jasonvarga Apr 30, 2024
c576286
Merge branch 'master' into deferred-all-augmentation
jasonvarga Apr 30, 2024
8b35960
Some nitpicks that help me understand it all a bit better, plus just …
jasonvarga May 1, 2024
6379697
Refactor out the trait by putting resolves into Value
jasonvarga May 1, 2024
018fde1
Just always materialize values. It'll know not to bother doing anythi…
jasonvarga May 1, 2024
1f989dd
Clear this up
jasonvarga May 1, 2024
bcaa0f4
Remove the 3 Value class inheritors by using a closure based solution…
jasonvarga May 1, 2024
015368c
oops
jasonvarga May 1, 2024
8dd6b79
isSelecting is never used since transient values were introduced
jasonvarga May 1, 2024
828e3a5
null coalesce
jasonvarga May 1, 2024
b1ec703
Get rid of materialize and toValue ...
jasonvarga May 1, 2024
1ec54a1
refactor to single fieldtype method
jasonvarga May 1, 2024
3509be9
Avoid passing around fields argument
jasonvarga May 1, 2024
81a8547
docblock for clickthrough fun
jasonvarga May 1, 2024
e3ad099
undo some viz changes
jasonvarga May 1, 2024
d85fac3
not that
jasonvarga May 1, 2024
2811e9c
Get augmented once
jasonvarga May 1, 2024
9fb80bd
avoid passing around a fieldtype
jasonvarga May 1, 2024
db30f96
Nitpicks ...
jasonvarga May 2, 2024
37f13c1
rename
jasonvarga May 2, 2024
5c4e66d
resolve when serializing (so it works in nocache)
jasonvarga May 2, 2024
0a2836a
apply to terms
jasonvarga May 2, 2024
fecfc5b
viz
jasonvarga May 2, 2024
bf5c321
nitpick
jasonvarga May 2, 2024
6bac97b
contract
jasonvarga May 2, 2024
8a5bb26
Nitpick dumper ...
jasonvarga May 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Auth/AugmentedUser.php
Expand Up @@ -39,7 +39,7 @@ private function commonKeys()
];
}

public function get($handle): Value
public function get($handle, $fieldtype = null): Value
{
if ($handle === 'is_user') {
return new Value(true, 'is_user', null, $this->data);
Expand All @@ -57,7 +57,7 @@ public function get($handle): Value
return new Value(in_array(Str::after($handle, 'in_'), $this->groups()), $handle, null, $this->data);
}

return parent::get($handle);
return parent::get($handle, $fieldtype);
}

protected function roles()
Expand Down
8 changes: 8 additions & 0 deletions src/Contracts/Data/BulkAugmentable.php
@@ -0,0 +1,8 @@
<?php

namespace Statamic\Contracts\Data;

interface BulkAugmentable
{
public function getAugmentationReferenceKey(): ?string;
}
90 changes: 74 additions & 16 deletions src/Data/AbstractAugmented.php
Expand Up @@ -13,6 +13,7 @@ abstract class AbstractAugmented implements Augmented
protected $data;
protected $blueprintFields;
protected $relations = [];
protected $isSelecting = false;

public function __construct($data)
{
Expand All @@ -29,38 +30,66 @@ public function except($keys)
return $this->select(array_diff($this->keys(), Arr::wrap($keys)));
}

public function select($keys = null)
public function select($keys = null, $fields = null)
{
$arr = [];

if (! $fields) {
$fields = $this->blueprintFields();
}

$keys = $this->filterKeys(Arr::wrap($keys ?: $this->keys()));

$this->isSelecting = true;

foreach ($keys as $key) {
$arr[$key] = $this->get($key);
$arr[$key] = (new TransientValue(null, $key, null))->withAugmentationReferences($this, $fields->get($key));
}

$this->isSelecting = false;

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

abstract public function keys();

public function get($handle): Value
public function getAugmentedMethodValue($method)
{
if ($this->methodExistsOnThisClass($method)) {
return $this->$method();
}

return $this->data->$method();
}

protected function adjustFieldtype($handle, $fieldtype)
{
if ($this->isSelecting || $fieldtype !== null) {
return $fieldtype;
}

return $this->getFieldtype($handle);
}

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

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

return $value instanceof Value
? $value
: new Value($value, $method, null, $this->data);
$value = $this->wrapInvokable($method, true, $this, $handle, $fieldtype);
} elseif (method_exists($this->data, $method) && collect($this->keys())->contains(Str::snake($handle))) {
$value = $this->wrapInvokable($method, false, $this->data, $handle, $fieldtype);
} else {
$value = $this->wrapDeferredValue($handle, $fieldtype);
}

if (method_exists($this->data, $method) && collect($this->keys())->contains(Str::snake($handle))) {
return $this->wrapValue($this->data->$method(), $handle);
// If someone is calling ->get() directly they probably
// don't want to remember to also ->materialize() it.
if (! $this->isSelecting) {
return $value->materialize();
}

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

protected function filterKeys($keys)
Expand All @@ -80,7 +109,7 @@ private function methodExistsOnThisClass($method)
return method_exists($this, $method) && ! in_array($method, ['select', 'except']);
}

protected function getFromData($handle)
public function getFromData($handle)
{
$value = method_exists($this->data, 'value') ? $this->data->value($handle) : $this->data->get($handle);

Expand All @@ -93,19 +122,48 @@ protected function getFromData($handle)
return $value;
}

protected function wrapValue($value, $handle)
protected function wrapDeferredValue($handle, $fieldtype = null)
{
$fieldtype = $this->adjustFieldtype($handle, $fieldtype);

return (new DeferredValue(
null,
$handle,
$fieldtype,
$this->data
))->withAugmentedReference($this);
}

protected function wrapInvokable(string $method, bool $proxy, $methodTarget, string $handle, $fieldtype = null)
{
$fieldtype = $this->adjustFieldtype($handle, $fieldtype);

return (new InvokableValue(
null,
$handle,
$fieldtype,
$this->data
))->setInvokableDetails($method, $proxy, $methodTarget);
}

protected function wrapValue($value, $handle, $fieldtype = null)
{
$fields = $this->blueprintFields();
$fieldtype = $this->adjustFieldtype($handle, $fieldtype);

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

protected function blueprintFields()
protected function getFieldtype($handle)
{
return 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 Down
23 changes: 23 additions & 0 deletions src/Data/AugmentedCollection.php
Expand Up @@ -47,6 +47,29 @@ public function withoutEvaluation()
return $this;
}

protected function requiresMaterialization($item)
{
return $item instanceof InvokableValue ||
$item instanceof DeferredValue ||
$item instanceof TransientValue;
}

public function all()
{
return collect($this->items)->map(function ($item) {
if ($this->requiresMaterialization($item)) {
return $item->materialize();
}

return $item;
})->all();
}

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

public function toArray()
{
return $this->map(function ($value) {
Expand Down
2 changes: 1 addition & 1 deletion src/Data/AugmentedData.php
Expand Up @@ -20,7 +20,7 @@ public function keys()
return array_keys($this->array);
}

protected function getFromData($handle)
public function getFromData($handle)
{
return $this->array[$handle] ?? null;
}
Expand Down
95 changes: 95 additions & 0 deletions src/Data/BulkAugmentor.php
@@ -0,0 +1,95 @@
<?php

namespace Statamic\Data;

use Statamic\Contracts\Data\BulkAugmentable;

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

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

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

public 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;
}

$referenceKeys[$reference] = $item->augmented()->keys();
$referenceFields[$reference] = $item->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->toDeferredAugmentedArray($keys, $fields);
}

return $this;
}

public 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 augmented()
{
return $this->augmentedValues;
}
}
57 changes: 57 additions & 0 deletions src/Data/Concerns/ResolvesValues.php
@@ -0,0 +1,57 @@
<?php

namespace Statamic\Data\Concerns;

use Statamic\Fields\Value;

trait ResolvesValues
{
abstract protected function resolve();

public function materialize()
{
$this->resolve();

return $this->toValue();
}

protected function toValue()
{
return new Value($this->raw, $this->handle, $this->fieldtype, $this->augmentable, $this->shallow);
}

public function raw()
{
$this->resolve();

return parent::raw();
}

public function value()
{
$this->resolve();

return parent::value();
}

public function shallow()
{
$this->resolve();

return parent::shallow();
}

public function isRelationship(): bool
{
$this->resolve();

return parent::isRelationship();
}

public function fieldtype()
{
$this->resolve();

return parent::fieldtype();
}
}