Skip to content

Commit

Permalink
chore: refactor view engine (#560)
Browse files Browse the repository at this point in the history
  • Loading branch information
brendt authored Oct 11, 2024
1 parent 5718796 commit c1233aa
Show file tree
Hide file tree
Showing 72 changed files with 1,631 additions and 807 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ENVIRONMENT=production
BASE_URI=http://localhost
DISCOVERY_CACHE=false
DISCOVERY_CACHE=false
CACHE=false
3 changes: 2 additions & 1 deletion src/Tempest/Cache/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"require": {
"php": "^8.3",
"psr/cache": "^3.0",
"symfony/cache": "^7.2"
"symfony/cache": "^7.2",
"tempest/core": "dev-main"
},
"require-dev": {
"tempest/clock": "dev-main"
Expand Down
5 changes: 5 additions & 0 deletions src/Tempest/Cache/src/CacheConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@

use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use function Tempest\env;

final class CacheConfig
{
/** @var class-string<\Tempest\Cache\Cache>[] */
public array $caches = [];

public bool $enabled;

public function __construct(
public CacheItemPoolInterface $pool = new FilesystemAdapter(
namespace: '',
defaultLifetime: 0,
directory: __DIR__ . '/../../../../.cache',
),
?bool $enabled = null,
) {
$this->enabled = $enabled ?? env('CACHE', true);
}

/** @param class-string<\Tempest\Cache\Cache> $className */
Expand Down
2 changes: 1 addition & 1 deletion src/Tempest/Cache/src/CacheInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
#[Singleton]
public function initialize(Container $container): Cache|GenericCache
{
return new GenericCache($container->get(CacheConfig::class)->pool);
return new GenericCache($container->get(CacheConfig::class));
}
}
11 changes: 8 additions & 3 deletions src/Tempest/Cache/src/GenericCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@

use Psr\Cache\CacheItemPoolInterface;

final readonly class GenericCache implements Cache
final class GenericCache implements Cache
{
use IsCache;

public function __construct(
private CacheItemPoolInterface $pool,
private readonly CacheConfig $cacheConfig,
) {
}

protected function getCachePool(): CacheItemPoolInterface
{
return $this->pool;
return $this->cacheConfig->pool;
}

protected function isEnabled(): bool
{
return $this->cacheConfig->enabled;
}
}
6 changes: 6 additions & 0 deletions src/Tempest/Cache/src/IsCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ trait IsCache
{
abstract protected function getCachePool(): CacheItemPoolInterface;

abstract protected function isEnabled(): bool;

public function put(string $key, mixed $value, ?DateTimeInterface $expiresAt = null): CacheItemInterface
{
$item = $this->getCachePool()
Expand All @@ -36,6 +38,10 @@ public function get(string $key): mixed
/** @param Closure(): mixed $cache */
public function resolve(string $key, Closure $cache, ?DateTimeInterface $expiresAt = null): mixed
{
if (! $this->isEnabled()) {
return $cache();
}

$item = $this->getCachePool()->getItem($key);

if (! $item->isHit()) {
Expand Down
15 changes: 10 additions & 5 deletions src/Tempest/Cache/tests/CacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use DateInterval;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Tempest\Cache\CacheConfig;
use Tempest\Cache\GenericCache;
use Tempest\Clock\MockClock;

Expand All @@ -19,7 +20,7 @@ public function test_put(): void
{
$clock = new MockClock();
$pool = new ArrayAdapter(clock: $clock);
$cache = new GenericCache($pool);
$cache = new GenericCache(new CacheConfig($pool));
$interval = new DateInterval('P1D');

$cache->put('a', 'a', $clock->now()->add($interval));
Expand All @@ -43,7 +44,7 @@ public function test_get(): void
{
$clock = new MockClock();
$pool = new ArrayAdapter(clock: $clock);
$cache = new GenericCache($pool);
$cache = new GenericCache(new CacheConfig($pool));
$interval = new DateInterval('P1D');

$cache->put('a', 'a', $clock->now()->add($interval));
Expand All @@ -62,7 +63,11 @@ public function test_resolve(): void
{
$clock = new MockClock();
$pool = new ArrayAdapter(clock: $clock);
$cache = new GenericCache($pool);
$config = new CacheConfig(
pool: $pool,
enabled: true,
);
$cache = new GenericCache($config);
$interval = new DateInterval('P1D');

$a = $cache->resolve('a', fn () => 'a', $clock->now()->add($interval));
Expand All @@ -83,7 +88,7 @@ public function test_resolve(): void
public function test_remove(): void
{
$pool = new ArrayAdapter();
$cache = new GenericCache($pool);
$cache = new GenericCache(new CacheConfig($pool));

$cache->put('a', 'a');

Expand All @@ -95,7 +100,7 @@ public function test_remove(): void
public function test_clear(): void
{
$pool = new ArrayAdapter();
$cache = new GenericCache($pool);
$cache = new GenericCache(new CacheConfig($pool));

$cache->put('a', 'a');
$cache->put('b', 'b');
Expand Down
46 changes: 42 additions & 4 deletions src/Tempest/Support/src/StringHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,34 @@ public function classBasename(): self
return new self(basename(str_replace('\\', '/', $this->string)));
}

public function startsWith(Stringable|string $needle): bool
public function startsWith(Stringable|string|array $needles): bool
{
return str_starts_with($this->string, (string) $needle);
if (! is_array($needles)) {
$needles = [$needles];
}

foreach ($needles as $needle) {
if (str_starts_with($this->string, (string) $needle)) {
return true;
}
}

return false;
}

public function endsWith(Stringable|string $needle): bool
public function endsWith(Stringable|string|array $needles): bool
{
return str_ends_with($this->string, (string) $needle);
if (! is_array($needles)) {
$needles = [$needles];
}

foreach ($needles as $needle) {
if (str_ends_with($this->string, (string) $needle)) {
return true;
}
}

return false;
}

public function replaceFirst(Stringable|string $search, Stringable|string $replace): self
Expand Down Expand Up @@ -420,6 +440,24 @@ public function dump(mixed ...$dumps): self
return $this;
}

public function excerpt(int $from, int $to, bool $asArray = false): self|ArrayHelper
{
$lines = explode(PHP_EOL, $this->string);

$from = max(0, $from - 1);

$to = min($to - 1, count($lines));

$lines = array_slice($lines, $from, $to - $from + 1, true);

if ($asArray) {
return arr($lines)
->mapWithKeys(fn (string $line, int $number) => yield $number + 1 => $line);
}

return new self(implode(PHP_EOL, $lines));
}

private function normalizeString(mixed $value): mixed
{
if ($value instanceof Stringable) {
Expand Down
22 changes: 22 additions & 0 deletions src/Tempest/Support/tests/StringHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -419,4 +419,26 @@ public function test_implode(): void
$this->assertSame('path/to/tempest', StringHelper::implode(arr(['path', 'to', 'tempest']), '/')->toString());
$this->assertSame('john doe', StringHelper::implode(arr(['john', 'doe']))->toString());
}

public function test_excerpt(): void
{
$content = str('a
b
c
d
e
f
g');

$this->assertTrue($content->excerpt(2, 4)->equals('b
c
d'));

$this->assertTrue($content->excerpt(-10, 2)->equals('a
b'));

$this->assertTrue($content->excerpt(7, 100)->equals('g'));

$this->assertSame([2 => 'b', 3 => 'c', 4 => 'd'], $content->excerpt(2, 4, asArray: true)->toArray());
}
}
3 changes: 2 additions & 1 deletion src/Tempest/View/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"masterminds/html5": "^2.9",
"tempest/core": "dev-main",
"tempest/container": "dev-main",
"tempest/validation": "dev-main"
"tempest/validation": "dev-main",
"tempest/cache": "dev-main"
},
"autoload": {
"files": [
Expand Down
2 changes: 1 addition & 1 deletion src/Tempest/View/src/Attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

interface Attribute
{
public function apply(Element $element): Element;
public function apply(Element $element): ?Element;
}
9 changes: 4 additions & 5 deletions src/Tempest/View/src/Attributes/AttributeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,19 @@
namespace Tempest\View\Attributes;

use Tempest\View\Attribute;
use Tempest\View\View;

final readonly class AttributeFactory
{
public function make(View $view, string $name, ?string $value): Attribute
public function make(string $name): Attribute
{
return match(true) {
$name === ':if' => new IfAttribute(),
$name === ':elseif' => new ElseIfAttribute(),
$name === ':else' => new ElseAttribute(),
$name === ':foreach' => new ForeachAttribute($view, $value),
$name === ':foreach' => new ForeachAttribute(),
$name === ':forelse' => new ForelseAttribute(),
str_starts_with(':', $name) && $value => new DataAttribute($view, $name, $value),
default => new DefaultAttribute(),
str_starts_with($name, ':') => new ExpressionAttribute($name),
default => new DataAttribute($name),
};
}
}
18 changes: 14 additions & 4 deletions src/Tempest/View/src/Attributes/DataAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,31 @@

namespace Tempest\View\Attributes;

use function Tempest\Support\str;
use Tempest\View\Attribute;
use Tempest\View\Element;
use Tempest\View\View;
use Tempest\View\Elements\PhpDataElement;
use Tempest\View\Elements\ViewComponentElement;

final readonly class DataAttribute implements Attribute
{
public function __construct(
private View $view,
private string $name,
private string $eval
) {
}

public function apply(Element $element): Element
{
return $element->addData(...[$this->name => $this->view->eval($this->eval)]);
if (! $element instanceof ViewComponentElement) {
return $element;
}

$value = str($element->getAttribute($this->name));

return new PhpDataElement(
$this->name,
$value->toString(),
$element,
);
}
}
30 changes: 7 additions & 23 deletions src/Tempest/View/src/Attributes/ElseAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,23 @@

namespace Tempest\View\Attributes;

use Exception;
use Tempest\View\Attribute;
use Tempest\View\Element;
use Tempest\View\Elements\EmptyElement;
use Tempest\View\Elements\GenericElement;
use Tempest\View\Elements\PhpIfElement;
use Tempest\View\Exceptions\InvalidElement;

final readonly class ElseAttribute implements Attribute
{
public function apply(Element $element): Element
public function apply(Element $element): ?Element
{
$previous = $element->getPrevious();
$previousCondition = false;

if (! $previous instanceof GenericElement) {
throw new Exception("Invalid preceding element before :else");
if (! $previous instanceof PhpIfElement) {
throw new InvalidElement('There needs to be an if or elseif element before.');
}

// Check all :elseif and :if conditions for previous elements
// If one of the previous element's conditions is true, we'll stop.
// We won't have to render this :else element
while (
$previousCondition === false
&& $previous instanceof GenericElement
&& ($previous->hasAttribute('if') || $previous->hasAttribute('elseif'))
) {
$previousCondition = (bool) ($previous->getAttribute('if') ?? $previous->getAttribute('elseif'));
$previous = $previous->getPrevious();
}

if ($previousCondition) {
return new EmptyElement();
}
$previous->setElse($element);

return $element;
return null;
}
}
Loading

0 comments on commit c1233aa

Please sign in to comment.