# Phoenix Deviations from Elixir Core Where Phoenix deliberately differs from Elixir core patterns and why. ## 1. Heavy Macro Usage for Performance **Elixir core philosophy:** Keep macro usage minimal. From the Router source: > Phoenix does its best to keep the usage of macros low. **Phoenix deviation:** The Router uses macros extensively. **Source:** `lib/phoenix/router.ex:109-123` > We use `get`, `post`, `put`, and `delete` to define your routes. We use macros > for two purposes: > > * They define the routing engine... Phoenix compiles all of your routes to a > single case-statement with pattern matching rules > > * For each route you define, we also define metadata to implement > `Phoenix.VerifiedRoutes` **Why the deviation:** Performance. Elixir core uses macros sparingly because they add cognitive complexity. Phoenix justifies them because routing is the hottest path in a web app — compile-time optimization yields measurable request/second gains. --- ## 2. `import` without Restriction in Router **Elixir core pattern:** Always use `import Module, only: [...]` to be explicit. **Phoenix deviation:** The Router imports entire modules: **Source:** `lib/phoenix/router.ex:274-276` ```elixir import Phoenix.Router import Plug.Conn import Phoenix.Controller ``` **Why the deviation:** The Router is a DSL. Users need `get`, `post`, `pipe_through`, `scope`, `resources`, `plug`, `fetch_session`, etc. — all available without qualification. Restricting imports would make the DSL unusable. --- ## 3. Compile-Time State Accumulation **Elixir core pattern:** Modules are generally stateless during compilation. Functions are defined and that's it. **Phoenix deviation:** Aggressive use of module attribute accumulation. **Source:** `lib/phoenix/router.ex:271-280` ```elixir Module.register_attribute(__MODULE__, :phoenix_routes, accumulate: true) @phoenix_pipeline nil Phoenix.Router.Scope.init(__MODULE__) @before_compile unquote(__MODULE__) ``` **Why the deviation:** The Router needs to collect ALL routes, then compile them into a single dispatch function. This requires building up state during module compilation, then consuming it all at `@before_compile`. --- ## 4. Channel Restart Strategy: `:temporary` **Elixir core GenServer default:** `:permanent` (always restart). **Phoenix Channel default:** `:temporary` (never restart). **Source:** `lib/phoenix/channel.ex:470-475` ```elixir def child_spec(init_arg) do %{ id: __MODULE__, start: {__MODULE__, :start_link, [init_arg]}, shutdown: @phoenix_shutdown, restart: :temporary } end ``` **Why the deviation:** A crashed channel should NOT auto-restart — the client needs to explicitly reconnect and rejoin. Auto-restarting would create a channel without a connected client, which is meaningless. --- ## 5. Auto-Hibernation **Elixir core GenServer:** No default hibernation — processes stay in memory. **Phoenix Channel:** Defaults to hibernate after 15 seconds of inactivity. **Source:** `lib/phoenix/channel.ex:460` ```elixir @phoenix_hibernate_after Keyword.get(opts, :hibernate_after, 15_000) ``` ```elixir def start_link(triplet) do GenServer.start_link(Phoenix.Channel.Server, triplet, hibernate_after: @phoenix_hibernate_after ) end ``` **Why the deviation:** Web apps have many idle connections. Channels for users who are "connected but not active" are common. Hibernation reclaims memory for the heap without killing the process. A chat app with 10,000 connected users benefits enormously. --- ## 6. `Plug.Builder` vs Raw Behaviour **Elixir core:** Behaviours define contracts. Implementations are manual. **Phoenix Endpoint:** Uses `Plug.Builder` — a macro that generates the `call/2` pipeline by chaining plugs at compile time. **Source:** `lib/phoenix/endpoint.ex:481-483` ```elixir defp plug() do quote location: :keep do use Plug.Builder, init_mode: Phoenix.plug_init_mode() ... end end ``` **Why the deviation:** The Plug specification (`init/1` + `call/2`) is too low-level for composing dozens of middleware. `Plug.Builder` provides the `plug` macro that chains them automatically. It's a higher-level abstraction over the raw behaviour pattern. --- ## 7. Exception Structs with HTTP Status Codes **Elixir core exceptions:** Pure data — message, maybe some context fields. **Phoenix exceptions:** Include `plug_status` for HTTP response mapping. **Source:** `lib/phoenix/router.ex:7-8` ```elixir defmodule NoRouteError do defexception plug_status: 404, message: "no route found", conn: nil, router: nil end defmodule MalformedURIError do defexception [:message, plug_status: 400] end ``` **Why the deviation:** In a web context, exceptions need to map to HTTP status codes. Plug's error handling middleware reads `plug_status` to determine the response code. This bridges the gap between Elixir's exception system and HTTP semantics.