教程 2:INVO 项目讲解(Tutorial 2: Explaining INVO)
In this second tutorial, we’ll explain a more complete application in order to deepen the development with Phalcon. INVO is one of the applications we have created as samples. INVO is a small website that allows their users
to generate invoices, and do other tasks such as manage their customers and products. You can clone its code from Github.
Also, INVO was made with Bootstrap as client-side framework. Although the application does not generate invoices, it still serves as an example to understand how the framework works.
在第二个例子中,为了更好的使用Phalcon进行开发我们提供了一个更完整的应用。INVO是我们所书写的例子程序,这样做仅是为了更好的向您展示Phalcon的强大之处。INVO是一个小的站点,这个站点允许用户生订单,做一些其它的工作比如管理客户和产品。我们可以在Github上下载此代码。
项目结构(Project Structure)
Once you clone the project in your document root you’ll see the following structure:
项目的结构如下:
invo/
app/
app/config/
app/controllers/
app/library/
app/models/
app/plugins/
app/views/
public/
public/bootstrap/
public/css/
public/js/
schemas/
As you know, Phalcon does not impose a particular file structure for application development. This project provides a simple MVC structure and
a public document root.
Once you open the application in your browser
http://localhost/invo you’ll see something like this:
正如我们所知,Phalcon不强制用户使用某种特定的文档结构进行开发。这个项目提供了一个简单的MVC结构目录和一个公共文档根目录。当你在浏览器里输入http://localhost/invo时会显示如下信息:
The application is divided into two parts, a frontend, that is a public part where visitors can receive information about INVO and request contact
information. The second part is the backend, an administrative area where a registered user can manage his/her products and customers.
这个应用分为两端,一个前端,这个是访问者的公有访问在端,访客可以从其中接收到有关INVO的信息和联系信息。第二个就是后端,即是管理端,注册的管理员可以管理他自己的产品及用户。
路由(Routing)?
INVO uses the standard route that is built-in with the Router component. These routes match the following pattern: /:controller/:action/:params.
This means that the first part of a URI is the controller, the second the action and the rest are the parameters.
The following route /session/register executes the controller SessionController and its action registerAction.
INVO使用框架内置的路由。这些路由监视了形如如下的URI:/:controller/:action/:params. 从此我们可以看出第一个部分即是控制器,第二部分即是action第三个部分即是其它参数。/session/register这个路由选项会执行SessionController这个控制器中的registerAction方法。
配置(Configuration)?
INVO has a configuration file that sets general parameters in the application. This file is read in the first few lines of the bootstrap file
(public/index.php):
INVO里有一个配置文件,这个配置文件里设置了一些应用中所使用的参数。这个文件在启动文件的前端进行了加载:
<?php
//Read the configuration
$config =
new Phalcon\Config\Adapter\Ini(‘../app/config/config.ini‘);
Phalcon\Config allows us to manipulate the file in an object-oriented way. The
configuration file contains the following settings:
\Phalcon\Config允许我们以面象的方式来管理配置文件。这个配置文件中包含了如下配置文件:
[database]
host =
localhost
username =
root
password =
secret
name =
invo
[application]
controllersDir =
/../app/controllers/
modelsDir =
/../app/models/
viewsDir =
/../app/views/
pluginsDir =
/../app/plugins/
libraryDir =
/../app/library/
baseUri =
/invo/
;[metadata]
;adapter = "Apc"
;suffix = my-suffix
;lifetime = 3600
Phalcon hasn’t any pre-defined convention settings. Sections help us to organize the options as appropriate. In this file there are three sections
to be used later.
Phalcon并没有任何的预设置项。这些片段帮助我们更好的组织配置项。这里有三个我们以后会用到的配置段。
自动加载(Autoloaders)?
The second part that appears in the bootstrap file (public/index.php) is the autoloader. The autoloader registers a set of directories in which
the application will look for the classes that it eventually will need.
启动文件中第二部分即是自动加载器。这个自动加载器注册了一批目录,这些目录中的类我们以后可能会遇到。
<?php
$loader =
new \Phalcon\Loader();
$loader->registerDirs(
array(
$config->application->controllersDir,
$config->application->pluginsDir,
$config->application->libraryDir,
$config->application->modelsDir,
)
)->register();
Note that the above code has registered the directories that were defined in the configuration file. The only directory that is not registered
is the viewsDir, because it contains HTML + PHP files but no classes.
注意上面的代码注册了一些目录,这些目录我们在配置文件中已经设置了。这里唯一一个没有注册的目录即是viewsDir因为这个目录中不包含类只是一个html或是html及php的混合文件。
处理请求(Handling the Request)?
If we skip to the end of the file, the request is finally handled by Phalcon\Mvc\Application which initializes and executes all that is necessary
to make the application run:
看启动文件的最后,这个请求最终会使用\Phalcon\Mvc\Application初始化并执行所有必要的操作:
<?php
$app =
new \Phalcon\Mvc\Application($di);
echo
$app->handle()->getContent();
依赖注入(Dependency Injection)?
Look at the first line of the code block above, the Application class constructor is receiving the variable $di as an argument. What is the purpose
of that variable? Phalcon is a highly decoupled framework, so we need a component that acts as glue to make everything work together. That component is Phalcon\DI. It is a service container that also performs dependency injection, instantiating all components
as they are needed by the application.
There are many ways of registering services in the container. In INVO, most services have been registered using anonymous functions. Thanks to
this, the objects are instantiated in a lazy way, reducing the resources needed by the application.
For instance, in the following excerpt the session service is registered. The anonymous function will only be called when the application requires
access to the session data:
观察前面的代码我们在Application的构造器中设置了一个$di作为参数。设置这个参数的目录的是什么?Phalcon是一个高度松耦合的框架所以我们需要一个组件来扮演一个连接器的角度,以使所有的组件能够共同协作。这个组件即是\Phalcon\DI. 这个组件即是一个一个容器,其中执行了了依赖注入的工作,它会依据应用的需求实例化组件。
有很多的方法可以注册服务到服务容器里。INVO中我们使用了匿名的方式注册了服务。由于此服务对象即使用迟加载技术(需要时才加载),这减少的资源的占用。
例如下面的session服务注册。使用匿名函数的方式进行注册服务,使得只在使用session时才调用匿名函数:
<?php
//Start the session the first time a component requests the session service
$di->set(‘session‘,
function() {
$session =
new Phalcon\Session\Adapter\Files();
$session->start();
return
$session;
});
Here, we have the freedom to change the adapter, perform additional initialization and much more. Note that the service was registered using the
name “session”. This is a convention that will allow the framework to identify the active service in the services container.
A request can use many services and registering each service individually can be a cumbersome task. For that reason, the framework provides a
variant of Phalcon\DI called Phalcon\DI\FactoryDefault whose task is to register all services providing a full-stack framework.
在这里我们可以非常自由的修改适配器,执行额外的初始化工作。注意这里我们使用session为名注册了服务。这样做可以让我们在以后的应用中直接使用此名进行服务的调用。通常在应用中我们会使用逐个的注册非常多的服务,这个会一个非常烦的工作。基于此Phalcon框架提供了一个\Phalcon\DI的变体\Phalcon\DI\FactoryDefault,
这个东西为我们注册许多作为一个全功能框架的所必须的一些服务。
<?php
// The FactoryDefault Dependency Injector automatically registers the
// right services providing a full-stack framework
$di =
new \Phalcon\DI\FactoryDefault();
It registers the majority of services with components provided by the framework as standard. If we need to override the definition of some service
we could just set it again as we did above with “session”. This is the reason for the existence of the variable $di.
其注册了Phalcon作为一个框架所需要的标准服务。如果我们需要改写此服务只需要重新设置相关的服务即可,比如session服务。这也是$di变量存在的原因。
登录应用(Log into the Application)?
A “log in” facility will allow us to work on backend controllers. The separation between backend controllers and frontend ones is only logical.
All controllers are located in the same directory (app/controllers/).
To enter the system, users must have a valid username and password. Users are stored in the table “users” in the database “invo”.
Before we can start a session, we need to configure the connection to the database in the application. A service called “db” is set up in the
service container with the connection information. As with the autoloader, we are again taking parameters from the configuration file in order to configure a service:
登陆功能可以让我们工作进行一些后台的操作。前后台控制器的区分只是逻辑上的。所有的控制器都放在了同一个目录(app/controllers)。要进入系统,用户必须要输入用户名及密码。用户信息在invo数据库的users表中。 在我们使用此session之前,我们需要配置数据库连接。名为db的服务被我们注册到了服务容器里(当然其中设置的有数据库连接信息)。正如autoloader一样,我们从配置文件中即得了服务的配置信息。
<?php
// Database connection is created based on parameters defined in the configuration file
$di->set(‘db‘,
function()
use ($config) {
return
new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" =>
$config->database->host,
"username" =>
$config->database->username,
"password" =>
$config->database->password,
"dbname" =>
$config->database->name
));
});
Here, we return an instance of the MySQL connection adapter. If needed, you could do extra actions such as adding a logger, a profiler or change
the adapter, setting it up as you want.
The following simple form (app/views/session/index.phtml) requests the login information. We’ve removed some HTML code to make the example more
concise:
这里我们的匿名函数中返回了一个连接的造配器。如果需要我们可以添加一些其它的操作,如日志,性能测试,或是修改配置等。
接下来的简单表单(app/views/sesson/index.phtml)需要登陆信息。我们去除了一些html的代码以使得代码更简洁:
<?php
echo
$this->tag->form(‘session/start‘)
?>
<label
for="email">Username/Email</label>
<?php
echo
$this->tag->textField(array("email",
"size" =>
"30"))
?>
<label
for="password">Password</label>
<?php
echo
$this->tag->passwordField(array("password",
"size" =>
"30"))
?>
<?php
echo
$this->tag->submitButton(array(‘Login‘))
?>
</form>
The SessionController::startAction function (app/controllers/SessionController.php) has the task of validating the data entered in the form including
checking for a valid user in the database:
SessionController::startAction主要处理用户输入的数据的验证(与数据库进行对比):
<?php
class SessionController
extends ControllerBase
{
// ...
private
function
_registerSession($user)
{
$this->session->set(‘auth‘,
array(
‘id‘ =>
$user->id,
‘name‘ =>
$user->name
));
}
public
function
startAction()
{
if ($this->request->isPost())
{
//Receiving the variables sent by POST
$email =
$this->request->getPost(‘email‘,
‘email‘);
$password =
$this->request->getPost(‘password‘);
$password = sha1($password);
//Find the user in the database
$user = Users::findFirst(array(
"email = :email: AND password = :password: AND active = ‘Y‘",
"bind" =>
array(‘email‘ =>
$email,
‘password‘ =>
$password)
));
if ($user
!= false) {
$this->_registerSession($user);
$this->flash->success(‘Welcome
‘ . $user->name);
//Forward to the ‘invoices‘ controller if the user is valid
return
$this->dispatcher->forward(array(
‘controller‘ =>
‘invoices‘,
‘action‘ =>
‘index‘
));
}
$this->flash->error(‘Wrong
email/password‘);
}
//Forward to the login form again
return
$this->dispatcher->forward(array(
‘controller‘ =>
‘session‘,
‘action‘ =>
‘index‘
));
}
}
For simplicity, we have used “sha1”
to store the password hashes in the database, however, this algorithm is not recommended in real applications, use “bcrypt” instead.
Note that multiple public attributes are accessed in the controller like: $this->flash, $this->request or $this->session. These are services defined
in the services container from earlier. When they’re accessed the first time, they are injected as part of the controller.
These services are shared, which means that we are always accessing the same instance regardless of the place where we invoke them.
For instance, here we invoke the “session” service and then we store the user identity in the variable “auth”:
为简单起见我们使用sha1算法来对密码进行hash操作之后保存到数据库中,当然在现实的工作中我们不会使用此方法,一般会使用bcrypt.
<?php
$this->session->set(‘auth‘,
array(
‘id‘ =>
$user->id,
‘name‘ =>
$user->name
));
保护后端(Securing the Backend)?
The backend is a private area where only registered users have access. Therefore, it is necessary to check that only registered users have access
to these controllers. If you aren’t logged into the application and you try to access, for example, the products controller (which is private) you will see a screen like this:
后端只有注册用户才可访问。因此必须要检查只有注册过的用户才可以访问后台的控制器。如果你未登陆应用,比如访问未经授权的控制器(如products控制器)会显示如下界面:
Every time someone attempts to access any controller/action, the application verifies that the current role (in session) has access to it, otherwise
it displays a message like the above and forwards the flow to the home page.
Now let’s find out how the application accomplishes this. The first thing to know is that there is a component called
Dispatcher. It is informed about the route found by the
Routing component. Then, it is responsible for loading the appropriate controller and execute the corresponding action method.
Normally, the framework creates the Dispatcher automatically. In our case, we want to perform a verification before executing the required action,
checking if the user has access to it or not. To achieve this, we have replaced the component by creating a function in the bootstrap:
用户在访问controller/action时程序会检查当前的角色是否有权限访问指定的controller/action, 否则显示如下的界面,展示了一条提示信息然后把访问定向到主页。
现在让我们来看应用是如何完成这个的。这里我们首先要看的是即是一个名为Dispatcher的组件。路由会在route组件里进行处理。然后路由模块会分发请求到指定的控制器中进行处理。正常情况下框架会自动的创建分发器。在这个例子中我们执行了一个验证,以检验用户是否有权限访问指定的controller/action.为实现这个我们对分发器进行了替换以使得我们能够执行这些验证。
<?php
$di->set(‘dispatcher‘,
function()
use ($di) {
$dispatcher =
new Phalcon\Mvc\Dispatcher();
return
$dispatcher;
});
We now have total control over the Dispatcher used in the application. Many components in the framework trigger events that allow us to modify
their internal flow of operation. As the Dependency Injector component acts as glue for components, a new component called
EventsManager allows us to intercept the events produced by a component, routing the events to listeners.
在应用中我们对Dispatcher有完全的控制权。许多组件允许我们以触发事件的方式改变程序的执行流程。正如DI作为连接器连接各组件一样Phalcon中一个名为EventsManager的组件允许我们处理组件成产生的事件,并路由这些事件到事件的监听器(处理器)。
事件管理(Events Management)?
An
EventsManager allows us to attach listeners to a particular type of event. The type that interests us now is “dispatch”. The following code filters all
events produced by the Dispatcher:
EventsManager(事件管理器)允许我们绑定监听器到指定的事件。这里我们关心的事件是dispatch。下面的代码过滤了所有经由Dispatcher的事件:
<?php
$di->set(‘dispatcher‘,
function()
use ($di) {
//Obtain the standard eventsManager from the DI
$eventsManager =
$di->getShared(‘eventsManager‘);
//Instantiate the Security plugin
$security =
new Security($di);
//Listen for events produced in the dispatcher using the Security plugin
$eventsManager->attach(‘dispatch‘,
$security);
$dispatcher =
new Phalcon\Mvc\Dispatcher();
//Bind the EventsManager to the Dispatcher
$dispatcher->setEventsManager($eventsManager);
return
$dispatcher;
});
The Security plugin is a class located at (app/plugins/Security.php). This class implements the method “beforeDispatch”. This is the same name
as one of the events produced in the Dispatcher:
这个Security插件被我们放在了app/plugins/Security.php中。这个类实现了beforeDispatch方法,这个方法与Dispatcher中的一个事件名相同:
<?php
use Phalcon\Events\Event,
Phalcon\Mvc\User\Plugin,
Phalcon\Mvc\Dispatcher,
Phalcon\Acl;
class Security
extends Plugin
{
// ...
public
function
beforeDispatch(Event
$event, Dispatcher
$dispatcher)
{
// ...
}
}
The hook events always receive a first parameter that contains contextual information of the event produced ($event) and a second one that is
the object that produced the event itself ($dispatcher). It is not mandatory that plugins extend the class Phalcon\Mvc\User\Plugin, but by doing this they gain easier access to the services available in the application.
Now, we’re verifying the role in the current session, checking if the user has access using the ACL list. If the user does not have access we
redirect to the home screen as explained before:
这个钩子方法的第一个参数即是产生事件的上下文信息,第二个参数即是产生此事件的类本身(这里好是$dispatcher). Phalcon里强制继承\Phalcon\Mvc\Uesr\Plugin类,但如果用户继承此类的话则会让我们直接访问到应用中的其它服务。现在我们检测当前session中的数据看一下当前用户是否有访问权限,如果没有则直接跳转到主页面:
<?php
use Phalcon\Events\Event,
Phalcon\Mvc\User\Plugin,
Phalcon\Mvc\Dispatcher,
Phalcon\Acl;
class Security
extends Plugin
{
// ...
public
function
beforeExecuteRoute(Event
$event, Dispatcher
$dispatcher)
{
//Check whether the "auth" variable exists in session to define the active role 查看当前的session中是否有auth,以设置当前的用户身份
$auth =
$this->session->get(‘auth‘);
if (!$auth)
{
$role =
‘Guests‘;
} else {
$role =
‘Users‘;
}
//Take the active controller/action from the dispatcher从dispatcher中取当前的controller/action
$controller =
$dispatcher->getControllerName();
$action =
$dispatcher->getActionName();
//Obtain the ACL list取ACL列表
$acl =
$this->getAcl();
//Check if the Role have access to the controller (resource)检测当前角色是否有权限访问控制器
$allowed =
$acl->isAllowed($role,
$controller,
$action);
if ($allowed
!= Acl::ALLOW) {
//If he doesn‘t have access forward him to the index controller如果没有访问权限则重定向到index控制器
$this->flash->error("You
don‘t have access to this module");
$dispatcher->forward(
array(
‘controller‘ =>
‘index‘,
‘action‘ =>
‘index‘
)
);
//Returning "false" we tell to the dispatcher to stop the current operation
return
false;
}
}
}
提供 ACL 列表(Providing an ACL list)?
In the above example we have obtained the ACL using the method $this->_getAcl(). This method is also implemented in the Plugin. Now we are going
to explain step-by-step how we built the access control list (ACL):
上面的例子中我们使用$this->getAcl()取得了ACL信息。这个方法也可以在一个插件中实现。现在我们会一步步的解释如何创建ACL:
<?php
//Create the ACL创建ACL
$acl =
new Phalcon\Acl\Adapter\Memory();
//The default action is DENY access设置默认行为为\Phalcon\Acl:DENY
$acl->setDefaultAction(Phalcon\Acl::DENY);
//Register two roles, Users is registered users
//and guests are users without a defined identity
$roles =
array(
‘users‘ =>
new Phalcon\Acl\Role(‘Users‘),
‘guests‘ =>
new Phalcon\Acl\Role(‘Guests‘)
);
foreach ($roles
as
$role) {
$acl->addRole($role);
}
Now we define the resources for each area respectively. Controller names are resources and their actions are accesses for the resources:
现在我们为不同的角色定义不同的资源。控制器即是资源,控制器的action即是用来访问这些资源的。
<?php
//Private area resources (backend)
$privateResources =
array(
‘companies‘ =>
array(‘index‘,
‘search‘,
‘new‘,
‘edit‘,
‘save‘,
‘create‘,
‘delete‘),
‘products‘ =>
array(‘index‘,
‘search‘,
‘new‘,
‘edit‘,
‘save‘,
‘create‘,
‘delete‘),
‘producttypes‘ =>
array(‘index‘,
‘search‘,
‘new‘,
‘edit‘,
‘save‘,
‘create‘,
‘delete‘),
‘invoices‘ =>
array(‘index‘,
‘profile‘)
);
foreach ($privateResources
as
$resource =>
$actions) {
$acl->addResource(new
Phalcon\Acl\Resource($resource),
$actions);
}
//Public area resources (frontend)
$publicResources =
array(
‘index‘ =>
array(‘index‘),
‘about‘ =>
array(‘index‘),
‘session‘ =>
array(‘index‘,
‘register‘,
‘start‘,
‘end‘),
‘contact‘ =>
array(‘index‘,
‘send‘)
);
foreach ($publicResources
as
$resource =>
$actions) {
$acl->addResource(new
Phalcon\Acl\Resource($resource),
$actions);
}
The ACL now have knowledge of the existing controllers and their related actions. Role “Users” has access to all the resources of both frontend
and backend. The role “Guests” only has access to the public area:
现在ACL已经知了已有的控制器及他们相关的action. Users这个角色不仅可以访问前台的角色也可以访问后台的角色。Guests角色仅能访问前台的公共资源:
<?php
//Grant access to public areas to both users and guests给用户和访客角色分配公共区的访问权限
foreach ($roles
as
$role) {
foreach ($publicResources
as
$resource =>
$actions) {
$acl->allow($role->getName(),
$resource,
‘*‘);
}
}
//Grant access to private area only to role Users
仅授予Users后台访问权限:
foreach ($privateResources
as
$resource =>
$actions) {
foreach ($actions
as
$action) {
$acl->allow(‘Users‘,
$resource,
$action);
}
}
Hooray!, the ACL is now complete.
OK,至此ACL已经完成了。
用户组件(User Components)?
All the UI elements and visual style of the application has been achieved mostly through
Bootstrap. Some elements, such as the navigation bar changes according to the state of the application. For example, in the upper right corner,
the link “Log in / Sign Up” changes to “Log out” if an user is logged into the application.
This part of the application is implemented in the component “Elements” (app/library/Elements.php).
这个例子中的所有可视元素绝大多数是使用Bootstrap实现的。一些html控件,比如说导航条会根据应用的状态进行改变。例如,右上角的部分会在用户已经登陆时显示为Log out当用户未登陆时显示Login/Sign up。这部分的应用在组件Elements(app/library/Elements.php)里实现的。
<?php
use Phalcon\Mvc\User\Component;
class Elements
extends Component
{
public
function
getMenu()
{
//...
}
public
function
getTabs()
{
//...
}
}
This class extends the Phalcon\Mvc\User\Component, it is not imposed to extend a component with this class, but it helps to get access more quickly
to the application services. Now, we register this class in the services container:
这个类扩展自\Phalcon\Mvc\User\Component, 不过这并不是强制的,扩展后我们可以访问应用的其它资源:
<?php
//Register an user component
$di->set(‘elements‘,
function(){
return
new Elements();
});
As controllers, plugins or components within a view, this component also has access to the services registered in the container and by just accessing
an attribute with the same name as a previously registered service:
像控制器,插件,或是组件等一样可以在视图中非常容易的使用一样,这个组件也可以访问DI容器中注册的服务,只需要使用前面注册的服务名直接访问即可。
<div
class="navbar navbar-fixed-top">
<div
class="navbar-inner">
<div
class="container">
<a
class="btn btn-navbar"
data-toggle="collapse"
data-target=".nav-collapse">
<span
class="icon-bar"></span>
<span
class="icon-bar"></span>
<span
class="icon-bar"></span>
</a>
<a
class="brand"
href="#">INVO</a>
<?php
echo
$this->elements->getMenu()
?>
</div>
</div>
</div>
<div
class="container">
<?php
echo
$this->getContent()
?>
<hr>
<footer>
<p>© Company 2012</p>
</footer>
</div>
The important part is:
最重要的部分是:
<?php
echo
$this->elements->getMenu()
?>
CRUD 的使用(Working with the CRUD)?
Most options that manipulate data (companies, products and types of products), were developed using a basic and common
CRUD (Create, Read, Update and Delete). Each CRUD contains the following files:
这里我们使用CRUD来操作数据(这个有些废话)。每个CRUD操作包含如下文件:
invo/
app/
app/controllers/
ProductsController.php
app/models/
Products.php
app/views/
products/
edit.phtml
index.phtml
new.phtml
search.phtml
Each controller has the following actions:
每个控制器包含如下action:
<?php
class ProductsController
extends ControllerBase
{
/**
* The start action, it shows the "search" view
*/
public
function
indexAction()
{
//...
}
/**
* Execute the "search" based on the criteria sent from the "index"
* Returning a paginator for the results
*/
public
function
searchAction()
{
//...
}
/**
* Shows the view to create a "new" product
*/
public
function
newAction()
{
//...
}
/**
* Shows the view to "edit" an existing product
*/
public
function
editAction()
{
//...
}
/**
* Creates a product based on the data entered in the "new" action
*/
public
function
createAction()
{
//...
}
/**
* Updates a product based on the data entered in the "edit" action
*/
public
function
saveAction()
{
//...
}
/**
* Deletes an existing product
*/
public
function
deleteAction($id)
{
//...
}
}
搜索表单(The Search Form)?
Every CRUD starts with a search form. This form shows each field that has the table (products), allowing the user creating a search criteria from
any field. Table “products” has a relationship to the table “products_types”. In this case, we previously queried the records in this table in order to facilitate the search by that field:
每个CRUD操作组以一个search表单开始。这个表单展示数据库表中的字段,允许用户为每个字段创建一个搜索条件。products表和products_types表存在关系。这个例子中我们先查询相关的记录以展示出相关数据以便搜索:
<?php
/**
* The start action, it shows the "search" view
*/
public
function
indexAction()
{
$this->persistent->searchParams
= null;
$this->view->productTypes
= ProductTypes::find();
}
All the “product types” are queried and passed to the view as a local variable “productTypes”. Then, in the view (app/views/index.phtml) we show
a “select” tag filled with those results:
所有的产品类型都被查询之后保存了一个名为productTypes的变量里然后传给了视图。然后在视图文件中我们使用select标签显示结果:
<div>
<label
for="product_types_id">Product Type</label>
<?php
echo
$this->tag->select(array(
"product_types_id",
$productTypes,
"using" =>
array("id",
"name"),
"useDummy" =>
true
)) ?>
</div>
Note that $productTypes contains the data necessary to fill the SELECT tag using Phalcon\Tag::select. Once the form is submitted, the action “search”
is executed in the controller performing the search based on the data entered by the user.
注意$productTypes里包含了填充\Phalcon\Tag::select所必须的数据。一旦表单被提交search action即会依据用户所输入的数据进行执行。
执行搜索(Performing a Search)?
The action “search” has a dual behavior. When accessed via POST, it performs a search based on the data sent from the form. But when accessed
via GET it moves the current page in the paginator. To differentiate one from another HTTP method, we check it using the
Request component:
search action有两个动作。当用使用post的方式访问此action时会依据输入的数据进行搜索。当以get的方式进行访问时则会返回当前页的数据。为了区分两个请示方式我们使用Request组件进行检测:
<?php
/**
* Execute the "search" based on the criteria sent from the "index"
* Returning a paginator for the results
*/
public
function
searchAction()
{
if ($this->request->isPost())
{
//create the query conditions
} else {
//paginate using the existing conditions
}
//...
}
With the help of
Phalcon\Mvc\Model\Criteria, we can create the search conditions intelligently based on the data types and values sent from the form:
当我们使用\Phalcon\Mvc\Model\Criteria时, Phalcon会非常智能的依据其数据类型创建搜索条件:
<?php
$query = Criteria::fromInput($this->di,
"Products",
$_POST);
This method verifies which values are different from “” (empty string) and null and takes them into account to create the search criteria:
?
If the field data type is text or similar (char, varchar, text, etc.) It uses an SQL “like” operator to filter the results.
?
If the data type is not text or similar, it’ll use the operator “=”.
Additionally, “Criteria” ignores all the $_POST variables that do not match any field in the table. Values are automatically escaped using “bound
parameters”.
Now, we store the produced parameters in the controller’s session bag:
当值不同时所返回的搜索条件也是不同的这个基于如下的条件:
如果字段类型为文本或是近似的类型时则查询条件设置时会使用like
如果字段类型为非文本时则会使用等号(=)。
另外Criteria会忽略$_POST中与数据库不相配(不存在)的字段。查询条件会自动的使用参数绑定的方式进行sql 注入攻击防范。现在我们把生成的参数放到了控制器里的session bag(局部session)里。
<?php
$this->persistent->searchParams
= $query->getParams();
A session bag, is a special attribute in a controller that persists between requests. When accessed, this attribute injects a
Phalcon\Session\Bag service that is independent in each controller.
Then, based on the built params we perform the query:
session包,即是一个控制器的特殊属性变量这里持续化了一些数据,这些数据仅能在此控制器中使用不像全局session一样可以在所有的控制器里使用。通俗的讲即是局限于单个controller的session. 当我们访问此变量时控制器中会立即注入了一个\Phalcon\Session\Bag的实例。然后基本创建的参数执行查询操作:
<?php
$products = Products::find($parameters);
if (count($products) ==
0) {
$this->flash->notice("The
search did not found any products");
return
$this->forward("products/index");
}
If the search doesn’t return any product, we forward the user to the index action again. Let’s pretend the search returned results, then we create
a paginator to navigate easily through them:
如果未返回任何搜索结果,我们会再次的重定向用户到index action.假定搜索可以返回数据,我们创建一个paginator , 这样我们可以非常通过此来访问查询的结果:
<?php
$paginator =
new Phalcon\Paginator\Adapter\Model(array(
"data" =>
$products,
//Data to paginate待分页的数据
"limit" =>
5,
//Rows per page每页几行
"page" =>
$numberPage
//Active page当前页
));
//Get active page in the paginator取当前页
$page =
$paginator->getPaginate();
Finally we pass the returned page to view:
最后我们把page传给视图:
<?php
$this->view->setVar("page",
$page);
In the view (app/views/products/search.phtml), we traverse the results corresponding to the current page:
在视图(app/views/products/search.phtml)中,我们可以在当前的页面中遍历数据:
<?php
foreach ($page->items
as
$product) {
?>
<tr>
<td><?=
$product->id
?></td>
<td><?=
$product->getProductTypes()->name
?></td>
<td><?=
$product->name
?></td>
<td><?=
$product->price
?></td>
<td><?=
$product->active
?></td>
<td><?=
$this->tag->linkTo("products/edit/"
. $product->id,
‘Edit‘)
?></td>
<td><?=
$this->tag->linkTo("products/delete/"
. $product->id,
‘Delete‘)
?></td>
</tr>
<?php }
?>
创建和更新记录(Creating and Updating Records)?
Now let’s see how the CRUD creates and updates records. From the “new” and “edit” views the data entered by the user are sent to the actions “create”
and “save” that perform actions of “creating” and “updating” products respectively.
In the creation case, we recover the data submitted and assign them to a new “products” instance:
现在我们来看Phalcon里提供的一个简易的CRUD工具是如何创建和更新数据的(这里的简易CRUD可以使用Phalcon Develop tools进行生成)。
<?php
/**
* Creates a product based on the data entered in the "new" action
*/
public
function
createAction()
{
$products =
new Products();
$products->id
= $this->request->getPost("id",
"int");
$products->product_types_id
= $this->request->getPost("product_types_id",
"int");
$products->name
= $this->request->getPost("name",
"striptags");
$products->price
= $this->request->getPost("price",
"double");
$products->active
= $this->request->getPost("active");
//...
}
Data is filtered before being assigned to the object. This filtering is optional, the ORM escapes the input data and performs additional casting
according to the column types.
When saving we’ll know whether the data conforms to the business rules and validations implemented in the model Products:
数据在赋值之前进行了过滤。这个过滤是可选的,ORM会对输入的数据进行转义然后根据对应表中的数据类型执行转换。不难发现无论是业务规则还是验证操作全在products这个模型中进行的:
<?php
/**
* Creates a product based on the data entered in the "new" action生成一条新记录
*/
public
function
createAction()
{
//...
if (!$products->create())
{
//The store failed, the following messages were produced
foreach ($products->getMessages()
as
$message) {
$this->flash->error((string)
$message);
}
return
$this->forward("products/new");
} else {
$this->flash->success("Product
was created successfully");
return
$this->forward("products/index");
}
}
Now, in the case of product updating, first we must present to the user the data that is currently in the edited record:
在更新数据的例子中,我们首先要先展示待编辑的数据给用户:
<?php
/**
* Shows the view to "edit" an existing product
*/
public
function
editAction($id)
{
//...
$product = Products::findFirstById($id);
$this->tag->setDefault("id",
$product->id);
$this->tag->setDefault("product_types_id",
$product->product_types_id);
$this->tag->setDefault("name",
$product->name);
$this->tag->setDefault("price",
$product->price);
$this->tag->setDefault("active",
$product->active);
}
The “setDefault” helper sets a default value in the form on the attribute with the same name. Thanks to this, the user can change any value and
then sent it back to the database through to the “save” action:
setDefault(帮助)方法,可以设置同名的方法以默认的值。基于此,用户可以修改数据后直接保存结果到数据库中:
<?php
/**
* Updates a product based on the data entered in the "edit" action
*/
public
function
saveAction()
{
//...
//Find the product to update
$id =
$this->request->getPost("id");
$product = Products::findFirstById($id);
if (!$product)
{
$this->flash->error("products
does not exist " .
$id);
return
$this->forward("products/index");
}
//... assign the values to the object and store it
}
动态更改标题(Changing the Title Dynamically)?
When you browse between one option and another will see that the title changes dynamically indicating where we are currently working. This is
achieved in each controller initializer:
当我们在网页中穿梭时我们会看到网面的标题会根据页面的不同而不同。这个东西在Phalcon中是在控制器的initialize方法中实现的(当然也可以在action方法中实现):
<?php
class ProductsController
extends ControllerBase
{
public
function
initialize()
{
//Set the document title
$this->tag->setTitle(‘Manage
your product types‘);
parent::initialize();
}
//...
}
Note, that the method parent::initialize() is also called, it adds more data to the title:
注意上面的例子中父类的initialize()方法也会执行,这样就可以添加更多的信息到title中了:
<?php
class ControllerBase
extends Phalcon\Mvc\Controller
{
protected
function
initialize()
{
//Prepend the application name to the title
$this->tag->prependTitle(‘INVO
| ‘);
}
//...
}
Finally, the title is printed in the main view (app/views/index.phtml):
最后,标题在主视图文件中显示出来(app/views/index.phtml):
<!DOCTYPE html>
<html>
<head>
<?php
echo
$this->tag->getTitle()
?>
</head>
<!-- ... -->
</html>
结束语(Conclusion)?
This tutorial covers many more aspects of building applications with Phalcon, hope you have served to learn more and get more out of the framework.
这个示例中我们介绍了phalcon的多方面的知识,希望大家能够从中学到更多,对Phalcon有更近一步的了解。