Skip to content

Commit

Permalink
pint
Browse files Browse the repository at this point in the history
  • Loading branch information
markhuot committed Dec 12, 2023
1 parent 3f6578d commit 238779c
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 19 deletions.
12 changes: 12 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
frankenphp
order php_server before file_server
}

# The domain name of your server
localhost {
# Enable compression (optional)
encode zstd gzip
# Execute PHP files in the current directory and serve assets
php_server
}
2 changes: 1 addition & 1 deletion bin/create-default-fs.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
'name' => 'Local',
'handle' => 'local',
'hasUrls' => true,
'url' => 'http://localhost:8080/volumes/local/',
'url' => '@web/volumes/local/',
'settings' => ['path' => CRAFT_BASE_PATH.'/web/volumes/local'],
]);
$result = $app->fs->saveFilesystem($fs);
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"require": {
"craftcms/cms": "^4.5.7",
"nikic/php-parser": "^4.17",
"sebastian/comparator": "^5.0@dev"
"sebastian/comparator": "^5.0@dev",
"craftcms/ckeditor": "dev-main"
}
}
2 changes: 2 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
</testsuites>
<php>
<env name="CRAFT_TEMPLATES_PATH" value="./tests/templates/" />
<env name="CRAFT_OMIT_SCRIPT_NAME_IN_URLS" value="true" />
<env name="PRIMARY_SITE_URL" value="http://localhost:8080/" />
</php>
<source>
<include>
Expand Down
35 changes: 26 additions & 9 deletions src/actions/EagerLoadComponents.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,50 @@
namespace markhuot\keystone\actions;

use craft\elements\Asset;
use craft\elements\Entry;
use craft\fields\Assets;
use craft\fields\Entries;
use Illuminate\Support\Collection;
use markhuot\keystone\interfaces\ShouldHandleEvents;
use markhuot\keystone\models\Component;

