mix archive.install hex phx_new
mix phx.new
➜ mix phx.new app We are almost there! The following steps are missing: $ cd app Then configure your database in config/dev.exs and run: $ mix ecto.create Start your Phoenix app with: $ mix phx.server You can also run your app inside IEx (Interactive Elixir) as: $ iex -S mix phx.server ➜ cd app ➜ mix ecto.create # Създаваме базата данни ➜ mix phx.gen.auth Accounts User users # Създаваме authentication
assets
lib
lib/my_app
lib/my_app_web
priv
test
. ├── assets │ ├── ... ├── config ├── lib │ ├── test_structure │ └── test_structure_web │ ├── components │ │ └── layouts │ └── controllers │ └── page_html ├── priv │ ├── gettext │ │ └── en │ │ └── LC_MESSAGES │ ├── repo │ │ └── migrations │ └── static │ └── images └── test ├── support └── test_structure_web └── controllers
./test_structure_web ├── components │ ├── core_components.ex │ ├── layouts │ │ ├── app.html.heex │ │ └── root.html.heex │ └── layouts.ex ├── controllers │ ├── error_html.ex │ ├── error_json.ex │ ├── page_controller.ex │ ├── page_html │ │ └── home.html.heex │ └── page_html.ex ├── endpoint.ex ├── gettext.ex ├── router.ex └── telemetry.ex
Ecto Query
{:ok, result}
{:error, reason}
Accounts
User
UserToken
UserNotifier
|>
MatchError
with
belongs_to
has_one
has_many
./lib/pento ├── accounts # folder named as the context module, holds the schema modules │ ├── user.ex # schema/core module │ ├── user_notifier.ex # schema/core module │ └── user_token.ex # schema/core module ├── accounts.ex # context module ├── application.ex ├── catalog │ └── product.ex # schema/core module ├── catalog.ex # context module ├── mailer.ex ├── promo # folder named as the context module, holds the schema modules │ └── recipient.ex # schema/core module ├── promo.ex # context module ├── release.ex └── repo.ex
use MyWebWeb, :controller
Plug
plug
actions
# router.ex get "/", PageController, :index # page_controller.ex defmodule HelloWeb.PageController do use HelloWeb, :controller def index(conn, _params) do render(conn, :index) end end
defmodule MyAppWeb.UserController do use MyAppWeb, :controller alias MyApp.Accounts alias MyApp.Accounts.User # ... def create(conn, %{"user" => user_params}) do case Accounts.create_user(user_params) do {:ok, user} -> conn |> put_flash(:info, "User created successfully.") |> redirect(to: ~p"/users/#{user}") {:error, %Ecto.Changeset{} = changeset} -> render(conn, :new, changeset: changeset) end end end
Plug.Conn
render/2
render/3
json/2
text/2
Plug.Conn.send_resp/2
halt
action
params
conn.path_params
conn.body_params
conn.query_params
/hello/:name
POST
?param1=value1¶m2=value2¶m3=value3
path_params
query_params
conn
assigns
def render(conn, template, assigns) do # ... conn #... |> render_and_send(...) end defp render_and_send(conn, format, template, assigns) do view = view_module(conn, format) conn = prepare_assigns(conn, assigns, template, format) data = render_with_layouts(conn, view, template, format) conn |> ensure_resp_content_type(MIME.type(format)) |> send_resp(conn.status || 200, data) end
:phoenix_view
render
~H
.heex
attr :for, :any, required: true, doc: "the data structure for the form" attr :as, :any, default: nil, doc: "the server side parameter to collect all input under" attr :rest, :global, include: ~w(autocomplete name rel action enctype method novalidate target), doc: "the arbitrary HTML attributes to apply to the form tag" slot :inner_block, required: true slot :actions, doc: "the slot for form actions, such as a submit button" def simple_form(assigns) do ~H""" <.form :let={f} for={@for} as={@as} {@rest}> <div class="mt-10 space-y-8 bg-white"> <%= render_slot(@inner_block, f) %> <div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6"> <%= render_slot(action, f) %> </div> </div> </.form> """ end # More examples here: https://gist.github.com/IvanIvanoff/f2034c33e162eb6dde551d9f0c242bd5
Template
View
Layout
<html>
<head>
<body>
</body>
Component
~D[2023-05-05]
Date.new(2023, 5, 5)
~r/foo|bar/
Regex.compile!("foo|bar")
%{}
{}
[]
~r/foo/
~D[2023-05-15]
~s
~r
~U
~D
var=~opts[bar]
var =~ opts[bar]
var = ~opts[bar]
~PID
~PORT
defmacro sigil_D({:<<>>, _, [string]}, []) do {{:ok, {year, month, day}}, calendar} = parse_with_calendar!(string, :parse_date, "Date") to_calendar_struct(Date, calendar: calendar, year: year, month: month, day: day) end defmacro sigil_r({:<<>>, _meta, [string]}, options) when is_binary(string) do binary = :elixir_interpolation.unescape_string(string, &Regex.unescape_map/1) regex = Regex.compile!(binary, :binary.list_to_bin(options)) Macro.escape(regex) end defmacro sigil_r({:<<>>, meta, pieces}, options) do binary = {:<<>>, meta, unescape_tokens(pieces, &Regex.unescape_map/1)} quote(do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options)))) end
"/users/#{user.id}"
/user/#{user.id}
Routes.user_path(conn, :show, user)
~p
~p"/users/#{user.id}
~p"/users/user"
user
:id
URI.encode_query/2
~H"<span class={[@name, @class]} />"
plugins: [Phoenix.LiveView.HTMLFormatter]
<%= <elixir code> %>
<% <elixir code> %>
<div id={@id}>
~H""" <.form :let={f} for={@for} as={@as} {@rest}> <div class="mt-10 space-y-8 bg-white"> <%= render_slot(@inner_block, f) %> <div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6"> <%= render_slot(action, f) %> </div> </div> </.form> """
Phoenix.Endpoint
scope
pipeline
pipe_through
get/3
post/3
defmodule MyApp.Router do use PresentemWeb, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, {MyApp.LayoutView, :root} plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :api do plug :accepts, ["json"] end scope "/", MyApp do pipe_through :browser get "/users/:id", UserController, :show get "/users", UserController, :index post "/users", UserController, :create end end
defmodule MyAppWeb.UserController do use MyAppWeb, :controller alias MyApp.Accounts alias MyApp.Accounts.User def index(conn, _params) do users = Accounts.list_users() render(conn, :index, users: users) end def create(conn, %{"user" => user_params}) do case Accounts.create_user(user_params) do {:ok, user} -> conn |> put_flash(:info, "User created successfully.") |> redirect(to: ~p"/users/#{user}") {:error, %Ecto.Changeset{} = changeset} -> render(conn, :new, changeset: changeset) end end def show(conn, %{"id" => id}) do user = Accounts.get_user!(id) render(conn, :show, user: user) end end
Mix Task
core_components.ex
mix phx.gen.auth Accounts User users
mix phx.gen.html Catalog Product products sku:string name:string price:integer
/products
router.ex
mix phx.gen.json
mix phx.gen.live
mix phx.gen.release --docker
mix phx.gen.secret
Instrumentation
Binary Instrumentation
Instrument
:telemetry
# event emitting :telemetry.execute( [:web, :request, :done], %{latency: latency}, %{request_path: path, status_code: status} ) # event handling defmodule LogResponseHandler do require Logger def handle_event([:web, :request, :done], measurements, metadata, _config) do Logger.info( "[#{metadata.request_path}] #{metadata.status_code} sent in #{measurements.latency}" ) end end
Internazionalization
:gettext
"Hello World!"
gettext("Hello world!")
"Hello world!"
msgid
ngettext
dgettext
pgettext
file
locale
:swoosh
config.runtime.exs
my_room:10
defmodule HelloWeb.RoomChannel do use Phoenix.Channel def join("room:lobby", _message, socket) do {:ok, socket} end def join("room:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end def handle_in("new_msg", %{"body" => body}, socket) do broadcast!(socket, "new_msg", %{body: body}) {:noreply, socket} end end
let channel = socket.channel("room:lobby", {}) let chatInput = document.querySelector("#chat-input") let messagesContainer = document.querySelector("#messages") chatInput.addEventListener("keypress", event => { if(event.key === 'Enter'){ channel.push("new_msg", {body: chatInput.value}) chatInput.value = "" } }) channel.on("new_msg", payload => { let messageItem = document.createElement("p") messageItem.innerText = `[${Date()}] ${payload.body}` messagesContainer.appendChild(messageItem) }) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket