Phoenix (Elixir)JuniorTechnical

Что такое 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.

Sources

Related topics

Что такое Phoenix controller и как он взаимодействует с views и templates? | Talanto