Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions dist/Exceptions/EventHandlerDoesNotExist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
/**
* Livewire copyright © Caleb Porzio (https://github.com/livewire/livewire).
* Magewire copyright © Willem Poortman 2024-present.
* All rights reserved.
*
* Please read the README and LICENSE files for more
* details on copyrights and license information.
*/
namespace Magewirephp\Magewire\Exceptions;

class EventHandlerDoesNotExist extends \Exception
{
public function __construct(public readonly string $eventName)
{
parent::__construct('Handler for event ' . $eventName . ' does not exist');
}
}
113 changes: 113 additions & 0 deletions lib/Magewire/Features/SupportMagewireEvents/SupportMagewireEvents.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

/**
* Copyright © Willem Poortman 2021-present. All rights reserved.
*
* Please read the README and LICENSE files for more
* details on copyrights and license information.
*/

declare(strict_types=1);

namespace Magewirephp\Magewire\Features\SupportMagewireEvents;

use Magewirephp\Magewire\ComponentHook;
use Magewirephp\Magewire\ComponentHookRegistry;
use Magewirephp\Magewire\Exceptions\EventHandlerDoesNotExist;
use Magewirephp\Magewire\Features\SupportEvents\SupportEvents;
use Magewirephp\Magewire\Mechanisms\HandleComponents\ComponentContext;
use Magewirephp\Magewire\Mechanisms\ResolveComponents\ComponentArguments\LayoutArgumentOverlay;

use function Magewirephp\Magewire\before;
use function Magewirephp\Magewire\store;

