Phoenix 框架:从创建应用到视图模板开发
1. Phoenix 简介
Phoenix 是一个基于 Elixir 的 Web 框架,实现了服务器端的 MVC 模式。它与 Ruby on Rails 或 Python Django 类似,但又不仅仅是它们的克隆。Phoenix 的目标是将高生产力与高性能相结合,引入了如 WebSocket 管理的通道、发布 - 订阅层的主题以及预编译模板等概念。Phoenix 由 Chris McCord 创建,首次提交于 2014 年 5 月 1 日,目前已具备初始路线图中定义的几乎所有功能,仅 iOS 和 Android 客户端功能待定。该项目有超过 80 位贡献者,其中包括 Elixir 的创造者 José Valim。
2. 创建 Phoenix 应用
2.1 准备工作
要开始使用 Phoenix 并创建新应用,需要从 GitHub 克隆 phoenixframework 仓库。具体步骤如下:
1. 打开终端窗口,进入想要放置 Phoenix 的目录。
2. 使用以下命令克隆仓库:
git clone https://github.com/phoenixframework/phoenix.git
- 进入 phoenix 目录并检出 版本:
cd phoenix && git checkout v0.8.0
- 获取依赖并编译 phoenix:
mix do deps.get, compile
2.2 创建新应用步骤
- 从克隆的 phoenix 目录生成应用:
mix phoenix.new todo ../todo
- 进入生成的应用目录:
cd ../todo
- 安装并编译应用依赖:
mix do deps.get, compile
- 启动服务器进行测试:
mix phoenix.server
此时会显示
Running Todo.Endpoint with Cowboy on port 4000 (http)
。
5. 打开浏览器窗口,访问
http://localhost:4000
,将看到 Phoenix 默认页面。
2.3 工作原理
克隆的 Phoenix 应用定义了多个 Mix 任务,其中
phoenix.new
任务负责根据应用名称和创建位置生成 Phoenix 应用的结构。后续获取依赖、编译和启动应用的过程与 Mix 应用的标准流程类似,Phoenix 还提供了
mix phoenix.server
任务来启动应用。
2.4 性能优化
为了提高性能,可以执行以下两个额外步骤:
1. 合并协议:
MIX_ENV=prod mix compile.protocols
- 在生产模式下启动应用:
MIX_ENV=prod PORT=4001 elixir -pa _build/prod/consolidated -S mix phoenix.start
使用
wrk
工具进行测试,在 1 分钟内进行 15 个并发请求,使用 2011 款 13 英寸 MacBook Pro(2.3 GHz Intel Core i5)笔记本电脑,Phoenix 每秒能够处理 3,793 个请求。
3. 定义路由
在创建了第一个 Phoenix 应用后,可以为 Phoenix 待办应用添加一些路由。通过添加路由,可以定义应用在访问特定 URL/端点时的行为。路由器任务用于解析请求并将其分发到当前控制器的操作,并传递任何现有的参数。
3.1 准备工作
打开之前生成的文件,完整代码可在相应路径找到。示例代码如下:
defmodule Todo.Router do
use Phoenix.Router
pipeline :browser do
plug :accepts, ~w(html)
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
end
pipeline :api do
plug :accepts, ~w(json)
end
scope "/", Todo do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
get "/text", MyController, :plaintext
get "/generated", MyController, :send_html
end
# Other scopes may use custom stacks.
# scope "/api", Todo do
# pipe_through :api
# end
end
3.2 定义路由步骤
-
定义
/text和/generated端点的路由,在get "/", PageController, :index下方添加以下两行:
get "/text", MyController, :plaintext
get "/generated", MyController, :send_html
-
通过在
router.ex文件中添加resources "todos", TodosController定义资源路由:
defmodule Todo.Router do
use Phoenix.Router
pipeline :browser do
plug :accepts, ~w(html)
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
end
pipeline :api do
plug :accepts, ~w(json)
end
scope "/", Todo do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
get "/text", MyController, :plaintext
get "/generated", MyController, :send_html
resources "todos", TodosController
end
# Other scopes may use custom stacks.
# scope "/api", Todo do
# pipe_through :api
# end
end
3.3 工作原理
路由器负责解析传入的请求并将其分发到处理请求的控制器操作。在步骤 1 中,定义了对
/text
和
/generated
请求的处理方式。通用定义格式为
<method_macro> <path>, <controller>, <action>
,这里使用了
get
宏,对应 HTTP GET 动词,还有为其他动词定义的宏。在步骤 2 中,使用
resource
宏定义路由,该宏会为
todo
资源生成八个实际端点,如下表所示:
| 路径名 | HTTP 方法 | 路径 | 控制器操作 |
| ---- | ---- | ---- | ---- |
| todos_path | GET | /todos | Todo.TodosController.index/2 |
| todos_path | GET | /todos/:id/edit | Todo.TodosController.edit/2 |
| todos_path | GET | /todos/new | Todo.TodosController.new/2 |
| todos_path | GET | /todos/:id | Todo.TodosController.show/2 |
| todos_path | POST | /todos | Todo.TodosController.create/2 |
| todos_path | PATCH | /todos/:id | Todo.TodosController.update/2 |
| todos_path | PUT | /todos/:id | Todo.TodosController.update/2 |
| todos_path | DELETE | /todos/:id | Todo.TodosController.destroy/2 |
需要在
TodosController
模块中实现这些操作。
3.4 更多操作
使用
resources
宏定义路由时,可以定义包含或排除哪些操作。例如:
- 仅列出和查看待办资源(index 和 show 操作):
resources "todos", TodosController, only: [:index, :show]
- 待办资源创建后不可更改(无编辑操作):
resources "todos", TodosController, except: [:edit, :update]
要查看这些选项生成的路由,可以在应用根目录的命令行中运行
mix phoenix.routes
。
4. 创建控制器
在 Phoenix 中,控制器是 Elixir 模块,用于定义处理路由器分发请求的函数(或操作)。控制器负责准备数据并将其传递给视图层,同时决定视图的渲染方式。
4.1 准备工作
扩展前面定义路由的项目,已定义的路由如下:
| 路径名 | HTTP 方法 | 路径 | 控制器操作 |
| ---- | ---- | ---- | ---- |
| page_path | GET | / | Todo.PageController.index/2 |
| my_path | GET | /text | Todo.MyController.plaintext/2 |
| my_path | GET | /generated | Todo.MyController.send_html/2 |
| todos_path | GET | /todos | Todo.TodosController.index/2 |
| todos_path | GET | /todos/:id/edit | Todo.TodosController.edit/2 |
| todos_path | GET | /todos/new | Todo.TodosController.new/2 |
| todos_path | GET | /todos/:id | Todo.TodosController.show/2 |
| todos_path | POST | /todos | Todo.TodosController.create/2 |
| todos_path | PATCH | /todos/:id | Todo.TodosController.update/2 |
| todos_path | PUT | /todos/:id | Todo.TodosController.update/2 |
| todos_path | DELETE | /todos/:id | Todo.TodosController.destroy/2 |
根路径由
PageController
处理,需要创建
my_path
和
todos_path
对应的控制器,创建两个新文件:
todo/web/controllers/my_controller.ex
和
todo/web/controllers/todos_controller.ex
。
4.2 创建控制器步骤
-
在
my_controller.ex中添加处理/text端点请求的操作:
defmodule Todo.MyController do
use Phoenix.Controller
plug :action
def plaintext(conn, _params) do
text conn, "Plain text rendered from Phoenix controller!"
end
end
-
在
plaintext操作下方添加处理/generated端点请求的操作:
def send_html(conn, _params) do
generated = """
<html>
<head>
<title>Generated HTML</title>
</head>
<body>
<h2>Creating Controllers</h2>
<p>It is possible to render html from a Phoenix controller!</p>
</body>
</html>
"""
html conn, generated
end
-
在
todos_controller模块中定义index操作(列出所有待办事项):
defmodule Todo.TodosController do
use Phoenix.Controller
plug :action
def index(conn, _params) do
todo = [%{id: 1, task: "Write the other controller actions!", created_at: "2014-12-13", status: "pending"}, %{id: 2, task: "Create Views", created_at: "2014-12-13", status: "pending"}]
json conn, todo
end
end
为了简化,打开路由器文件
web/router.ex
,将
resources "todos", TodosController
改为
resources "todos", TodosController, only: [:index]
,这样只需定义
index
操作即可使控制器正常工作。
4. 启动服务器(
mix phoenix.server
),访问以下端点检查工作结果:
-
http://localhost:4000/text
-
http://localhost:4000/generated
-
http://localhost:4000/todos
4.3 工作原理
定义的两个控制器都以应用名称(Todo)进行命名空间,并导入
Phoenix.Controller
模块,从而可以访问该模块提供的函数。控制器都有
plug :action
行,用于根据路由器的定义将请求分发到正确的控制器和操作。所有定义的函数(或操作)都接受
conn
和
_params
作为参数,
conn
表示连接结构,遵循 Elixir 的消息传递和非状态共享哲学,连接作为参数接收并作为结果的一部分返回。在定义的三个控制器操作中,使用了
text
、
HTML
和
JSON
宏来定义响应的数据类型。
4.4 更多操作
在步骤 3 中,将待办事项列表定义为控制器内的结构。Phoenix 支持与数据库中的数据进行交互,可以使用 ETS、DETS 和 Mnesia,也可以通过名为 Ecto 的 Elixir 项目使用外部数据库,如 PostgreSQL。更多信息可访问 Ecto 项目的网站(http://github.com/elixir-lang/ecto)。
5. 创建视图和模板
路由器确定处理请求的正确控制器和操作后,控制器完成准备响应数据的任务,通常需要输出这些数据。之前看到控制器可以通过渲染文本、输出静态 HTML 甚至 JSON 来响应请求。如果需要生成动态的 HTML 响应,该如何处理呢?
5.1 准备工作
打开创建控制器后得到的应用,找到
json conn, todos
行,将其替换为
assign(conn, :todos, todos)
,并在
plug :action
下方添加
plug :render
。文件内容如下:
defmodule Todo.TodosController do
use Phoenix.Controller
plug :action
plug :render
def index(conn, _params) do
todos = [%{id: 1, task: "Write the other controller actions!", created_at: "2014-12-13", status: "pending"}, %{id: 2, task: "Create Views", created_at: "2014-12-13", status: "pending"}]
assign(conn, :todos, todos)
end
end
5.2 创建视图和新布局步骤
-
创建视图,添加新文件
web/views/todos.view.ex,内容如下:
defmodule Todo.TodosView do
use Todo.View
def todos(conn) do
Enum.map(conn.assigns.todos, fn(x)->x[:task] end)
end
end
-
创建新模板,添加文件
templates/todos/index.html.eex,内容如下:
<div>
<h3>Views and Templates</h3>
<p>This is the template that will display only the task for each item in our todo list.</p>
<ol>
<p>TODO:</p>
<%= for t <- todos @conn do %>
<li><%= t %></li>
<% end %>
</ol>
</div>
- 启动 Phoenix 应用:
mix phoenix.server
-
访问
http://localhost:4000/todos,将看到新模板渲染的待办任务列表。
5.3 工作原理
在步骤 1 中定义的视图起到装饰器的作用。在
TodosController
的
index
操作中,传递了一个结构和一个映射列表,每个映射定义一个待办事项。使用视图过滤列表中的每个元素,只获取
task
字段:
Enum.map(conn.assigns.todos, fn(x)->x[:task] end)
视图中定义的函数是
todos/1
。在步骤 2 中,在列表推导式中使用该函数的结果:
<%= for t <- todos @conn do %>
<li><%= t %></li>
<% end %>
标记文件的扩展名是
html.eex
,表示使用嵌入 Elixir 代码的 HTML 作为模板。上述定义列表推导式的代码块混合了 HTML 和 Elixir,Elixir 代码在
<% %>
内声明,添加
=
符号表示表达式的结果会显示在最终输出中。
5.4 更多操作
在当前例子中,只分配了待办事项数据。如果需要向视图传递多个值,只需在控制器中多次调用
assign
并使用管道操作符(
|>
)连接这些调用。
assign
调用返回连接(
conn
),并作为后续
assign
函数调用的第一个值传递。例如,传递待办事项和两条消息到视图的代码如下:
defmodule Todo.TodosController do
use Phoenix.Controller
plug :action
plug :render
def index(conn, _params) do
todos = [%{id: 1, task: "Write the other controller actions!", created_at: "2014-12-13", status: "pending"}, %{id: 2, task: "Create Views", created_at: "2014-12-13", status: "pending"}]
conn
|> assign(:todos, todos)
|> assign(:message_one, "Hello")
|> assign(:message_two, "World!")
end
end
综上所述,通过以上步骤,我们可以从创建 Phoenix 应用开始,逐步完成路由定义、控制器创建、视图和模板的开发,充分利用 Phoenix 框架的功能,实现高生产力和高性能的 Web 应用开发。
Phoenix 框架:从创建应用到视图模板开发
6. 整体流程总结
为了更清晰地展示从创建 Phoenix 应用到开发视图和模板的整个过程,我们可以用 mermaid 格式的流程图来表示:
graph LR
A[克隆 Phoenix 仓库] --> B[创建新 Phoenix 应用]
B --> C[定义路由]
C --> D[创建控制器]
D --> E[创建视图和模板]
E --> F[启动应用并测试]
这个流程图展示了整个开发过程的主要步骤,从最开始的克隆仓库到最后的应用测试,每个步骤都是紧密相连的。
7. 开发过程中的注意事项
在使用 Phoenix 框架进行开发时,有一些注意事项需要我们牢记:
-
版本管理
:在克隆仓库时,要注意选择合适的版本。不同版本的 Phoenix 框架可能会有一些功能上的差异,因此要确保使用的版本与项目需求相匹配。例如,在前面的步骤中我们使用了
git checkout v0.8.0
来指定版本。
-
依赖管理
:获取依赖和编译的步骤非常重要。如果依赖没有正确安装或编译,可能会导致应用无法正常运行。在执行
mix do deps.get, compile
时,要确保网络连接正常,并且没有出现错误提示。
-
路由定义
:在定义路由时,要注意不同 HTTP 方法对应的宏。例如,
get
宏对应 HTTP GET 动词,还有其他宏用于 PUT、POST 等动词。同时,使用
resource
宏时要清楚它会生成多个实际端点,需要在控制器中实现相应的操作。
-
控制器开发
:控制器中的函数(操作)要遵循接收
conn
和
_params
作为参数的模式。并且要根据需求选择合适的响应数据类型,如
text
、
HTML
或
JSON
。
-
视图和模板
:视图和模板的开发中,要注意 Elixir 代码和 HTML 的混合使用。使用
html.eex
扩展名的文件时,要正确使用
<% %>
和
<%= %>
来处理 Elixir 代码的嵌入和输出。
8. 性能优化建议
除了前面提到的合并协议和在生产模式下启动应用的性能优化方法,还有一些其他的建议可以进一步提高 Phoenix 应用的性能:
-
缓存机制
:可以使用缓存来减少对数据库或其他资源的频繁访问。例如,对于一些不经常变化的数据,可以将其缓存起来,下次请求时直接从缓存中获取,而不是重新查询。
-
代码优化
:对控制器、视图和模板中的代码进行优化,避免不必要的计算和重复操作。例如,在视图中使用更高效的函数来处理数据。
-
并发处理
:利用 Elixir 的并发特性,合理处理并发请求。可以使用 GenServer 等机制来实现并发处理,提高应用的响应速度。
9. 常见问题及解决方案
在开发过程中,可能会遇到一些常见的问题,下面是一些问题及对应的解决方案:
| 问题描述 | 解决方案 |
| ---- | ---- |
| 克隆仓库失败 | 检查网络连接,确保可以访问 GitHub。也可以尝试使用其他网络环境或代理。 |
| 依赖获取失败 | 检查网络连接,确保可以访问依赖的资源。还可以尝试清除缓存并重新获取依赖,使用
mix deps.clean --all
清除缓存,然后再次执行
mix do deps.get, compile
。 |
| 路由无法正常工作 | 检查路由定义是否正确,确保控制器和操作的名称与路由定义一致。可以使用
mix phoenix.routes
来查看生成的路由。 |
| 控制器操作出错 | 检查控制器代码是否有语法错误,确保函数的参数和返回值符合要求。可以使用调试工具来定位问题。 |
| 视图和模板渲染出错 | 检查 Elixir 代码和 HTML 的混合使用是否正确,确保
<% %>
和
<%= %>
的使用符合规范。 |
10. 未来发展方向
Phoenix 框架作为一个不断发展的 Web 框架,未来可能会有更多的功能和改进。例如:
-
更多的数据库支持
:除了现有的 ETS、DETS、Mnesia 和通过 Ecto 支持的 PostgreSQL 等数据库,可能会增加对更多数据库的支持,如 MySQL、MongoDB 等。
-
更好的移动端支持
:虽然目前 iOS 和 Android 客户端功能还在开发中,但未来可能会有更完善的移动端支持,方便开发者开发跨平台的应用。
-
性能进一步提升
:随着 Elixir 语言和 Phoenix 框架的不断发展,可能会有更多的性能优化措施,使应用能够处理更多的并发请求。
总之,Phoenix 框架具有很大的发展潜力,通过不断学习和实践,我们可以更好地利用它来开发高性能、高生产力的 Web 应用。
超级会员免费看
11

被折叠的 条评论
为什么被折叠?



