# MVC应用程序和Presenters **注意:Presenters就象通常我们所说的控制器。** 从这一节开始我们将学习如何使用Nette框架创建应用程序。 这一节将学到以下几点。 1、MVC,Nette目录结构和*bootstrap.php*文件 2、如何使用presenter和actions(方法)。 3、如何使用templates(模板)。 3、什么是持久参数。 # Model-View-Controller (MVC) Model-View-Controller是一种软件体系结构,用于将应用程序代码(presenter)与应用程序逻辑代码(model)分离,并与用于在具有图形用户界面的应用程序中显示数据(view)的代码分离。 通过这种方法,应用程序更容易理解,它将简化未来的开发,并使我们能够单独测试应用程序的每个单元。 # Model 模型是数据,它是整个应用的功能基础。 它包含应用程序逻辑。 任何用户操作(登录,将货物插入到购物车,数据库中的值的改变)表示模型动作。 模型正在管理其内部状态,并为外部提供稳定的接口。 通过调用此接口上的方法,我们可以读取或更新其状态。模型的概念表示整个层,不仅仅是一个类。 # View View是一个正在处理显示的应用程序层。 它通常使用模板引擎,并知道如何通过从模型获取数据来呈现每个组件。 # Controller 控制器处理来自用户的请求,调用相关的应用程序逻辑(Model),然后要求View图层显示数据。 在Nette框架中,控制器由presenters表示。 # 目录结构 ~~~ sandbox/ ├── app/ ← 目录与应用程序 │ ├── config/ ← 配置文件 │ │ ├── config.neon ← 主配置文件 │ │ └── config.local.neon │ │ │ ├── forms/ ← 表单类 │ ├── model/ ← 模型层及其类 │ ├── presenters/ ←控制器类 │ │ ├── HomepagePresenter.php ← Homepage控制器 │ │ └── templates/ ← 模板目录 │ │ ├── @layout.latte ←模板的共享布局 │ │ └── Homepage/ ← Homepage模板目录 │ │ └── default.latte ← default方法模板 │ ├── router/ ← 路由器类 │ │ │ └── bootstrap.php ← 应用启动文件 │ ├── log/ ← 包含日志,错误等 ├── temp/ ←用于临时文件,缓存,... │ ├── vendor/ ← 目录与库(例如第三方组件) │ ├── nette/ ← 全部 Nette Framework 组件 │ │ └── nette/Nette ← Nette Framework本身由Composer安装 │ ├── ... │ │ │ └── autoload.php ← 脚本,用于处理已安装软件包中所有类的自动加载 │ └── www/ ← 公共目录,项目的文档根 ├── .htaccess ← mod_rewrite的规则 ├── index.php ← 触发应用程序 └── images/ ← images, styles,文件夹 .. ~~~ 此外,在一些目录中,有.htaccess和web.config文件,它让浏览器(对于Apache或IIS)不能直接访问。 并且你不能从您的浏览器直接到达app /和libs /目录。 请不要忘记向目录log /和temp /授予写权限(chmod 0777)。 # index.php & bootstrap.php 浏览器通过一个且只有一个文件发送所有请求,这个文件就位于公共目录**www/**中的**index.php**。 它只传递控制到应用程序(即app /目录),引导文件**bootstrap.php**。 在这里目录结构只是一个建议。 你可以很容易地将其更改为任何你想要的目录。 所以你需要做的是在bootstrap.php中重命名目录和更改路径。现在我们去了解一下bootstrap.php文件工作程序。 首先,文件加载Nette框架和库: ~~~ require __DIR__ . '/../vendor/autoload.php'; ~~~ Configurator配置创建所谓的DI上下文并处理应用程序初始化(DI配置后面会说到)。 ~~~ $configurator = new Nette\Configurator; ~~~ 在严格模式下激活调试器和日记记录器: ~~~ //$configurator->setDebugMode(TRUE); $configurator->enableDebugger(__DIR__ . '/../log'); ~~~ 设置临时文件的目录 ~~~ $configurator->setTempDirectory(__DIR__ . '/../temp'); ~~~ 激活自动加载,将自动加载所有文件与相关类: ~~~ $configurator->createRobotLoader() ->addDirectory(__DIR__) ->addDirectory(__DIR__ . '/../vendor/others') ->register(); ~~~ 并根据配置文件生成一个DI容器。 一切都取决于它 作为调用app / bootstrap.php的结果,我们将返回这个DI容器。 ~~~ $configurator->addConfig(__DIR__ . '/config/config.neon'); $configurator->addConfig(__DIR__ . '/config/config.local.neon'); return $configurator->createContainer(); ~~~ 我们将它作为局部变量存储在www/index.php中并运行应用程序: ~~~ $container = require __DIR__ . '/../app/bootstrap.php'; $container->getService('application')->run(); ~~~ 以上就是bootstrap.php和index.php工作方式。 # 处理presenter和方法。 那么如何处理程序请求呢? 每一个对我们应用程序的请求都将通过index.php和bootstrap.php传递给$application对象。但是这个对象不理解http请求,所以它会要求路由器翻译请求。路由器将查看请求,并告诉需要什么样presenter ,以及应该执行什么样action。 例如,路由器回应,用户想要presenter里Product类方法为show(这是一个好习惯,就像Product:show),并传递参数id = 123。 这是最终可以理解的$application,它将继续完成请求。它将创建一个ProductPresenter类的对象。表示Product控制器。 (为了完全准确,应用程序要求presenterFactory服务创建presenter)。 此新控制器将被要求执行操作(与参数id一起显示)。 控制器是接收请求,路由器翻译并计算给答案的对象。 这可以是HTML页面,图像,XML文档,文件,JSON,重定向或任何您需要的。 具体来说,ProductPresenter将查询模型的数据,并将该数据传递给模板进行显示。 这通常在方法renderShow中完成,其中单词Show必须匹配操作的名称,并且请求参数id将作为参数传递给方法: ~~~ class ProductPresenter extends Nette\Application\UI\Presenter { public function renderShow($id) { // 我们将从模型中获取数据并将其传递给模板 $this->template->product = $this->model->getProduct($id); } } ~~~ 如果你真的想要,你可以通过调用$ this-> request-> getParameters()获得所有GET请求参数的数组,但通常你不需要它们,而是使用路由和动作参数。 同样,你可以通过调用$ this-> request-> getPost()来获取所有接收到的POST参数。 通常,你也不需要这个方法。 大多数情况下,你正在处理表单请求,并且有一个表单组件。 然后控制器将渲染模板。 ~~~ 停~~!可能大家看不明白上面的意思,他实际要做一个实例,想要显示id为123的产品,怎么做呢?我们先要新建一个类:ProductPresenter,这个分类有一个方法,用来显示用户指定ID的产品。所以就要这样一个方法public function renderShow($id)。明白了吧。意思教你怎么新建控制器。 ~~~ ![](https://box.kancloud.cn/a1b60040d192e35b976f27ee7d6367cd_629x382.png) **跟我们一步一步来** 我们想要新建一个product控制器来管理商品,所以我们就要在presenters文件夹下面新建一个ProductPresenters.php如下面图 ![](https://box.kancloud.cn/90ce986dcd95a8d168c22eef33a36c4f_286x417.png) 他的代码如下。 ~~~ <?php namespace App\Presenters; use Nette; use App\Model; class ProductPresenter extends BasePresenter { public function renderShow() { } } ~~~ 在里面我们新建一个show方法。但是里面是空的。现在我们先不要管他,我们打开URL ~~~ http://localhost/Nette/sandbox/www/product/show ~~~ 从上面URL大家明白这个URL怎么产生了吧!以后路由内容会详细说明。 现在我们打开后发现出错,如下图 ![](https://box.kancloud.cn/853d4ee65f81e10c730ee196fae56ae7_1255x452.png) 怎么会出错呢?可能细心的人看到后面提示了。没有show.latte.这就是模板了,好的,我们照他的路径新建show.latte.里面代码是空。 我们再次刷新。发现不再出错了,但是是空白,所以我们再模板填入几个字。随意了。再次刷新就出现填入的文字了。 好的,模板方法也明白了吗?! 好的,如果我们要在控制里做一个变量。传到模板里显示呢。很简单,我们先在ProductPresenter里的show方法加入 ~~~ <?php //?presenter=Product&action=detail&id=123 namespace App\Presenters; use Nette; use App\Model; class ProductPresenter extends BasePresenter { public function renderShow() { $this->template->sp = '笔记本'; } } ~~~ 我们在show.latte里加入 ~~~ {$sp} ~~~ 再次刷新 ![](https://box.kancloud.cn/4ced0124853ed7cda0d34eba820f9c7d_704x176.png) 看,显示变量了,好的,我们跟着教程走,下面就是模板内容了! # 模板 控制器将从一些简单的规则产生模板文件的路径。 还要求进一步显示Product控制器和show方法,如果这些文件中的一个存在就不会出现错误: ~~~ - templates/Product/show.latte - templates/Product.show.latte ~~~ 来解说一下,我们刚才是不是新建了一个ProductPresenter控制器,记得我们只取Presenter前面字母也就是Product为路径,那他是不是有一个方法renderShow,这个方法我们就取render后面的字母为文件名,所以就产生了相对映模板就是templates/Product/show.latte 记下这个了!我们小实例也说明了这些内容。 除了以上找模板之外控制器将尝试搜索布局(这是可选的): 这个一般很少用。 ~~~ - templates/Product/@layout.latte - templates/Product.@layout.latte - templates/@layout.latte layout shared by multiple presenters ~~~ 控制器及其组件马上向模板传递了一些有用的变量: 这些变量是公共变量,如CSS或JS路径,文件路径。这些大家一定要记下来! ~~~ $basePath 是到根dir的绝对URL路径(例如/ CD-collection) $baseUrl 是到root dir的绝对URL(例如http:// localhost / CD-collection) $user 是表示用户的对象 $presenter 是当前的控制器 $control是当前组件或控制器 $flashes 方法发送的消息列表flashMessage() ~~~ 小总结,从上面这一小节看出来Nette框架创建应用程序其实不是很难! 他的创建过程就是当您请求方法时,例如:Homepage:default,然后 1、将创建类HomepagePresenter的对象 2、renderDefault()方法将被调用(如果它存在) 3、模板,例如templates / Homepage / default.latte与布局(例如templates/@layout.latte)将一起组合渲染视图。 可是可能大家发现照教程操作会发现出错。过不去,现在我来说一下, 大家跟我一步一步来: 我们新建ProductPresenter.php控制器,代码如下 ~~~ <?php namespace App\Presenters; use Nette; use App\Model; class ProductPresenter extends Nette\Application\UI\Presenter { public function renderShow($id) { $this->template->productId = 15; //原来教程代码$this->template->product = $this->model->getProduct($id);现在我们修改成以上代码。 //因为我们还没有学到模型。 //一般这个productId是数组,现在为了让大家能看明白我直接给一个数值上去。 } } ~~~ 然后在show.latte里加入以下代码 ~~~ <a n:href="Product:show $productId">product detail</a> ~~~ 现在可以刷新网页 ![](https://box.kancloud.cn/7a8c6a84ecbc00953e74f1a1eec7dcc0_688x218.png) 我们查一下product detail连接, ~~~ http://localhost/Nette/sandbox/www/product/show?id=15 ~~~ 从这里大家可能明白了吧。 现在看起来在Nette Framework中创建应用程序将是象玩孩子的游戏这么容易。 # 模块 有时我们要开发复杂的应用程序时,我们可以将控制器和模板的目录分为子目录。 我们称之为模块。 就象网站有后台模块和前台模块之分。 如果我们的应用程序将包含例如模块Front和Admin,其结构就象如下所示: ~~~ sandbox/ app/ ← 目录与应用程序 AdminModule/ ← Admin 模块 presenters/ ← 控制器 templates/ ← 视图模板 FrontModule/ ← Front 模块 presenters/ ← 控制器 templates/ ← 视图模板 ... ~~~ 模块不必以平面结构排列,您甚至可以创建子模块。 象上面例子中的ProductPresenter.php如果他存在Front模块中。那么相对show方法就要修改**Front:Product:show**和** ProductPresenter**将放在**FrontModule**命名空间中: ~~~ namespace FrontModule; //FrontModule代表路径。 class ProductPresenter extends \Nette\Application\UI\Presenter { // ... } ~~~ # 创建链接 链接的创建属于Nette框架的最强的功能。 由于双向路由,您不必对您的网址进行硬编码或轻松组合。 你可以只引用控制器的方法,并传递他们的参数,框架将自己生成URL。 创建链接与调用函数一样简单。 当编程和编码模板时,我们不必关心URL设计,我们将直接参考控制器的方法,例如上面已经提到的Product:show。 这点上面实例也说明白了。 # 模板中的链接 通常我们在模板中创建链接。 为了使它尽可能容易,框架提供了三个宏。 最聪明的是**n:href** ~~~ <a n:href="Product:show $productId">product detail</a> ~~~ 注意,我们使用n:href代替HTML属性href。 它的值不是一个URL,就像你习惯使用href属性一样,是一个控制器的方法。 语法是 ~~~ [Presenter:]action [,] [arg1] [, arg2] [, ...] ~~~ 点击链接后,方法ProductPresenter :: renderShow()将获得它的单词,作为参数$ id将获得$ productId的值。 我们可以以同样的方式传递更多的参数,就像我们调用一个方法一样。 它会变得更容易吗? 除此之外,甚至可以传递命名参数。 下一个链接传递具有值cs的参数lang: ~~~ <a n:href="Product:show $productId, lang => cs">product detail</a> ~~~ 虽然方法renderShow在其声明中没有$ lang,它可以通过调用$ lang = $ this-> getParameter('lang')读取此参数的值。 如果我们有一个数组中的所有参数,我们可以用(expand)运算符展开它们: ~~~ {var $args = [$productId, lang => cs]} <a n:href="Product:show (expand) $args">product detail</a> ~~~ 如果我们正在创建链接的模板是Product Presenter的一部分,我们可以省略控制器的名字,直接写n:href =“show $ productId”。 类似地,如果一个链接指向一个名为default的动作,你可以跳过它,并写n:href =“Product:$ id”(不要忘记冒号)。 链接甚至可以引用其他模块。 这里我们这样区分,如果它“相对”地指向子模块,或者“绝对地”指向不同的模块,则路径以冒号开头。 让我们假设实际的presenter是模块Front的一部分,然后我们将写: ~~~ <a n:href="Shop:Product:show">link for Front:Shop:Product:show</a> <a n:href=":Admin:Product:show">link for Admin:Product:show</a> ~~~ 生成的链接采用绝对路径格式。 当您想要生成包含域的绝对链接(例如http://example.com)时,只需在开头n:href =“// show $ productId”处提供两个斜杠。 如果我们将presenter中的属性$ absoluteUrls设置为TRUE,默认情况下所有的链接都是绝对的。 我们可以使用所谓的片段或锚来引用页面上的特定部分,使用#符号: ~~~ <a n:href="show#comments">link to Product:show and fragment #comments</a> ~~~ 如果我们创建一个HTML标签`<a>`,宏n:href非常方便。 当我们想在其他地方有这个链接时,例如在模板的文本中,我们可以使用具有相同内部语法的{link}宏: ~~~ The address is: {link Product:show $productId} ~~~ {ifCurrent $ link} ... {/ ifCurrent}是一个条件语句。 如果链接引用当前页,则执行标签内的块; 否则丢弃。 典型的用例是将CSS类添加到活动链接。 ~~~ <!-- use cases --> <a href="{link edit, 10}">edit</a> <ul class="menu"> <li><a href="{link Default:default}">...</a></li> <li><a href="{link}">...</a></li> ... <li {ifCurrent Default:default}class="active"{/ifCurrent}><a href="{link Default:default}">...</a></li> <!-- scope expanding --> <li {ifCurrent Default:*}class="active"{/ifCurrent}><a href="{link Default:default}">...</a></li> </ul> <!-- negation --> {ifCurrent Admin:login}{else}<a href="{link Admin:login}">Sign in!</a>{/ifCurrent} ~~~ [有关Latte模板语法](https://latte.nette.org/en/macros) 不过有了前面实例说明,我想大家认真读起来很易理解链接怎样产生。 # 在控制器中的链接 控制器和组件有方法链接,可以在模板中创建链接对象。 第一个参数是presenters目标操作,后面是传递的参数: ~~~ $url = $this->link(destination [,arg [,arg ...]]); ~~~ 它们也可以使用数组传递。 例如: ~~~ $url = $this->link('Product:show', $productId); $url = $this->link('Product:show', [$productId, 'lang' => 'en']); ~~~ 如果在生成链接时传递FALSE作为方法参数,它将不会被添加到链接。 解决方案是使用默认值TRUE或FALSE定义此参数。 Nette然后将理解它的类型是布尔型,它将作为1或0传递到url,并在控制器中处理时转换回布尔值。 # 无效链接 有时发生的一种情况是,我们要创建一个无效的链接 - 或者是因为它引用了一个不存在的控制器或者方法和参数,或者当目标方法不能生成一个URL时 。 如何由静态变量Presenter :: $ invalidLinkMode处理无效链接确定。 它可以有这些值(常数)之一: * Presenter :: INVALID_LINK_SILENT - 静默模式,以符号#作为网址返回 * presenter:: INVALID_LINK_WARNING - 将生成E_USER_WARNING * Presenter::INVALID_LINK_TEXTUAL –视觉警告,错误文本显示在链接中 * Presenter::INVALID_LINK_EXCEPTION –将抛出InvalidLinkException 生产模式中的默认设置为INVALID_LINK_WARNING,在开发模式下为INVALID_LINK_WARNING | INVALID_LINK_TEXTUAL。 INVALID_LINK_WARNING不会在生产环境中终止脚本,但会记录该警告。 在开发环境中,Tracy将拦截警告并显示错误蓝屏。 如果设置了INVALID_LINK_TEXTUAL,控制器和组件会返回错误消息,网址为#error:。 为了使这样的链接可见,我们可以添加一个CSS规则到我们的样式表: ~~~ a[href^="#error:"] { background: red; color: white; } ~~~ 如果我们不想在开发环境中生成警告,我们可以在config.neon配置中打开静默无效链接模式。 ~~~ application: silentLinks: yes ~~~ # 持久参数 除了经典参数,我们现在知道,有所谓的持久参数。 这些是非常重要的方式不同:它们通过请求自动传递。 这意味着,我们不必在链接中明确提及它们,但它们仍然被传递。 当您的应用程序具有多种语言变体时,在每个链接中传递实际语言将是令人难以置信的疲劳。你可以简单地将$ lang参数标记为persistent,如下所示: ~~~ class ProductPresenter extends Presenter { /** @persistent */ public $lang; ... ~~~ 如果参数$ lang的实际值为“en”,则进入链接 ~~~ <a n:href="Product:show $productId">product detail</a> ~~~ 参数lang => en将被自动传递。但是我们也可以添加参数lang改变它的值: ~~~ <a n:href="Product:show $productId, lang => cs">detail in Czech language</a> ~~~ 嗯。。学过对象一看就明白了。如上面这个$lang他就是ProductPresenter类的公共属性,所以他下面的方法都会自动自联到$lang这个变量数值。 该参数将在ProductPresenter的对象中的类变量$ lang中访问,并且可以通过$ this-> lang访问。 我们甚至可以将persistent参数的默认值设置为presenter类。 如果参数值将对应于默认值,则不会在URL中传递。 持久变量必须声明为public。 持久性反映了控制器类的层次结构,因此在特定控制器中定义的参数将在从其继承的每个控制器中被自动考虑。 #控制器的生命周期 我们现在知道,控制器方法导致调用render <Action>方法,因此例如renderShow。 它的生命周期如下图一样。 ![](https://box.kancloud.cn/27a729ccb83e24df9b0e1129d22da1ce_341x480.gif) # startup() 方法startup()在创建控制器之后立即被调用。 它初始化变量或检查用户权限。 如果你编写自己的startup()方法,不要忘了调用其父parent :: startup()。 # action<Action>() 类似于方法render<View>()。 有些情况下,控制器执行一些特定的操作(验证用户,写入数据库),然后重定向到其他地方。 调用render渲染是不合适的,因为没有渲染。 正因为如此,有一个被称为action的替代。 重要的是要知道方法action <Action>()在render <View>()之前被调用。 它甚至可以决定更改什么render方法被调用,通过调用$ this-> setView('otherView')(renderOtherView()将被调用)。 # handle<Signal>() 方法处理所谓的信号aka子请求。 主要用于组件和处理AJAX请求。 表格的处理也在这个级别上执行。 # beforeRender() 方法beforeRender,顾名思义,在render <View>()之前被调用,它可以包含例如模板的设置,将变量传递给模板common以获得更多的视图等等。 # render<View>() 通常将所需数据传递到模板 # shutdown() 在控制器生命周期结束时调用。 # 终止控制器 我们还可以在其生命周期的任何时候终止控制器。 我们通常这样做是为了防止渲染模板。 * 直接通过方法终止控制器:$presenter->terminate() * 终止呈现者并立即呈现模板:$presenter->sendTemplate() * 终止控制器和调度有效载荷:$presenter->sendPayload()(在 AJAX里) * 终止控制器和发送自己的响应:$presenter->sendResponse($response) 控制器BadRequestException也可以通过重定向或通过投掷来终止 以上内容也很少用到。一般处理是重定向。 # 重定向 有两种方法redirect()和forward()重定向另一个控制器。其具有类似于所提及的语法link()例如,在提交表单和写入数据库后,我们将通过调用重定向到产品的详细信息: ~~~ $this->redirect('Product:show', $id); ~~~ forward()将传递到新的方法没有重定向,redirect()将重定向浏览器与HTTP代码302或303.如果我们想使用不同的代码,我们将它放在控制器的方法的名称之前 : ~~~ $this->redirect(301, 'Product:show', $id); ~~~ 要转到应用程序之外的网址,您可以使用redirectUrl()进行重定向: ~~~ $this->redirectUrl('https://nette.org'); ~~~ 重定向通过抛出所谓的终止异常Nette \ Application \ AbortException立即终止控制器的活动。 有时,在重定向之前,我们想发送一个所谓的弹出消息。 这个消息,将在模板中重定向后出现。 # 错误404 当我们无法满足请求时,因为例如数据库中的记录丢失,我们将抛出一个异常Nette \ Application \ BadRequestException,它代表HTTP错误404: ~~~ public function renderShow($id) { $product = $this->model->getProduct($id); if (!$product) { throw new Nette\Application\BadRequestException; } // ... } ~~~ 当用户没有被授权查看页面时,我们将抛出一个Nette \ Application \ ForbiddenRequestException异常(错误403)。 可以使用Nette \ Application \ BadRequestException的第二个参数返回其他错误代码。 # 弹出信息 这些是通知某些操作的结果的消息。 弹出消息的一个重要功能是,即使在重定向后,它们也可以在模板中使用。 即使显示,它们可存在三秒钟 ,如果用户无意中刷新页面,消息不会丢失。 只需调用flashMessage()方法,控制器将消息传递到模板。 第一个参数是消息的文本,第二个可选参数是其类型(错误,警告,信息等)。 方法flashMessage()返回一个弹出消息的实例,以允许我们添加更多信息。 ~~~ public function deleteFormSubmitted($form) { // ... 模型要求删除记录 ... //删除成功 // 我们将传递弹出信息 $this->flashMessage('Item was removed.'); $this->redirect(...); // 自动跳转 } ~~~ 这些消息在模板变量$ flashes中可用作匿名对象,包含消息和类型属性,甚至可以包含前面提到的用户定义的信息。 它们可以像这样渲染: ~~~ {foreach $flashes as $flash} <div class="flash {$flash->type}">{$flash->message}</div> {/foreach} ~~~ # 模型类的用法 正如我们之前所说的,模型是一个由许多类组成的整个层,每个类都处理应用程序逻辑的一部分。 如果我们有一个模型App \ Articles,它将负责加载和保存文章,我们可能会在使用依赖注入的控制器中要求它,假设我们已经在DI Container中正确注册为服务。 ~~~ class ArticlePresenter extends Presenter { /** @var \App\Articles @inject */ public $articles; public function renderShow($id) { $this->template->article = $this->articles->find($id); } } ~~~ 刚刚发生了什么? 让我们一起去。 正如我们前面所说的,PresenterFactory创建了PresenterFactory的实例,并且这个类在创建之后做了什么,它检查Presenter是否有一些公共属性,用@inject注释,如果是这样,它试图找到一个 服务,它实现或是类型的实例,即在@var中,并将此服务从DI Container传递到属性。 由于这种机制,我们可以立即使用服务,不用关心它来自或试图实例化它,因为我们确信它将被传递到属性,只要它正确地注册在DI容器。 你只能注入类的公共属性。 # 控制器与组件 当我们谈论控制器时,在组件这一术语下,我们通常指的是Control类的后代。 更准确的是使用术语“控制器”, **Nette\Application\UI\Presenter**本身,是Control类的后代,因此组件和控制器之间存在很大的相似性。 但是UI \ Control(甚至UI \ Presenter)主要是一个所谓的组件容器。 这意味着你可以添加到其他组件。 就像我们添加到表单输入(文本字段,按钮,...)。你可以通过括号访问它们: ~~~ // 将组件附加到控制器中 $presenter['mymenu'] = new MenuControl; // 从控制器中获取组件并呈现它 $presenter['mymenu']->render(); ~~~ 将组件附加到控制器(绑定它们),您将能够: * 在组件中创建链接 * 使用 [signals](https://doc.nette.org/en/2.4/components#toc-signal-aka-subrequest) * 在组件中使用持久性参数 当您不需要这些功能时,您就不必绑定组件与控制器。 # 组件工厂 组件工厂是一个用来创建组件优雅的方式,只有真的需要(惰性/按需)组件。就执行createComponent<Name>()方法调用,<Name>是组件的名称,将创建和返回所需的组件。 然后将该组件附加到控制器中。createComponent<Name>()方法是可选的传递$name的组件,正在创建,作为参数。 ~~~ class DefaultPresenter extends Nette\Application\UI\Presenter { public function renderDefault() { $menu = $this['menu']; // 访问组件 // 如果这是第一次访问,方法createComponentMenu()将被调用 // ... } protected function createComponentMenu() { // 创建和配置组件 $menu = new MenuControl; $menu->items = $this->item; // 和运行它 return $menu; } } ~~~ 组件名称的第一个字母始终为小写,尽管工厂名称中的第一个字母为大写。 因为所有组件都是在单独的方法中创建的,所以代码更干净,更容易阅读。 我们从来不直接调用工厂,当我们第一次使用组件时,它们被自动调用。 由于这样,一个组件在适当的时刻创建,并且只有当它真的需要时。 如果我们不使用组件(例如在一些AJAX请求,我们只返回部分页面,或者部分缓存),它甚至不会被创建,我们保存服务器的性能。 您可以使用宏{control}访问和呈现组件。 因此,不需要手动地将组件传递到模板。 ~~~ <h2>Edit form</h2> {control editForm} ~~~ 您可以在单独的页面上阅读有关组件的[更多详细信息](https://doc.nette.org/en/2.4/components)。 # 持久组件 不仅参数,而且组件都可以持久化。 它们的状态然后在跳到另一个控制器时传递,就像持久参数一样。 我们用这个注释标记持久化组件(这里我们标记组件日历和菜单): ~~~ /** * @persistent(calendar, menu) */ class DefaultPresenter extends Presenter { // ... } ~~~ 你不必将子组件标记为持久化,它们会自动持久化。 # 在哪里可以得到一些组件? 在页面加载项,插件和组件上,您可以找到由Nette Framework社区制作和共享的一些[开源组件](https://addons.nette.org/)。 # errorPresenter 默认值:Nette:Error 此参数指定在发生错误时调用的Presenter。 我们可以在 sandbox中找到一个例子。 如果在执行错误提示器时发生错误,Tracy将仅显示一个简单的错误页面。 # catchExceptions 默认值:在生产模式下为TRUE,在调试模式下为FALSE 此参数决定是否调用错误提示程序。 通过在调试模式下将该值更改为TRUE,我们可以测试错误提示器的功能。 # mapping 默认值:*:* Module \ * Presenter(在 sandbox:* App * * Module \ Presenters \ * Presenter) 此参数包含具有将控制器名称转换为类名称的规则的数组。 在键中,我们定义一个模块名称或*作为其他模块的回退。 该值包含规则掩码: ~~~ application: mapping: Admin: App\Admin\*Module\*Presenter *: App\*Module\Presenters\*Presenter ~~~ 控制器Front:Blog:文章包含在Front模块内。 由于此模块没有显式映射,将使用带星号的映射,类将被称为App \ FrontModule \ BlogModule \ Presenters \ ArticlePresenter。 控制器管理:用户:编辑是管理模块的一部分,它有单独的映射,所以控制器类的名称将是App \ Admin \ UserModule \ EditPresenter(请注意,由于管理员已经在映射定义,它将被剥离 从结果类名)。 # silentLinks 默认值:FALSE 当链接生成失败时,此选项指定调试模式下的Nette行为。 使用FALSE值Nette将触发E_USER_WARNING。 将此配置选项更改为TRUE将抑制此错误。 在生产环境中,将始终触发E_USER_WARNING。 我们还可以使用类属性Presenter :: $ invalidLinkMode来调整此行为。 # scan* Nette允许自动注册控制器作为服务。 此步骤提高了控制器创建速度。 有几个配置选项可以调整Nette在哪里和如何查找它们。 # scanDirs 默认值: %appDir% (/app) 此值包含一个包含目录的数组,Nette查找控制器。 如果我们在配置中指定另一个值,它将与默认值合并: ~~~ application: scanDirs: - foo ~~~ 在这种情况下,scanDirs将包含值%appDir%和foo。 如果我们要禁用或覆盖此配置选项,我们需要使用! 修饰语: ~~~ application: scanDirs!: - foo ~~~ 现在scanDirs只包含foo。 # scanComposer 默认值:TRUE 此参数指定Nette是否应在Composer类映射中查找控制器。 # scanFilter 默认值:Presenter 指定必须包含在文件和类名称中的字符串。