computed) === true) { return $this->computed[$name]; } if (array_key_exists($name, $this->props) === true) { return $this->props[$name]; } if (array_key_exists($name, $this->methods) === true) { return $this->methods[$name]->call($this, ...$arguments); } return $this->$name; } /** * Creates a new component for the given type * * @param string $type * @param array $attrs */ public function __construct(string $type, array $attrs = []) { if (isset(static::$types[$type]) === false) { throw new InvalidArgumentException('Undefined component type: ' . $type); } $this->attrs = $attrs; $this->options = $options = $this->setup($type); $this->methods = $methods = $options['methods'] ?? []; foreach ($attrs as $attrName => $attrValue) { $this->$attrName = $attrValue; } if (isset($options['props']) === true) { $this->applyProps($options['props']); } if (isset($options['computed']) === true) { $this->applyComputed($options['computed']); } $this->attrs = $attrs; $this->methods = $methods; $this->options = $options; $this->type = $type; } /** * Improved var_dump output * * @return array */ public function __debuginfo(): array { return $this->toArray(); } /** * Fallback for missing properties to return * null instead of an error * * @param string $attr * @return null */ public function __get(string $attr) { return null; } /** * A set of default options for each component. * This can be overwritten by extended classes * to define basic options that should always * be applied. * * @return array */ public static function defaults(): array { return []; } /** * Register all defined props and apply the * passed values. * * @param array $props * @return void */ protected function applyProps(array $props): void { foreach ($props as $propName => $propFunction) { if (is_callable($propFunction) === true) { if (isset($this->attrs[$propName]) === true) { try { $this->$propName = $this->props[$propName] = $propFunction->call($this, $this->attrs[$propName]); } catch (TypeError $e) { throw new TypeError('Invalid value for "' . $propName . '"'); } } else { try { $this->$propName = $this->props[$propName] = $propFunction->call($this); } catch (ArgumentCountError $e) { throw new ArgumentCountError('Please provide a value for "' . $propName . '"'); } } } else { $this->$propName = $this->props[$propName] = $propFunction; } } } /** * Register all computed properties and calculate their values. * This must happen after all props are registered. * * @param array $computed * @return void */ protected function applyComputed(array $computed): void { foreach ($computed as $computedName => $computedFunction) { $this->$computedName = $this->computed[$computedName] = $computedFunction->call($this); } } /** * Load a component definition by type * * @param string $type * @return array */ public static function load(string $type): array { $definition = static::$types[$type]; // load definitions from string if (is_array($definition) === false) { static::$types[$type] = $definition = include $definition; } return $definition; } /** * Loads all options from the component definition * mixes in the defaults from the defaults method and * then injects all additional mixins, defined in the * component options. * * @param string $type * @return array */ public static function setup(string $type): array { // load component definition $definition = static::load($type); if (isset($definition['extends']) === true) { // extend other definitions $options = array_replace_recursive(static::defaults(), static::load($definition['extends']), $definition); } else { // inject defaults $options = array_replace_recursive(static::defaults(), $definition); } // inject mixins if (isset($options['mixins']) === true) { foreach ($options['mixins'] as $mixin) { if (isset(static::$mixins[$mixin]) === true) { $options = array_replace_recursive(static::$mixins[$mixin], $options); } } } return $options; } /** * Converts all props and computed props to an array * * @return array */ public function toArray(): array { if (is_a($this->options['toArray'] ?? null, 'Closure') === true) { return $this->options['toArray']->call($this); } $array = array_merge($this->attrs, $this->props, $this->computed); ksort($array); return $array; } }