Что такое Phoenix controller и как он взаимодействует с views и templates?
Controller принимает conn и params, вызывает бизнес-логику через контекст и передаёт данные в View через render/3. В Phoenix 1.7+ View — это модуль PostHTML с embed_templates, а шаблоны — HEEx-файлы. Assigns из render доступны в шаблоне через @variable.
Phoenix Controller: связующее звено между HTTP и бизнес-логикой
Controller в Phoenix — это Elixir-модуль, который принимает входящий HTTP-запрос через %Plug.Conn{}, вызывает нужную бизнес-логику и передаёт данные в View для формирования ответа. Каждое публичное действие контроллера — это функция, принимающая conn и params.
Структура контроллера
defmodule MyAppWeb.PostController do
use MyAppWeb, :controller
alias MyApp.Blog
# GET /posts
def index(conn, _params) do
posts = Blog.list_posts()
render(conn, :index, posts: posts)
end
# GET /posts/:id
def show(conn, %{"id" => id}) do
post = Blog.get_post!(id)
render(conn, :show, post: post)
end
# GET /posts/new
def new(conn, _params) do
changeset = Blog.change_post(%Blog.Post{})
render(conn, :new, changeset: changeset)
end
# POST /posts
def create(conn, %{"post" => post_params}) do
case Blog.create_post(post_params) do
{:ok, post} ->
conn
|> put_flash(:info, "Post created successfully.")
|> redirect(to: ~p"/posts/#{post}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset)
end
end
# DELETE /posts/:id
def delete(conn, %{"id" => id}) do
post = Blog.get_post!(id)
{:ok, _post} = Blog.delete_post(post)
conn
|> put_flash(:info, "Post deleted.")
|> redirect(to: ~p"/posts")
end
end
View и Templates в Phoenix 1.7+
Начиная с Phoenix 1.7 архитектура View изменилась: вместо отдельного модуля PostView используются функциональные компоненты в файле post_html.ex и HEEx-шаблоны:
# lib/my_app_web/controllers/post_html.ex
defmodule MyAppWeb.PostHTML do
use MyAppWeb, :html
embed_templates "post_html/*"
# Компонент для отдельного поста
attr :post, MyApp.Blog.Post, required: true
def post_card(assigns) do
~H"""
<div class="post-card">
<h2><%= @post.title %></h2>
<p><%= @post.body %></p>
</div>
"""
end
end
# lib/my_app_web/controllers/post_html/index.html.heex
<h1>All Posts</h1>
<%= for post <- @posts do %>
<.post_card post={post} />
<% end %>
Передача данных: controller -> view -> template
Функция render/3 принимает conn, атом-шаблон и keyword list с assigns. Все переданные значения доступны в шаблоне через @variable:
# В контроллере
render(conn, :show, post: post, comments: comments, current_user: conn.assigns.current_user)
# В шаблоне show.html.heex
<h1><%= @post.title %></h1>
<p>By: <%= @current_user.name %></p>
<%= for comment <- @comments do %>
<p><%= comment.body %></p>
<% end %>
JSON-ответы для API
def show(conn, %{"id" => id}) do
post = Blog.get_post!(id)
render(conn, :show, post: post)
end
# post_json.ex
defmodule MyAppWeb.PostJSON do
def show(%{post: post}) do
%{data: data(post)}
end
defp data(post) do
%{id: post.id, title: post.title, body: post.body}
end
end
Роутер связывает URL с actions контроллера
scope "/", MyAppWeb do
pipe_through :browser
resources "/posts", PostController, only: [:index, :show, :new, :create, :delete]
end
Подводные камни
- Логика в контроллере — контроллер должен быть тонким; весь запрос к базе и бизнес-логика должны быть в контексте, не inline в action-функции.
- Прямой доступ к Repo — вызов
Repo.get/2прямо из контроллера обходит контекстный слой и усложняет тестирование. - Разница Phoenix 1.6 и 1.7 — до 1.7 были отдельные View-модули (
PostView); в 1.7+ этоPostHTMLсembed_templates. Смешивание старого и нового стиля ломает render. - Assigns недоступны автоматически — чтобы использовать
current_userв шаблоне, нужно явно передать его черезrender/3или установить в plug черезassign/3. - put_flash перед redirect — flash должен быть установлен до
redirect/2, иначе сообщение не сохранится в сессии. - Паттерн-матчинг params — если action ожидает
%{"post" => post_params}, но клиент присылает плоский JSON, функция не вызовется (FunctionClauseError). - ~p sigil для путей — в Phoenix 1.7 нужно использовать верифицированные маршруты
~p"/posts/#{id}"; старыйRoutes.post_path(conn, :show, id)устарел.
Common mistakes
- Сводить controllers views templates к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: Phoenix 1.8 работает поверх Plug, Endpoint, Router, Controllers/LiveViews, PubSub и OTP supervision.
- Не отделять validation, authorization, transaction boundary и business logic.
What the interviewer is testing
- Объясняет controllers views templates через конкретную точку lifecycle в Phoenix (Elixir).
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.