1
0
Fork 0
mirror of https://github.com/archtechx/airwire.git synced 2025-12-12 10:44:03 +00:00
This commit is contained in:
Samuel Štancl 2021-05-20 20:15:55 +02:00
commit d26fa93f1e
35 changed files with 2388 additions and 0 deletions

View file

@ -0,0 +1,122 @@
<?php
namespace Airwire\Concerns;
use Airwire\Airwire;
use ReflectionMethod;
use ReflectionObject;
use Illuminate\Foundation\Exceptions\Handler;
use Illuminate\Validation\ValidationException;
use Throwable;
trait ManagesActions
{
public array $readonly = [];
public function makeChanges(array $changes): void
{
foreach ($this->getSharedProperties() as $property) {
if (isset($changes[$property]) && ! $this->isReadonly($property)) {
if (! $this->makeChange($property, $changes[$property])) {
unset($changes[$property]);
}
} else {
unset($changes[$property]);
}
}
if ($changes) {
try {
$this->changed($changes);
} catch (ValidationException) {}
}
}
protected function makeChange(string $property, mixed $new): bool
{
$old = $this->$property ?? null;
try {
if ($this->updating($property, $new, $old) === false) {
return false;
}
if (method_exists($this, $method = ('updating' . ucfirst($property)))) {
if ($this->$method($new, $old) === false) {
return false;
}
}
} catch (ValidationException $e) {
return false;
}
$this->$property = $new;
$this->updated($property, $new);
if (method_exists($this, $method = ('updated' . ucfirst($property)))) {
$this->$method($new, $old);
}
return true;
}
public function makeCalls(array $calls): void
{
$this->metadata['calls'] ??= [];
foreach ($this->getSharedMethods() as $method) {
if (isset($calls[$method])) {
try {
$result = $this->callWiredMethod($method, $calls[$method]);
if ($method === 'mount') {
if (isset($result['readonly'])) {
$readonly = $result['readonly'];
unset($result['readonly']);
$result = array_merge($readonly, $result);
$this->readonly = array_unique(array_merge(
$this->readonly, array_keys($readonly)
));
}
}
$this->metadata['calls'][$method] = $result;
} catch (Throwable $e) {
if (! app()->isProduction() && ! $e instanceof ValidationException) {
$reflection = (new ReflectionObject($handler = (new Handler(app()))))->getMethod('convertExceptionToArray');
$reflection->setAccessible(true);
$this->metadata['exceptions'] ??= [];
$this->metadata['exceptions'][$method] = $reflection->invoke($handler, $e);
}
}
}
}
}
protected function callWiredMethod(string $method, array $arguments): mixed
{
$reflectionMethod = new ReflectionMethod($this, $method);
$parameters = $reflectionMethod->getParameters();
foreach ($arguments as $index => &$value) {
if (! isset($parameters[$index])) {
break;
}
$parameter = $parameters[$index];
$value = Airwire::decode($parameter, $value);
}
$result = $this->$method(...$arguments);
if ($returnType = $reflectionMethod->getReturnType()) {
return Airwire::encode($returnType, $result);
} else {
return $result;
}
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Airwire\Concerns;
use Illuminate\Validation\ValidationException;
trait ManagesLifecycle
{
public function hydrateComponent(): bool
{
if ($this->strictValidation && $this->validate(throw: false) === false) {
return false;
}
if (method_exists($this, 'hydrate')) {
$hydrate = app()->call([$this, 'hydrate'], $this->requestState);
if (is_bool($hydrate)) {
return $hydrate;
}
}
return true;
}
public function dehydrateComponent(): void
{
try {
$this->validate();
if (method_exists($this, 'dehydrate')) {
app()->call([$this, 'dehydrate'], $this->requestState);
}
} catch (ValidationException) {}
if (isset($this->errors) && ! $this->hasBeenReset) {
$this->metadata['errors'] = $this->errors->toArray();
} else {
$this->metadata['errors'] = [];
}
$this->metadata['readonly'] = array_unique(array_merge($this->readonly, $this->getReadonlyProperties()));
}
public function updating(string $property, mixed $new, mixed $old): bool
{
return true;
}
public function updated(string $property, mixed $value): void
{
}
public function changed(array $changes): void
{
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Airwire\Concerns;
use Airwire\Airwire;
use ReflectionMethod;
use ReflectionObject;
use ReflectionProperty;
use Airwire\Attributes\Wired;
trait ManagesState
{
public bool $hasBeenReset = false;
public function getSharedProperties(): array
{
return collect((new ReflectionObject($this))->getProperties())
->filter(
fn (ReflectionProperty $property) => collect($property->getAttributes(Wired::class))->isNotEmpty()
)
->map(fn (ReflectionProperty $property) => $property->getName())
->toArray();
}
public function getSharedMethods(): array
{
return collect((new ReflectionObject($this))->getMethods())
->filter(
fn (ReflectionMethod $method) => collect($method->getAttributes(Wired::class))->isNotEmpty()
)
->map(fn (ReflectionMethod $method) => $method->getName())
->merge(method_exists($this, 'mount') ? ['mount'] : [])
->toArray();
}
public function getState(): array
{
return collect($this->getSharedProperties())
->combine($this->getSharedProperties())
->map(function (string $property) {
if (isset($this->$property)) {
return $this->$property;
}
$default = optional((new ReflectionProperty($this, $property))->getAttributes(Wired::class))[0]->newInstance()->default;
if ($default !== null) {
return Airwire::decode([$this, $property], $default);
}
return null;
})
->all();
}
public function getEncodedState(): array
{
return collect($this->getState())
->map(fn ($value, $key) => Airwire::encode([$this, $key], $value))
->toArray();
}
public function getReadonlyProperties(): array
{
return collect($this->getSharedProperties())
->filter(fn (string $property) => $this->isReadonly($property))
->values()
->toArray();
}
public function isReadonly(string $property): bool
{
$attributes = (new ReflectionProperty($this, $property))->getAttributes(Wired::class);
return count($attributes) === 1 && $attributes[0]->newInstance()->readonly === true;
}
public function reset(array $properties = null): void
{
$properties ??= $this->getSharedProperties();
foreach ($properties as $property) {
unset($this->$property);
}
$this->hasBeenReset = true;
}
public function meta(string|array $key, mixed $value): void
{
if (is_array($key)) {
$this->metadata = array_merge($this->metadata, $key);
} else {
$this->metadata[$key] = $value;
}
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Airwire\Concerns;
use Illuminate\Contracts\Validation\Validator as AbstractValidator;
use Illuminate\Support\Arr;
use Illuminate\Support\MessageBag;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
trait ManagesValidation
{
public bool $strictValidation = true;
public MessageBag $errors;
/** @throws ValidationException */
public function validate(string|array $properties = null, bool $throw = true): bool
{
$validator = $this->validator($properties);
if ($validator->fails()) {
if (isset($this->errors)) {
foreach ($validator->errors()->toArray() as $property => $errors) {
foreach ($errors as $error) {
if (! in_array($error, $this->errors->get($property))) {
$this->errors->add($property, $error);
}
}
}
} else {
$this->errors = $validator->errors();
}
if ($throw) {
$validator->validate();
} else {
return false;
}
}
return true;
}
/** @throws ValidationException */
public function validated(string|array $properties = null): array
{
return $this->validator($properties)->validated();
}
public function validator(string|array $properties = null): AbstractValidator
{
$state = array_merge($this->getState(), $this->changes);
$rules = $this->rules();
$messages = $this->messages();
$attributes = $this->attributes();
$properties = $properties
? Arr::wrap($properties)
: $this->getSharedProperties();
$state = collect($state)->only($properties)->toArray();
$rules = collect($rules)->only($properties)->toArray();
$messages = collect($messages)->only($properties)->toArray();
$attributes = collect($attributes)->only($properties)->toArray();
return Validator::make($state, $rules, $messages, $attributes);
}
public function rules()
{
return $this->rules ?? [];
}
public function messages()
{
return [];
}
public function attributes()
{
return [];
}
}