13、Phoenix 框架:从创建应用到视图模板开发

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
  1. 进入 phoenix 目录并检出 版本:
cd phoenix && git checkout v0.8.0 
  1. 获取依赖并编译 phoenix:
mix do deps.get, compile
2.2 创建新应用步骤
  1. 从克隆的 phoenix 目录生成应用:
mix phoenix.new todo ../todo
  1. 进入生成的应用目录:
cd ../todo
  1. 安装并编译应用依赖:
mix do deps.get, compile
  1. 启动服务器进行测试:
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
  1. 在生产模式下启动应用:
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 定义路由步骤
  1. 定义 /text /generated 端点的路由,在 get "/", PageController, :index 下方添加以下两行:
get "/text", MyController, :plaintext
get "/generated", MyController, :send_html
  1. 通过在 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 创建控制器步骤
  1. 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
  1. 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
  1. 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 创建视图和新布局步骤
  1. 创建视图,添加新文件 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
  1. 创建新模板,添加文件 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>
  1. 启动 Phoenix 应用:
mix phoenix.server
  1. 访问 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 应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值