753 lines
23 KiB
PHP
753 lines
23 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
/**
|
|
* The base widget class file of zin of ZenTaoPMS.
|
|
*
|
|
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
|
|
* @author Hao Sun <sunhao@easycorp.ltd>
|
|
* @package zin
|
|
* @version $Id
|
|
* @link https://www.zentao.net
|
|
*/
|
|
|
|
namespace zin;
|
|
|
|
require_once __DIR__ . DS . 'props.class.php';
|
|
require_once __DIR__ . DS . 'directive.class.php';
|
|
require_once __DIR__ . DS . 'zin.class.php';
|
|
require_once __DIR__ . DS . 'context.class.php';
|
|
require_once __DIR__ . DS . 'selector.func.php';
|
|
require_once __DIR__ . DS . 'dom.class.php';
|
|
|
|
class wg
|
|
{
|
|
/**
|
|
* Define props for the element
|
|
*
|
|
* @var array
|
|
*/
|
|
protected static array $defineProps = array();
|
|
|
|
protected static array $defaultProps = array();
|
|
|
|
protected static array $defineBlocks = array();
|
|
|
|
protected static array $wgToBlockMap = array();
|
|
|
|
protected static array $definedPropsMap = array();
|
|
|
|
private static array $pageResources = array();
|
|
|
|
/**
|
|
* The props of the element
|
|
*
|
|
* @access public
|
|
* @var props
|
|
*/
|
|
public props $props;
|
|
|
|
public array $blocks = array();
|
|
|
|
public ?wg $parent = null;
|
|
|
|
public string $gid;
|
|
|
|
public bool $displayed = false;
|
|
|
|
protected array $renderOptions = array();
|
|
|
|
public function __construct(/* string|element|object|array|null ...$args */)
|
|
{
|
|
$this->props = new props();
|
|
|
|
$this->gid = 'zin_' . uniqid();
|
|
$this->setDefaultProps(static::getDefaultProps());
|
|
$this->add(func_get_args());
|
|
$this->created();
|
|
|
|
zin::renderInGlobal($this);
|
|
static::checkPageResources();
|
|
|
|
$this->checkErrors();
|
|
}
|
|
|
|
public function __debugInfo(): array
|
|
{
|
|
return $this->toJsonData();
|
|
}
|
|
|
|
public function isDomElement(): bool
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if the element is match any of the selectors
|
|
* @param string|array|object $selectors
|
|
*/
|
|
public function isMatch(string|array|object $selectors): bool
|
|
{
|
|
$list = parseWgSelectors($selectors);
|
|
foreach($list as $selector)
|
|
{
|
|
if(isset($selector->command)) continue;
|
|
if(!empty($selector->id) && $this->id() !== $selector->id) continue;
|
|
if(!empty($selector->tag) && $this->shortType() !== $selector->tag) continue;
|
|
if(!empty($selector->class) && !$this->props->class->has($selector->class)) continue;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Build dom object
|
|
* @return dom
|
|
*/
|
|
public function buildDom(): dom
|
|
{
|
|
$before = $this->buildBefore();
|
|
$children = $this->build();
|
|
$after = $this->buildAfter();
|
|
$options = $this->renderOptions;
|
|
$selectors = (!empty($options) && isset($options['selector'])) ? $options['selector'] : null;
|
|
|
|
return new dom
|
|
(
|
|
$this,
|
|
[$before, $children, $after],
|
|
$selectors,
|
|
(!empty($options) && isset($options['type'])) ? $options['type'] : 'html', // TODO: () may not work in lower php
|
|
(!empty($options) && isset($options['data'])) ? $options['data'] : null,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render widget to html
|
|
* @return string
|
|
*/
|
|
public function render(): string
|
|
{
|
|
$dom = $this->buildDom();
|
|
$result = $dom->render();
|
|
|
|
return is_string($result) ? $result : json_encode($result);
|
|
}
|
|
|
|
public function display(array $options = array()): wg
|
|
{
|
|
zin::disableGlobalRender();
|
|
$this->renderOptions = $options;
|
|
|
|
$dom = $this->buildDom();
|
|
$result = $dom->render();
|
|
$context = context::current();
|
|
$css = $context->getCSS();
|
|
$js = $context->getJS();
|
|
|
|
global $app, $config;
|
|
$zinDebug = null;
|
|
if($config->debug && (!isAjaxRequest() || isAjaxRequest('zin')))
|
|
{
|
|
$zinDebug = data('zinDebug');
|
|
if(is_array($zinDebug))
|
|
{
|
|
$zinDebug['basePath'] = $app->getBasePath();
|
|
if(isset($app->zinErrors)) $zinDebug['errors'] = $app->zinErrors;
|
|
}
|
|
}
|
|
|
|
$rawContent = ob_get_contents();
|
|
if(!is_string($rawContent)) $rawContent = '';
|
|
ob_end_clean();
|
|
|
|
if(is_object($result))
|
|
{
|
|
if($zinDebug && isset($result['zinDebug'])) $result['zinDebug'] = $zinDebug;
|
|
$result = json_encode($result);
|
|
}
|
|
elseif(is_array($result))
|
|
{
|
|
foreach($result as $name => $item)
|
|
{
|
|
if(!isset($item['type']) || $item['type'] !== 'html') continue;
|
|
|
|
$item['data'] = str_replace('/*{{ZIN_PAGE_CSS}}*/', $css, $item['data']);
|
|
$item['data'] = str_replace('/*{{ZIN_PAGE_JS}}*/', $js, $item['data']);
|
|
$item['data'] = str_replace('<!-- {{RAW_CONTENT}} -->', $rawContent, $item['data']);
|
|
|
|
$result[$name]['data'] = $item['data'];
|
|
}
|
|
if($zinDebug && isset($result['zinDebug'])) $result['zinDebug'] = $zinDebug;
|
|
$result = json_encode($result);
|
|
}
|
|
else
|
|
{
|
|
if($zinDebug) $js .= h::createJsVarCode('window.zinDebug', $zinDebug);
|
|
$result = str_replace('/*{{ZIN_PAGE_CSS}}*/', $css, $result);
|
|
$result = str_replace('/*{{ZIN_PAGE_JS}}*/', $js, $result);
|
|
$result = str_replace('<!-- {{RAW_CONTENT}} -->', $rawContent, $result);
|
|
}
|
|
|
|
ob_start();
|
|
echo $result;
|
|
|
|
$this->displayed = true;
|
|
context::destroy();
|
|
return $this;
|
|
}
|
|
|
|
protected function created() {}
|
|
|
|
protected function buildBefore(): array
|
|
{
|
|
return $this->block('before');
|
|
}
|
|
|
|
protected function buildAfter(): array
|
|
{
|
|
return $this->block('after');
|
|
}
|
|
|
|
protected function build(): array|wg|directive
|
|
{
|
|
return $this->children();
|
|
}
|
|
|
|
public function buildEvents(): ?string
|
|
{
|
|
$events = $this->props->events();
|
|
if(empty($events)) return null;
|
|
|
|
$id = $this->id();
|
|
$code = array($this->shortType() === 'html' ? 'const ele = document;' : 'const ele = document.getElementById("' . (empty($id) ? $this->gid : $id) . '");if(!ele)return;const $ele = $(ele); const events = new Set(($ele.attr("data-zin-events") || "").split(" ").filter(Boolean));');
|
|
foreach($events as $event => $bindingList)
|
|
{
|
|
$code[] = "\$ele.on('$event.on.zin', function(e){";
|
|
foreach($bindingList as $binding)
|
|
{
|
|
if(is_string($binding)) $binding = (object)array('handler' => $binding);
|
|
$selector = isset($binding->selector) ? $binding->selector : null;
|
|
$handler = isset($binding->handler) ? trim($binding->handler) : '';
|
|
$stop = isset($binding->stop) ? $binding->stop : null;
|
|
$prevent = isset($binding->prevent) ? $binding->prevent : null;
|
|
$self = isset($binding->self) ? $binding->self : null;
|
|
|
|
$code[] = '(function(){';
|
|
if($selector) $code[] = "const target = e.target.closest('$selector');if(!target) return;";
|
|
else $code[] = "const target = ele;";
|
|
if($self) $code[] = "if(ele !== e.target) return;";
|
|
if($stop) $code[] = "e.stopPropagation();";
|
|
if($prevent) $code[] = "e.preventDefault();";
|
|
|
|
if(preg_match('/^[$A-Z_][0-9A-Z_$\[\]."\']*$/i', $handler)) $code[] = "($handler).call(target,e);";
|
|
else $code[] = $handler;
|
|
|
|
$code[] = '})();';
|
|
}
|
|
$code[] = "});events.add('$event');";
|
|
}
|
|
$code[] = '$ele.attr("data-zin-events", Array.from(events).join(" "));';
|
|
return h::createJsScopeCode($code);
|
|
}
|
|
|
|
|
|
protected function onAddBlock(array|string|wg|directive $child, string $name)
|
|
{
|
|
return $child;
|
|
}
|
|
|
|
protected function onAddChild(array|string|wg|directive $child)
|
|
{
|
|
return $child;
|
|
}
|
|
|
|
protected function onSetProp(array|string $prop, mixed $value)
|
|
{
|
|
if($prop === 'id' && $value === '$GID') $value = $this->gid;
|
|
if($prop[0] === '@')
|
|
{
|
|
$this->setDefaultProps(array('id' => $this->gid));
|
|
context::current()->addWgWithEvents($this);
|
|
}
|
|
$this->props->set($prop, $value);
|
|
}
|
|
|
|
protected function onGetProp(string $prop, mixed $defaultValue): mixed
|
|
{
|
|
return $this->props->get($prop, $defaultValue);
|
|
}
|
|
|
|
public function add($item, string $blockName = 'children')
|
|
{
|
|
if($item === null || is_bool($item)) return $this;
|
|
|
|
if(is_array($item))
|
|
{
|
|
foreach($item as $child) $this->add($child, $blockName);
|
|
return $this;
|
|
}
|
|
|
|
zin::disableGlobalRender();
|
|
|
|
if($item instanceof wg) $this->addToBlock($blockName, $item);
|
|
elseif(is_string($item)) $this->addToBlock($blockName, htmlentities($item));
|
|
elseif(isDirective($item)) $this->directive($item, $blockName);
|
|
else $this->addToBlock($blockName, htmlentities(strval($item)));
|
|
|
|
zin::enableGlobalRender();
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function addToBlock(array|string $name, array|string|null|wg|directive $child = null)
|
|
{
|
|
if(is_array($name))
|
|
{
|
|
foreach($name as $blockName => $blockChildren)
|
|
{
|
|
$this->addToBlock($blockName, $blockChildren);
|
|
}
|
|
return;
|
|
}
|
|
if(is_array($child))
|
|
{
|
|
foreach($child as $blockChild)
|
|
{
|
|
$this->addToBlock($name, $blockChild);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if($child instanceof wg && empty($child->parent)) $child->parent = &$this;
|
|
|
|
if($name === 'children' && $child instanceof wg)
|
|
{
|
|
$blockName = static::getBlockNameForWg($child);
|
|
if($blockName !== null) $name = $blockName;
|
|
}
|
|
|
|
$result = $name === 'children' ? $this->onAddChild($child) : $this->onAddBlock($child, $name);
|
|
|
|
if($result === false) return;
|
|
if($result !== null && $result !== true) $child = $result;
|
|
|
|
if(isset($this->blocks[$name])) $this->blocks[$name][] = $child;
|
|
else $this->blocks[$name] = array($child);
|
|
}
|
|
|
|
public function children(): array
|
|
{
|
|
return $this->block('children');
|
|
}
|
|
|
|
public function block(string $name): array
|
|
{
|
|
$list = array();
|
|
if(isset($this->blocks[$name]))
|
|
{
|
|
$blocks = $this->blocks[$name];
|
|
foreach($blocks as $block)
|
|
{
|
|
$isWg = $block instanceof wg && $block->shortType() === 'wg';
|
|
$block = $isWg ? $block->children() : $block;
|
|
if(is_array($block)) $list = array_merge($list, $block);
|
|
else $list[] = $block;
|
|
}
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
public function hasBlock(string $name): bool
|
|
{
|
|
return isset($this->blocks[$name]);
|
|
}
|
|
|
|
/**
|
|
* Apply directive
|
|
*/
|
|
public function directive(directive &$directive, array|string $blockName)
|
|
{
|
|
$data = $directive->data;
|
|
$type = $directive->type;
|
|
$directive->parent = &$this;
|
|
|
|
if($type === 'prop')
|
|
{
|
|
$this->setProp($data);
|
|
return;
|
|
}
|
|
if($type === 'class' || $type === 'style')
|
|
{
|
|
$this->setProp($type, $data);
|
|
return;
|
|
}
|
|
if($type === 'cssVar')
|
|
{
|
|
$this->setProp('--', $data);
|
|
return;
|
|
}
|
|
if($type === 'html')
|
|
{
|
|
$this->addToBlock($blockName, $directive);
|
|
return;
|
|
}
|
|
if($type === 'text')
|
|
{
|
|
$this->addToBlock($blockName, htmlspecialchars($data));
|
|
return;
|
|
}
|
|
if($type === 'block')
|
|
{
|
|
foreach($data as $blockName => $blockChildren)
|
|
{
|
|
$this->add($blockChildren, $blockName);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function prop(array|string $name, mixed $defaultValue = null): mixed
|
|
{
|
|
if(is_array($name))
|
|
{
|
|
$values = array();
|
|
foreach($name as $index => $propName)
|
|
{
|
|
$values[] = $this->onGetProp($propName, is_array($defaultValue) ? (isset($defaultValue[$propName]) ? $defaultValue[$propName] : $defaultValue[$index]) : $defaultValue);
|
|
}
|
|
return $values;
|
|
}
|
|
|
|
return $this->onGetProp($name, $defaultValue);
|
|
}
|
|
|
|
/**
|
|
* Set property, an array can be passed to set multiple properties
|
|
*
|
|
* @access public
|
|
* @param props|array|string $prop - Property name or properties list
|
|
* @param mixed $value - Property value
|
|
*/
|
|
public function setProp(props|array|string $prop, mixed $value = null)
|
|
{
|
|
if($prop instanceof props) $prop = $prop->toJsonData();
|
|
|
|
if(is_array($prop))
|
|
{
|
|
foreach($prop as $name => $value) $this->setProp($name, $value);
|
|
return $this;
|
|
}
|
|
|
|
if(!is_string($prop) || empty($prop)) return $this;
|
|
|
|
if($prop[0] === '#')
|
|
{
|
|
$this->add($value, substr($prop, 1));
|
|
return $this;
|
|
}
|
|
|
|
$this->onSetProp($prop, $value);
|
|
return $this;
|
|
}
|
|
|
|
public function hasProp(): bool
|
|
{
|
|
$names = func_get_args();
|
|
if(empty($names)) return false;
|
|
foreach($names as $name)
|
|
{
|
|
if(!$this->props->has($name)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function setDefaultProps(array $props)
|
|
{
|
|
if(!is_array($props) || empty($props)) return;
|
|
|
|
foreach($props as $name => $value)
|
|
{
|
|
if($this->props->has($name)) continue;
|
|
$this->setProp($name, $value);
|
|
}
|
|
}
|
|
|
|
public function getRestProps(): array
|
|
{
|
|
return $this->props->skip(array_keys(static::definedPropsList()));
|
|
}
|
|
|
|
public function getDefinedProps(): array
|
|
{
|
|
return $this->props->pick(array_keys(static::definedPropsList()));
|
|
}
|
|
|
|
public function type(): string
|
|
{
|
|
return get_called_class();
|
|
}
|
|
|
|
public function shortType(): string
|
|
{
|
|
$type = $this->type();
|
|
$pos = strrpos($type, '\\');
|
|
return $pos === false ? $type : substr($type, $pos + 1);
|
|
}
|
|
|
|
public function id(): ?string
|
|
{
|
|
return $this->prop('id');
|
|
}
|
|
|
|
public function toJsonData(): array
|
|
{
|
|
$data = array();
|
|
$data['gid'] = $this->gid;
|
|
$data['props'] = $this->props->toJsonData();
|
|
|
|
$data['type'] = $this->type();
|
|
if(str_starts_with($data['type'], 'zin\\')) $data['type'] = substr($data['type'], 4);
|
|
|
|
$data['blocks'] = array();
|
|
foreach($this->blocks as $key => $value)
|
|
{
|
|
foreach($value as $index => $child)
|
|
{
|
|
if($child instanceof wg || (is_object($child) && method_exists($child, 'toJsonData')))
|
|
{
|
|
$value[$index] = $child->toJsonData();
|
|
}
|
|
elseif(isDirective($child, 'html'))
|
|
{
|
|
$value[$index] = $child->data;
|
|
}
|
|
}
|
|
if($key === 'children')
|
|
{
|
|
unset($data['blocks'][$key]);
|
|
$data['children'] = $value;
|
|
}
|
|
else
|
|
{
|
|
$data['blocks'][$key] = $value;
|
|
}
|
|
}
|
|
|
|
if(empty($data['blocks'])) unset($data['blocks']);
|
|
|
|
if(!empty($this->parent)) $data['parent'] = $this->parent->gid;
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Check errors in debug mode.
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function checkErrors()
|
|
{
|
|
global $config;
|
|
if(!isset($config->debug) || !$config->debug) return;
|
|
|
|
$definedProps = static::definedPropsList();
|
|
foreach($definedProps as $name => $definition)
|
|
{
|
|
if($this->hasProp($name)) continue;
|
|
if(isset($definition['default']) && $definition['default'] !== null) continue;
|
|
if(isset($definition['optional']) && $definition['optional']) continue;
|
|
|
|
trigger_error("[ZIN] The property \"$name: {$definition['type']}\" of widget \"{$this->type()}#$this->gid\" is required.", E_USER_ERROR);
|
|
}
|
|
|
|
$wgErrors = $this->onCheckErrors();
|
|
if(empty($wgErrors)) return;
|
|
|
|
foreach($wgErrors as $error)
|
|
{
|
|
if(is_array($error)) trigger_error("[ZIN] $error[0]", count($error) > 1 ? $error[1] : E_USER_WARNING);
|
|
else trigger_error("[ZIN] $error", E_USER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The lifecycle method for checking errors in debug mode.
|
|
*
|
|
* @access protected
|
|
* @return array|null
|
|
*/
|
|
protected function onCheckErrors(): array|null
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public static function getPageCSS(): string|false
|
|
{
|
|
return false; // No css
|
|
}
|
|
|
|
public static function getPageJS(): string|false
|
|
{
|
|
return false; // No js
|
|
}
|
|
|
|
protected static function checkPageResources()
|
|
{
|
|
$name = get_called_class();
|
|
if(isset(static::$pageResources[$name])) return;
|
|
|
|
static::$pageResources[$name] = true;
|
|
|
|
$pageCSS = static::getPageCSS();
|
|
$pageJS = static::getPageJS();
|
|
|
|
if(!empty($pageCSS)) context::css($pageCSS);
|
|
if(!empty($pageJS)) context::js($pageJS);
|
|
}
|
|
|
|
public static function wgBlockMap(): array
|
|
{
|
|
$wgName = get_called_class();
|
|
if(!isset(wg::$wgToBlockMap[$wgName]))
|
|
{
|
|
$wgBlockMap = array();
|
|
if(!empty(static::$defineBlocks))
|
|
{
|
|
foreach(static::$defineBlocks as $blockName => $setting)
|
|
{
|
|
if(!isset($setting['map'])) continue;
|
|
$map = $setting['map'];
|
|
if(is_string($map)) $map = explode(',', $map);
|
|
foreach($map as $name) $wgBlockMap[$name] = $blockName;
|
|
}
|
|
}
|
|
wg::$wgToBlockMap[$wgName] = $wgBlockMap;
|
|
}
|
|
return wg::$wgToBlockMap[$wgName];
|
|
}
|
|
|
|
public static function getBlockNameForWg(wg|string $wg): ?string
|
|
{
|
|
$wgType = ($wg instanceof wg) ? $wg->type() : $wg;
|
|
$wgBlockMap = static::wgBlockMap();
|
|
if(str_starts_with($wgType, 'zin\\')) $wgType = substr($wgType, 4);
|
|
return isset($wgBlockMap[$wgType]) ? $wgBlockMap[$wgType] : null;
|
|
}
|
|
|
|
protected static function definedPropsList(?string $wgName = null): array
|
|
{
|
|
if($wgName === null) $wgName = get_called_class();
|
|
|
|
if(!isset(wg::$definedPropsMap[$wgName]) && $wgName === get_called_class())
|
|
{
|
|
wg::$definedPropsMap[$wgName] = static::parsePropsDefinition(static::$defineProps);
|
|
}
|
|
return wg::$definedPropsMap[$wgName];
|
|
}
|
|
|
|
protected static function getDefaultProps(?string $wgName = null): array
|
|
{
|
|
$defaultProps = array();
|
|
foreach(static::definedPropsList($wgName) as $name => $definition)
|
|
{
|
|
if(!isset($definition['default'])) continue;
|
|
$defaultProps[$name] = $definition['default'];
|
|
}
|
|
return $defaultProps;
|
|
}
|
|
|
|
/**
|
|
* Parse props definition
|
|
* @param $definition
|
|
* @example
|
|
*
|
|
* $definition = array('name', 'desc:string', 'title?:string|element', 'icon?:string="star"');
|
|
* $definition = array('name' => 'mixed', 'desc' => 'string', 'title' => array('type' => 'string|element', 'optional' => true), 'icon' => array('type' => 'string', 'default' => 'star', 'optional' => true))))
|
|
*/
|
|
private static function parsePropsDefinition(array $definition): array
|
|
{
|
|
$parentClass = get_parent_class(get_called_class());
|
|
/**
|
|
* @var array
|
|
*/
|
|
$props = $parentClass ? call_user_func("$parentClass::definedPropsList") : array();
|
|
|
|
if($parentClass !== false && $definition === $parentClass::$defineProps)
|
|
{
|
|
if(!empty(static::$defaultProps) && static::$defaultProps !== $parentClass::$defaultProps)
|
|
{
|
|
foreach($props as $name => $value)
|
|
{
|
|
if(isset(static::$defaultProps[$name]))
|
|
{
|
|
$value['default'] = static::$defaultProps[$name];
|
|
$props[$name] = $value;
|
|
}
|
|
}
|
|
}
|
|
return $props;
|
|
}
|
|
|
|
foreach($definition as $name => $value)
|
|
{
|
|
$optional = false;
|
|
$type = 'mixed';
|
|
$default = (isset($props[$name]) && isset($props[$name]['default'])) ? $props[$name]['default'] : null;
|
|
|
|
if(is_int($name) && is_string($value))
|
|
{
|
|
$value = trim($value);
|
|
if(!str_contains($value, ':'))
|
|
{
|
|
$name = $value;
|
|
$value = '';
|
|
}
|
|
else
|
|
{
|
|
list($name, $value) = explode(':', $value, 2);
|
|
}
|
|
$name = trim($name);
|
|
if($name[strlen($name) - 1] === '?')
|
|
{
|
|
$name = substr($name, 0, strlen($name) - 1);
|
|
$optional = true;
|
|
}
|
|
}
|
|
|
|
if(is_array($value))
|
|
{
|
|
$type = isset($value['type']) ? $value['type'] : $type;
|
|
$default = isset($value['default']) ? $value['default'] : $default;
|
|
$optional = isset($value['optional'])? $value['optional']: $optional;
|
|
}
|
|
else if(is_string($value))
|
|
{
|
|
if(!str_contains($value, '='))
|
|
{
|
|
$type = $value;
|
|
$default = null;
|
|
}
|
|
else
|
|
{
|
|
list($type, $default) = explode('=', $value, 2);
|
|
}
|
|
$type = trim($type);
|
|
|
|
if(is_string($default)) $default = json_decode(trim($default));
|
|
}
|
|
|
|
$props[$name] = array('type' => empty($type) ? 'mixed' : $type, 'default' => $default, 'optional' => $default !== null || $optional);
|
|
}
|
|
|
|
if(static::$defaultProps && (!$parentClass || static::$defaultProps !== $parentClass::$defaultProps))
|
|
{
|
|
foreach(static::$defaultProps as $name => $value)
|
|
{
|
|
if(!isset($props[$name])) continue;
|
|
$props[$name]['default'] = $value;
|
|
}
|
|
}
|
|
return $props;
|
|
}
|
|
}
|