class EagerLoadComponents implements ShouldHandleEvents
{
/**
* @param Collection<Component> $components
*/
public function handle(Collection $components)
{
$assetIds = [];
$fieldTypes = [Assets::class => [], Entries::class => []];

// foreach loops are just as optimzed as array_search queries so we'll opt for readability
// here and just loop over all the components looking for any fields that can be eager loaded.
//
// Compare the following two approaches, both taking the same 21-23ms to run
// https://3v4l.org/PJRaF#v8.2.13
// https://3v4l.org/ETqcS#v8.2.13
foreach ($components as $component) {
foreach ($component->getType()->getFieldDefinitions() as $field) {
if ($field->className === Assets::class) {
$assetIds = array_merge($assetIds, $component->data->getRaw($field->handle) ?? []);
foreach ($fieldTypes as $type => &$ids) {
if ($field->className === $type) {
$ids = array_merge($ids, $component->data->getRaw($field->handle) ?? []);
}
}
}
}

$assets = Asset::find()->id($assetIds)->collect()->keyBy('id');
$fieldTypes[Assets::class] = Asset::find()->id($fieldTypes[Assets::class])->collect()->keyBy('id');
$fieldTypes[Entries::class] = Entry::find()->id($fieldTypes[Entries::class])->collect()->keyBy('id');

foreach ($components as $component) {
foreach ($component->getType()->getFieldDefinitions() as $field) {
if ($field->className === Assets::class) {
$componentData = collect($component->data->getRaw($field->handle) ?? [])
->map(fn ($id) => $assets->get($id))
->filter();
$component->data->populateRelation($field->handle, $componentData);
foreach ($fieldTypes as $type => $elements) {
if ($field->className === $type) {
$componentData = collect($component->data->getRaw($field->handle) ?? [])
->map(fn($id) => $elements->get($id))
->filter();
$component->data->populateRelation($field->handle, $componentData);
}
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/actions/GetComponentType.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ public function byType(string $type): ComponentType
$fqcn = $className;
}

if (! empty($fqcn)) {
return Craft::$container->get($fqcn, ['context' => $this->context]);
if (empty($fqcn)) {
return $this->byType('keystone/missing');
}

throw new \RuntimeException('Could not find a component type definition for '.$type);
return Craft::$container->get($fqcn, ['context' => $this->context]);
}
}
5 changes: 5 additions & 0 deletions src/listeners/RegisterDefaultComponentTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ public function handle(RegisterComponentTypes $event): void
$event->registerTwigTemplate('keystone/heading', 'cp:keystone/components/heading.twig');
$event->registerTwigTemplate('keystone/icon', 'cp:keystone/components/icon.twig');
$event->registerTwigTemplate('keystone/link', 'cp:keystone/components/link.twig');
$event->registerTwigTemplate('keystone/missing', 'cp:keystone/components/missing.twig');
$event->registerTwigTemplate('keystone/section', 'cp:keystone/components/section.twig');
$event->registerTwigTemplate('keystone/tab', 'cp:keystone/components/tab.twig');
$event->registerTwigTemplate('keystone/tabs', 'cp:keystone/components/tabs.twig');
$event->registerTwigTemplate('keystone/template', 'cp:keystone/components/template.twig');
$event->registerTwigTemplate('keystone/text', 'cp:keystone/components/text.twig');

if (\Craft::$app->getPlugins()->isPluginInstalled('ckeditor')) {
$event->registerTwigTemplate('keystone/richtext', 'cp:keystone/components/rich-text.twig');
}
}
}
6 changes: 4 additions & 2 deletions src/templates/components/entryquery.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
search: field('\\markhuot\\keystone\\fields\\Condition'),
limit: field('\\craft\\fields\\Number'),
} %}
{% set defaultSlot = component.getType().defineSlot().collapsed() %}
{% set elements = props.search.all()|default([]) %}
{% set defaultSlot = component.getType().defineSlot()
.defaults([{type: 'keystone/template', data: {template: 'cp:keystone/entry/link'}}])
.collapsed() %}
{% set elements = props.search.limit(props.limit ?? 100).all()|default([]) %}
{% for element in elements %}
{{ defaultSlot.render({entry: element}) }}
{% endfor %}
1 change: 1 addition & 0 deletions src/templates/components/missing.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{% export icon %}<svg xmlns="http://www.w3.org/2000/svg" fill="#000000" viewBox="0 0 256 256"><path d="M96.26,37.05A8,8,0,0,1,102,27.29a104.11,104.11,0,0,1,52,0,8,8,0,0,1-2,15.75,8.15,8.15,0,0,1-2-.26,88.09,88.09,0,0,0-44,0A8,8,0,0,1,96.26,37.05ZM53.79,55.14a104.05,104.05,0,0,0-26,45,8,8,0,0,0,15.42,4.27,88,88,0,0,1,22-38.09A8,8,0,0,0,53.79,55.14ZM43.21,151.55a8,8,0,1,0-15.42,4.28,104.12,104.12,0,0,0,26,45,8,8,0,0,0,11.41-11.22A88.14,88.14,0,0,1,43.21,151.55ZM150,213.22a88,88,0,0,1-44,0,8,8,0,1,0-4,15.49,104.11,104.11,0,0,0,52,0,8,8,0,0,0-4-15.49ZM222.65,146a8,8,0,0,0-9.85,5.58,87.91,87.91,0,0,1-22,38.08,8,8,0,1,0,11.42,11.21,104,104,0,0,0,26-45A8,8,0,0,0,222.65,146Zm-9.86-41.54a8,8,0,0,0,15.42-4.28,104,104,0,0,0-26-45,8,8,0,1,0-11.41,11.22A88,88,0,0,1,212.79,104.45Z"></path></svg>{% endexport %}
7 changes: 7 additions & 0 deletions src/templates/components/rich-text.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% export name = "Rich Text" %}
{% export icon %}<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path d="M87.24,52.59a8,8,0,0,0-14.48,0l-64,136a8,8,0,1,0,14.48,6.81L39.9,160h80.2l16.66,35.4a8,8,0,1,0,14.48-6.81ZM47.43,144,80,74.79,112.57,144ZM200,96c-12.76,0-22.73,3.47-29.63,10.32a8,8,0,0,0,11.26,11.36c3.8-3.77,10-5.68,18.37-5.68,13.23,0,24,9,24,20v3.22A42.76,42.76,0,0,0,200,128c-22.06,0-40,16.15-40,36s17.94,36,40,36a42.73,42.73,0,0,0,24-7.25,8,8,0,0,0,16-.75V132C240,112.15,222.06,96,200,96Zm0,88c-13.23,0-24-9-24-20s10.77-20,24-20,24,9,24,20S213.23,184,200,184Z"></path></svg>{% endexport %}
{% export propTypes = {
text: field('craft\\ckeditor\\Field')
} %}
{% export summary = props.text|striptags|slice(0, 33) %}
{{ props.text }}
21 changes: 21 additions & 0 deletions tests/EagerLoadTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use markhuot\craftpest\factories\Asset;
use markhuot\craftpest\factories\Entry;
use markhuot\keystone\models\Component;

it('passes loaded components down so they\'re not refetched', function () {
Expand Down Expand Up @@ -47,3 +48,23 @@
$this->endBenchmark()
->assertQueryCount(4);
});

it('eager loads entries', function () {
$fragment = Component::factory()
->type('keystone/fragment')
->create();
$entry = Component::factory()
->type('keystone/entry')
->path($fragment->id)
->count(3)
->create();
$entry->each(fn ($c) => $c->data
->merge(['entry' => [Entry::factory()->section('pages')->create()->id]])
->save()
);

$this->beginBenchmark();
$fragment->getSlot()->each(fn ($c) => $c->getProp('entry')->one());
$this->endBenchmark()
->assertQueryCount(3);
});
6 changes: 3 additions & 3 deletions tests/ParseTwigForSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
use markhuot\keystone\actions\GetFileMTime;
use markhuot\keystone\models\Component;

it('throws on unknown component', function () {
$this->expectException(RuntimeException::class);
(new GetComponentType)->byType('foo/bar');
it('returns missing for unknown components', function () {
$type = (new GetComponentType)->byType('foo/bar');
expect($type)->getHandle()->toBe('keystone/missing');
});

it('throws on bad template path', function () {
Expand Down

0 comments on commit 238779c

Please sign in to comment.