class SupportMagewireEvents extends ComponentHook
{
public function __construct(
private readonly LayoutArgumentOverlay $layoutArgumentOverlay
) {
}

public function provide(): void
{
before('call', static function ($component, $method, $params): void {
if ($method !== '__dispatch') {
return;
}

$hook = ComponentHookRegistry::getHook($component, self::class);

if ($hook instanceof self && is_string($params[0] ?? null)) {
$hook->ensureListenerIsNotRemoved($params[0]);
}
});
}

public function skip(): bool
{
return ComponentHookRegistry::getHook($this->component(), SupportEvents::class) === null;
}

public function boot(): void
{
$component = $this->component();
$fromAttributes = store($component)->get('listenersFromAttributes', []);

store($component)->set('listenersFromAttributes', $this->layoutArgumentOverlay->apply(
$this->normalizeListeners($fromAttributes),
$this->normalizeListeners($this->getLayoutListeners($component)),
[false, null]
));
}

public function dehydrate(ComponentContext $context): void
{
if (! $context->isMounting() || ! $context->hasEffect('listeners')) {
return;
}

$removed = $this->getRemovedListenerNames();
$listeners = array_values(array_filter($context->getEffects()->getData('listeners', []), static fn ($listener) => ! in_array($listener, $removed, true)));

$listeners === [] ? $context->getEffects()->exclude('listeners') : $context->addEffect('listeners', $listeners);
}

/**
* Return the per-placement listener overlay declared through the
* `magewire:listeners` layout argument.
*/
protected function getLayoutListeners($component): array
{
$listeners = $this->layoutArgumentOverlay->get($component, 'listeners', []);

return is_array($listeners) ? $listeners : [];
}

protected function normalizeListeners(array $listeners): array
{
$normalized = [];

foreach ($listeners as $event => $method) {
$event = is_numeric($event) ? $method : $event;
$normalized[$event] = $method;
}

return $normalized;
}

private function ensureListenerIsNotRemoved(string $listener): void
{
if (in_array($listener, $this->getRemovedListenerNames(), true)) {
throw new EventHandlerDoesNotExist($listener);
}
}

private function getRemovedListenerNames(): array
{
$component = $this->component();
$tombstones = $this->layoutArgumentOverlay->removed($this->normalizeListeners($this->getLayoutListeners($component)), [false, null]);
$tombstones = SupportEvents::replaceDynamicEventNamePlaceholders($tombstones, $component);

return array_keys($tombstones);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@

namespace Magewirephp\Magewire\Features\SupportMagewireLoaders;

use Magento\Framework\Phrase;

trait HandlesMagewireLoaders
{
/** @var bool|array */
/** @var bool|string|Phrase|array */
protected $loader = false;

/**
* @return bool|array
* @return bool|string|Phrase|array
*/
public function getLoader()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,65 @@

namespace Magewirephp\Magewire\Features\SupportMagewireLoaders;

use Magento\Framework\Phrase;
use Magewirephp\Magewire\ComponentHook;
use Magewirephp\Magewire\Mechanisms\HandleComponents\ComponentContext;
use Magewirephp\Magewire\Mechanisms\ResolveComponents\ComponentArguments\LayoutArgumentOverlay;

use function Magewirephp\Magewire\map_with_keys;

class SupportMagewireLoaders extends ComponentHook
{
public function __construct(
private readonly LayoutArgumentOverlay $layoutArgumentOverlay
) {
}

function dehydrate(ComponentContext $context): void
{
$loader = $context->component->getLoader();
$loader = $this->layoutArgumentOverlay->value($context->component, 'loader', $context->component->getLoader(), [null]);

if ($loader) {
if (is_array($loader)) {
$loader = map_with_keys(static function ($value, $key) {
if (is_string($value)) {
$value = [$value];
}
if (is_array($value)) {
$value = array_map('__', array_filter($value, 'is_string'));
}

return [$key => $value];
}, $loader);
} elseif (is_string($loader)) {
$loader = __($loader);
$context->pushEffect('loader', $this->translateLoader($loader));
}
}

private function translateLoader(mixed $loader): mixed
{
if (is_string($loader)) {
return __($loader);
}

if (! is_array($loader)) {
return $loader;
}

return map_with_keys(fn ($value, $key) => [$key => $this->translateMessages($value)], $loader);
}

private function translateMessages(mixed $value): mixed
{
if (is_string($value) || $value instanceof Phrase) {
$value = [$value];
}

if (! is_array($value)) {
return $value;
}

$messages = [];

foreach ($value as $key => $message) {
if ($message instanceof Phrase) {
$messages[$key] = $message;
continue;
}

$context->pushEffect('loader', $loader);
if (is_string($message)) {
$messages[$key] = __($message);
}
}

return $messages;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

/**
* Copyright © Willem Poortman 2021-present. All rights reserved.
*
* Please read the README and LICENSE files for more
* details on copyrights and license information.
*/

declare(strict_types=1);

namespace Magewirephp\Magewire\Mechanisms\ResolveComponents\ComponentArguments;

use Magewirephp\Magewire\Component;

class LayoutArgumentOverlay
{
public function get(Component $component, string $name, mixed $default = null): mixed
{
$arguments = $this->arguments($component);

return array_key_exists($name, $arguments) ? $arguments[$name] : $default;
}

/**
* Use the layout value when present. Arrays add entries and replace keys;
* selected keyed values can remove entries from the original array.
*/
public function value(Component $component, string $name, mixed $original, array $removals = []): mixed
{
$arguments = $this->arguments($component);

if (! array_key_exists($name, $arguments)) {
return $original;
}

return $this->apply($original, $arguments[$name], $removals);
}

public function apply(mixed $original, mixed $overlay, array $removals = []): mixed
{
if (! is_array($overlay)) {
return $overlay;
}

$original = is_array($original) ? $original : [];

foreach ($overlay as $key => $value) {
if (in_array($value, $removals, true)) {
if (! is_int($key)) {
unset($original[$key]);
}

continue;
}

if (is_int($key)) {
$original[] = $value;
continue;
}

$original[$key] = $value;
}

return $original;
}

public function removed(array $overlay, array $removals): array
{
return array_filter($overlay, static fn ($value) => in_array($value, $removals, true));
}

private function arguments(Component $component): array
{
return $component->magewireResolver()?->arguments()->all() ?? [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/**
* Copyright © Willem Poortman 2021-present. All rights reserved.
*
* Please read the README and LICENSE files for more
* details on copyrights and license information.
*/

declare(strict_types=1);

namespace Magewirephp\Magewire\Mechanisms\ResolveComponents\ComponentModifiers;

use Magewirephp\Magewire\Component;
use Magewirephp\Magewire\Mechanisms\ResolveComponents\ComponentArguments\MagewireArguments;

class ComponentModifierContext
{
public function __construct(
private readonly Component $component,
private readonly MagewireArguments $arguments
) {
}

public function component(): Component
{
return $this->component;
}

public function arguments(): MagewireArguments
{
return $this->arguments;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/**
* Copyright © Willem Poortman 2021-present. All rights reserved.
*
* Please read the README and LICENSE files for more
* details on copyrights and license information.
*/

declare(strict_types=1);

namespace Magewirephp\Magewire\Mechanisms\ResolveComponents\ComponentModifiers;

use InvalidArgumentException;

class ComponentModifierRunner
{
public function run(ComponentModifierContext $context): void
{
$modifiers = $context->arguments()->get('modifiers', []);

if (! is_array($modifiers)) {
throw new InvalidArgumentException('The magewire:modifiers layout argument must be an array.');
}

foreach ($modifiers as $name => $modifier) {
if (! $modifier instanceof ModifierInterface) {
throw new InvalidArgumentException(sprintf('Component modifier "%s" must implement %s.', $name, ModifierInterface::class));
}

$modifier->modify($context);
}
}
}
Loading
Loading