Skip to content

Commit

Permalink
✨ Add block composers
Browse files Browse the repository at this point in the history
  • Loading branch information
tgeorgel committed Nov 21, 2024
1 parent dc179bb commit c3bdb92
Show file tree
Hide file tree
Showing 9 changed files with 687 additions and 9 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ You will need then to register the plugin into your Filament Panel :
GroguCMSPlugin::make()
->discoverTemplates(in: app_path('Content/Templates'), for: 'App\\Content\\Templates')
->discoverBlueprints(in: app_path('Content/Blueprints'), for: 'App\\Content\\Blueprints'),
->discoverBlockComposers(in: app_path('Content/BlockComposers'), for: 'App\\Content\\BlockComposers'),
```

To include Grogu scripts to your front-end, you should add the `@groguScripts` directive to your layout, preferably before the closing `</body>` tag :
Expand Down
35 changes: 35 additions & 0 deletions src/Collections/BlockCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Hydrat\GroguCMS\Collections;

use Hydrat\GroguCMS\Datas\Block;
use Illuminate\Support\Collection;
use Hydrat\GroguCMS\Facades\GroguCMS;

class BlockCollection extends Collection
{
public static function fromArray(array $blocks, bool $shouldCompose = true): self
{
return static::make($blocks)
->map(fn ($block) => Block::fromArray($block))
->when($shouldCompose, fn ($blocks) => $blocks->compose());
}

public function compose()
{
return $this->map(function ($block) {
foreach (GroguCMS::getBlockComposers($block->type) as $composer) {
with(new $composer)->compose($block);
}

return $block;
});
}

public static function fromString(string $blocks): self
{
return static::fromArray(
json_decode($blocks, true)
);
}
}
110 changes: 110 additions & 0 deletions src/Concerns/Extractable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

namespace Hydrat\GroguCMS\Concerns;

use Closure;
use Illuminate\View\InvokableComponentVariable;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;

/**
* Extract public properties from a composer to make available in a view.
*
* @copyright Taylor Otwell
* @link https://github.com/illuminate/view/blob/v10.35.0/Component.php#L220-L342
*
* @copyright Roots.io
* @link https://github.com/roots/acorn/blob/main/src/Roots/Acorn/View/Composers/Concerns/Extractable.php#L18
*/
trait Extractable
{
/**
* The cache of public property names, keyed by class.
*
* @var array
*/
protected static $propertyCache = [];

/**
* The cache of public method names, keyed by class.
*
* @var array
*/
protected static $methodCache = [];

/**
* Extract the public properties for the class.
*
* @return array
*/
protected function extractPublicProperties()
{
$class = get_class($this);

if (! isset(static::$propertyCache[$class])) {
$reflection = new ReflectionClass($this);

static::$propertyCache[$class] = collect($reflection->getProperties(ReflectionProperty::IS_PUBLIC))
->reject(fn (ReflectionProperty $property) => $property->isStatic() || $this->shouldIgnore($property->getName()))
->map(fn (ReflectionProperty $property) => $property->getName())
->all();
}

$values = [];

foreach (static::$propertyCache[$class] as $property) {
$values[$property] = $this->{$property};
}

return $values;
}

/**
* Extract the public methods for the class.
*
* @return array
*/
protected function extractPublicMethods()
{
$class = get_class($this);

if (! isset(static::$methodCache[$class])) {
$reflection = new ReflectionClass($this);

static::$methodCache[$class] = collect($reflection->getMethods(ReflectionMethod::IS_PUBLIC))
->reject(fn (ReflectionMethod $method) => $this->shouldIgnore($method->getName()))
->map(fn (ReflectionMethod $method) => $method->getName());
}

$values = [];

foreach (static::$methodCache[$class] as $method) {
$values[$method] = $this->createVariableFromMethod(new ReflectionMethod($this, $method));
}

return $values;
}

/**
* Create a callable variable from the given method.
*
* @return mixed
*/
protected function createVariableFromMethod(ReflectionMethod $method)
{
return $method->getNumberOfParameters() === 0
? $this->createInvokableVariable($method->getName())
: Closure::fromCallable([$this, $method->getName()]);
}

/**
* Create an invokable, toStringable variable for the given class method.
*
* @return \Illuminate\View\InvokableComponentVariable
*/
protected function createInvokableVariable(string $method)
{
return new InvokableComponentVariable(fn () => $this->{$method}());
}
}
146 changes: 146 additions & 0 deletions src/Content/BlockComposer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

namespace Hydrat\GroguCMS\Content;

use App\Cms\Block;
use Illuminate\Support\Str;
use Hydrat\GroguCMS\Concerns\Extractable;

/**
* @copyright Roots.io
* @link https://github.com/roots/acorn/blob/main/src/Roots/Acorn/block/Composer.php#L10
*/
abstract class BlockComposer
{
use Extractable;

/**
* The list of blocks served by this composer.
*
* @var string[]
*/
protected static $blocks;

/**
* The current block.
*
* @var \App\Cms\Block
*/
protected $block;

/**
* The current block data.
*
* @var \Illuminate\Support\Fluent
*/
protected $data;

/**
* The properties / methods that should not be exposed.
*
* @var array
*/
protected $except = [];

/**
* The default properties / methods that should not be exposed.
*
* @var array
*/
protected $defaultExcept = [
'cache',
'compose',
'override',
'toArray',
'blocks',
'with',
];

/**
* The list of blocks served by this composer.
*
* @return string|string[]
*/
public static function blocks()
{
if (static::$blocks) {
return static::$blocks;
}

$block = array_slice(explode('\\', static::class), 3);
$block = array_map([Str::class, 'snake'], $block, array_fill(0, count($block), '-'));

return implode('_', $block);
}

/**
* Compose the block before rendering.
*
* @return void
*/
public function compose(Block $block)
{
$this->block = $block;
$this->data = $block->getData();

$block->with($this->merge());
}

/**
* The data passed to the block before rendering.
*
* @return array
*/
protected function with()
{
return [];
}

/**
* The override data passed to the block before rendering.
*
* @return array
*/
protected function override()
{
return [];
}

/**
* The merged data to be passed to block before rendering.
*
* @return array
*/
protected function merge()
{
[$with, $override] = [$this->with(), $this->override()];

return array_merge(
$with,
$this->data->toArray(),
$override
);
}

/**
* Determine if the given property / method should be ignored.
*
* @param string $name
* @return bool
*/
protected function shouldIgnore($name)
{
return str_starts_with($name, '__') ||
in_array($name, $this->ignoredMethods());
}

/**
* Get the methods that should be ignored.
*
* @return array
*/
protected function ignoredMethods()
{
return array_merge($this->defaultExcept, $this->except);
}
}
Loading

0 comments on commit c3bdb92

Please sign in to comment.