sy / component
将应用程序构建为更简单组件的树
Requires
- php: >=5.6.0
- sy/debug: ^1
- sy/http: ^1
- sy/template: ^2
- sy/translate: ^1.0
Requires (Dev)
- phpunit/phpunit: ^11
README
组件的概念允许您将应用程序构建为更简单组件的树(组合设计模式)。每个组件都可以重用。
基本上,我们可以假设组件是一个可字符串化的对象。在这个阶段,它只是使用模板生成字符串,可以是任何格式(html、xml、json、纯文本等...)。在此基础上,我们可以通过添加css和js属性来构建Web组件。
Sy\Component类是其他Web组件(sy/webcomponent)和HTML页面及元素(sy/html)如表单和表格等的基类...
组件模板引擎
使用的模板引擎是sy/template
模板语法概念:插槽和块
使用setVar方法填充插槽的示例,您的PHP脚本
<?php $c = new Sy\Component(); $c->setTemplateFile(__DIR__ . '/template.tpl'); $c->setVar('NAME', 'World'); echo $c;
模板文件,template.tpl
Hello {NAME}
输出结果
Hello World
使用setBlock方法的示例,您的PHP脚本
<?php $c = new Sy\Component(); $c->setTemplateFile(__DIR__ . '/template.tpl'); for ($i = 0; $i < 10; $i++) { $c->setVar('VALUE', $i); $c->setBlock('MY_BLOCK'); } echo $c;
模板文件,template.tpl
<!-- BEGIN MY_BLOCK --> Block {VALUE} <!-- END MY_BLOCK -->
输出结果
Block 0
Block 1
Block 2
Block 3
Block 4
Block 5
Block 6
Block 7
Block 8
Block 9
ELSE块
当块未设置时,您可以使用ELSE块来显示默认内容。
<!-- BEGIN MY_BLOCK --> Block content <!-- ELSE MY_BLOCK --> Block not parsed <!-- END MY_BLOCK -->
使用独立变量设置块
默认情况下,当setBlock方法的第二个参数为空时,它将使用全局设置的变量在块作用域内设置插槽。使用第二个参数,可以使用独立变量在块作用域内设置插槽。
<?php $c = new Sy\Component(); $c->setTemplateContent('<!-- BEGIN A -->{SLOT}<!-- END A -->'); $c->setVar('SLOT', 'Hello'); foreach (['Foo', 'Bar', 'Baz'] as $v) { $c->setBlock('A', ['SLOT' => $v]); } $c->setBlock('A'); echo $c;
结果
FooBarBazHello
使用数据数组设置块
class A extends Sy\Component { public function __construct() { parent::__construct(); $this->setTemplateFile(__DIR__ . '/template.html'); // setBlocks will set a block for each line in the data array $this->setBlocks('foo', [ ['firstname' => 'John', 'lastname' => 'Doe', 'age' => 32], ['firstname' => 'John', 'lastname' => 'Wick', 'age' => 42], ['firstname' => 'Jane', 'lastname' => 'Doe', 'age' => 25], ['firstname' => 'Bob', 'lastname' => 'Doe'], ]); } } echo new A();
模板文件,template.html
Nb persons: {FOO_COUNT} <!-- BEGIN FOO_BLOCK --> <div> Index: {FOO_INDEX} Firstname: {FOO_FIRSTNAME} Lastname: {FOO_LASTNAME} <!-- BEGIN FOO_AGE_BLOCK --> Age: {FOO_AGE} <!-- ELSE FOO_AGE_BLOCK --> Unknown age <!-- END FOO_AGE_BLOCK --> </div> <!-- END FOO_BLOCK -->
输出
Nb persons: 3
<div>
Index: 1
Firstname: John
Lastname: Doe
Age: 32
</div>
<div>
Index: 2
Firstname: John
Lastname: Wick
Age: 42
</div>
<div>
Index: 3
Firstname: Jane
Lastname: Doe
Age: 25
</div>
<div>
Index: 4
Firstname: Bob
Lastname: Doe
Unknown age
</div>
替代模板语法
可以使用简单的PHP模板语法。必须指定您正在使用PHP模板文件
<?php $c = new Sy\Component(); // use a php template file with the second parameter $c->setTemplateFile(__DIR__ . '/template.tpl', 'php'); $c->setVar('NAME', 'World'); echo $c;
PHP模板文件,template.tpl
Hello <?php echo $NAME ?>
输出结果
Hello World
创建组件
从Sy\Component类派生一个自定义类。
例如在Hello.php
<?php use Sy\Component; class Hello extends Component { public function __construct($name = 'world') { $this->setTemplateFile(__DIR__ . '/Hello.tpl'); $this->setVar('NAME', $name); } }
Hello.tpl
Hello {NAME}!
使用您的组件
<?php // echo 'Hello world!' $hello = new Hello(); echo $hello; // echo 'Hello toto!' $hello = new Hello('toto'); echo $hello;
在另一个组件中添加组件
使用setVar方法在另一个组件中添加组件。
<?php $c = new Sy\Component(); $c->setTemplateFile(__DIR__ . '/template.tpl'); $c->setVar('NAME', new Hello());
组件动作
actionDispatch方法可以帮助您调用动作方法。
此方法接受两个参数
- actionName: $_REQUEST变量名,index.php?action=foo
- defaultMethod: 这一个是可选的,如果没有调用动作,它将执行此方法
动作方法名称必须以'Action'结尾:fooAction
例如在MyComponent.php
<?php use Sy\Component; class MyComponent extends Component { public function __construct() { parent::__construct(); $this->setTemplateFile(__DIR__ . '/MyComponent.tpl'); // if $_REQUEST['action'] is not set, call initAction $this->actionDispatch('action', 'init'); } public function initAction() { } public function fooAction() { } }
组件翻译器
可以在组件中添加翻译器。每个翻译器将从一个指定目录中的文件加载翻译数据。此翻译文件必须命名为检测到的语言。例如,如果检测到的语言是"fr",则PHP翻译器将尝试加载"fr.php"。Gettext翻译器将尝试加载"fr.mo"。
此功能由库sy/translate提供
语言检测
将使用以下顺序中的变量来检测语言
- $_SESSION['sy_language']
- $_COOKIE['sy_language']
- $_SERVER['HTTP_ACCEPT_LANGUAGE']
翻译方法
- void Component::addTranslator(string $directory [, string $type = 'php', string $lang = ''])
- string Component::_(mixed $values)
示例
<?php use Sy\Component; class MyComponent extends Component { public function __construct() { parent::__construct(); $this->setTemplateFile(__DIR__ . '/tpl/mycomponent.tpl'); // Add a translator, it will look for translation file into specified directory $this->addTranslator(__DIR__ . '/lang', 'php', 'fr'); // Use translation method $this->setVar('SLOT1', $this->_('Hello world')); $this->setVar('SLOT2', $this->_('This is %s', 'an apple')); $this->setVar('SLOT3', $this->_('This is %s', 'an pineapple')); $this->setVar('SLOT4', $this->_('Number of %d max', 10)); } } echo new MyComponent();
PHP翻译文件
<?php return array( 'Hello world' => 'Bonjour monde', 'This is %s' => 'Ceci est %s', 'an apple' => 'une pomme', 'a pineapple' => 'un ananas', 'Number of %d max' => 'Nombre de %d max', );
模板文件
{"Hello world"}
{"No traduction"}
{SLOT1}
{SLOT2}
{SLOT3}
{SLOT4}
输出结果
Bonjour monde
No traduction
Bonjour monde
Ceci est une pomme
Ceci est an pineapple
Nombre de 10 max
添加多个翻译器
可以在组件中添加多个翻译器。添加的顺序很重要,因为翻译过程将在找到第一个翻译数据后立即停止。
将翻译器传输到内部Web组件
当在Web组件A中添加Web组件B时,A的所有翻译器都将添加到B中。
<?php use Sy\Component; class A extends Component { public function __construct() { $this->mount(function () { $this->addTranslator(__DIR__ . '/lang', 'php', 'fr'); $this->setTemplateContent('<a>{HELLO/} {"world"} {B}</a>'); $this->setVars([ 'HELLO' => $this->_('hello'), 'B' => new B() ]); }); } } class B extends Component { public function __construct() { $this->mount(function () { $this->setTemplateContent('<b>{HELLO/} {"world"} {C}</b>'); $this->setVars([ 'HELLO' => $this->_('hello'), 'C' => new C() ]); }); } } class C extends Component { public function __construct() { $this->mount(function () { $this->addTranslator(__DIR__ . '/lang/alt', 'php', 'fr'); $this->setTemplateContent('<c>{HELLO/} {"world"}</c>'); $this->setVars([ 'HELLO' => $this->_('hello'), ]); }); } } echo new A();
在组件A中添加的翻译文件
return array( 'hello' => 'bonjour', 'world' => 'monde', );
在组件C中添加的翻译文件
return array( 'hello' => 'salut', );
输出结果
<a>bonjour monde <b>bonjour monde <c>salut monde</c></b></a>
A的翻译器被传输到B和C。C将优先使用自己的翻译器。
在这里我们需要使用mount方法来注册在渲染阶段之前触发的mount事件的回调。这是因为我们需要确保所有叶子组件都从其父组件接收到了翻译器。