PHP Formatter

Paste a class, a controller method or a snippet from a code review. Braces move to where PSR-12 puts them, indentation is rebuilt from nesting depth, and operators get one space on each side. Strings, heredocs and comments come back exactly as you wrote them.

PHP formatting workspace

  • Runs in this tab, no upload
  • Ctrl + Enter formats
  • Drop a .php file on the input
0 lines

PSR-12 output

waiting for input

Rule switches
Class and method braces
Indent

What changed

    Left for you to decide

      PSR-12 decides where every brace and space goes. This page applies those rules and flags the ones needing a person.

      PHP went fifteen years without a shared style guide. In 2012 the PHP Framework Interop Group published PSR-1 and PSR-2, and in 2019 PSR-12 replaced PSR-2 with rules for syntax PSR-2 predated: return types, nullable types, declare(strict_types=1) and grouped imports. The group now maintains the PER Coding Style on top of PSR-12 for PHP 8 syntax such as enums, attributes and match. Laravel Pint, PHP-CS-Fixer and PHP_CodeSniffer all ship a PSR-12 preset, so PSR-12 output passes review on most PHP teams without a style argument.

      The formatter here handles the parts of PSR-12 a tool decides on its own: brace placement, indentation depth, operator spacing and keyword case. A rule with more than one valid fix, such as a method missing its visibility keyword, gets listed under the output with a line number. Picking private over public for you would hide a design decision inside a whitespace diff.

      Before and after, from the class sample

      Pasted
      class InvoiceController extends Controller {var $perPage=25;function show($id){if($id===NULL){abort(404);}
      else if(!is_numeric($id)){$id=(INTEGER)$id;}
      return new InvoiceView;}}
      Formatted
      class InvoiceController extends Controller
      {var $perPage = 25;function show($id){if ($id === null) {abort(404);} elseif (!is_numeric($id)) {$id = (int) $id;}
      return new InvoiceView();}}
      1. The class and method braces drop to their own line. The if brace stays on the keyword line. PSR-12 4.1, 4.4, 5
      2. NULL becomes null, and (INTEGER) becomes (int) followed by one space. PSR-12 2.5, 6.1
      3. else if turns into elseif, and the closing brace joins it on one line. PSR-12 5.1
      4. new InvoiceView gains empty parentheses. PSR-12 4
      5. var and the missing public on show() stay put. Both appear under Left for you to decide. PSR-12 4.3, 4.4

      Two brace rules, and the exceptions people miss

      PSR-12 uses two placements. Classes, interfaces, traits, enums, functions and methods put the opening brace on the line after the declaration. if, foreach, while, switch, try and closures keep it on the keyword line. Developers arriving from JavaScript tend to write every brace on the same line. Developers arriving from C# put every brace on the next one. Both fail a PSR-12 check.

      The formatter also knows the two exceptions careful writers still get wrong. When a method signature is split across several lines, the closing parenthesis and the opening brace share one line:

      public function store(Request $request,InvoiceService $service) {return $service->create($request->validated());}

      An anonymous class keeps its brace on the opening line, the same as a closure, because new class () extends Base { sits inside an expression. Section 8 of PSR-12 covers this. Prefer K&R braces everywhere? Set Class and method braces to Same line and every other rule keeps running.

      Rules applied, with the PSR-12 section behind each

      Closing tag removed 2.2
      A file holding only PHP loses its final ?>. A stray newline after the tag reaches the browser and triggers the "headers already sent" warning on the next header() call.
      One statement per line 2.3
      $a = 1; $b = 2; splits into two lines. Trailing whitespace goes, except inside heredocs, where the spaces belong to the string.
      Four space indent 2.4
      Depth comes from open braces and brackets, not from the old whitespace. A tab indented file and a two space file land on the same grid.
      Lowercase keywords, short casts 2.5
      TRUE, NULL, Array and FUNCTION go lowercase. (integer) becomes (int), (boolean) becomes (bool), (double) becomes (float).
      Header blocks 3
      One blank line after the opening tag, after declare(strict_types=1);, after the namespace line and after the last top level use. strict_types=1 keeps no spaces around its equals sign.
      new with parentheses 4
      new Invoice becomes new Invoice(), including new static and new self.
      Control structures 5
      One space after the keyword, none inside the parentheses, one before the brace. } else {, } catch (...) { and } while (...); after a do block share a line.
      switch and case 5.2
      case sits one level inside switch, the body one level deeper. A body written on the same line as its case moves down.
      Operators 6
      One space around binary, comparison, assignment and ternary operators. None after !, none between $i and ++, one after a cast: (int) $input.
      Closures 7
      function ($line) use ($rate) { with a space after function and on both sides of use. Arrow functions get fn ($x) => the same way.

      Reported, never rewritten

      Some PSR-12 violations have more than one correct fix. The panel under the output lists each one with its line number in the formatted code:

      • A method without a visibility keyword. PHP treats it as public, though the author often meant private. Section 4.4 requires the keyword, and the choice belongs to whoever owns the class.
      • A property declared with var. Section 4.3 bans the keyword. Swapping in public keeps the old behavior, and swapping in private fixes what most legacy code meant. Only you know which.
      • Lines past 120 characters. Section 2.3 sets 120 as a soft limit and asks for 80 where practical. Breaking a long call means choosing where arguments go, so the lines get counted, not wrapped.
      • Unbalanced braces. A missing } throws off every indent below it. The report says so, and the output is worth a second look before you trust it.

      Templates with HTML in them

      When the input mixes HTML and PHP, as WordPress themes and older views do, the HTML passes through byte for byte and each PHP block is formatted on its own. A one line block stays on one line, so <?=$total?> comes back as <?= $total ?>. Alternative syntax such as foreach (...): with endforeach; gets indented inside a single block. Across separate blocks the indentation stays as you wrote it, since the HTML between them decides the layout. Load template sample shows the result on a short invoice list.

      Laravel Blade files are a different language. @if, @foreach and {{ $var }} are not PHP tokens, so input looking like Blade gets refused with a message instead of being mangled.

      This page, Pint, PHP-CS-Fixer or PHP_CodeSniffer?

      ToolRuns whereCommandPick it when
      This pageA browser tab, nothing installedNoneYou need one snippet cleaned before pasting it into a pull request comment or a forum answer.
      Laravel PintComposer dev dependency./vendor/bin/pint --preset psr12The project runs Laravel or you want zero configuration.
      PHP-CS-FixerComposer dev dependency or PHARphp-cs-fixer fix src --rules=@PSR12You want PSR-12 plus extra rules such as ordered imports or strict comparisons.
      PHP_CodeSnifferComposer dev dependencyphpcbf --standard=PSR12 srcCI already runs phpcs and the fixer has to match the checker rule for rule.

      All three command line tools parse your code into tokens with PHP's own tokenizer, so they see every construct the way the interpreter does. For a repository with a style check in CI, install one of them. Use this page for the code sitting in a chat message or a ticket.

      Your code stays in this tab

      Tokenizing, formatting and highlighting all run in JavaScript on this page. Nothing gets sent to a server once the page has loaded, and the formatter keeps working with your connection switched off. PHP pasted into online tools often carries a database password from a config array or an API key sitting in an env() fallback, and a formatter making network requests adds risk for no gain.

      Where this formatter falls short

      PHP formatting questions

      Brace placement, PSR versions, templates and what the formatter refuses to guess.

      Does formatting change how my PHP runs?

      Whitespace, keyword case and brace placement have no effect on execution. PHP keywords and the constants true, false and null are case insensitive, so lowercasing them is safe. Strings, heredocs and comments are never edited. The one removal, the closing tag at the end of a pure PHP file, prevents stray output rather than causing it. Input with a syntax error comes back formatted and still broken, so run php -l on code from an unknown source.

      Is PSR-2 still worth targeting?

      No. PSR-2 was deprecated in 2019 when PSR-12 replaced it. PSR-12 keeps the PSR-2 rules and adds rules for return types, nullable types, strict types declarations and grouped imports. Code following PSR-2 mostly passes a PSR-12 check, with the differences showing up around those newer constructs.

      Why did my method brace move down while my if brace stayed up?

      PSR-12 treats declarations and control structures differently. Classes, functions and methods put the opening brace on the next line. if, foreach, switch, try and closures keep it on the keyword line. When a method signature spans several lines, the brace sits after the closing parenthesis on that same line. Switch Class and method braces to Same line for K&R style.

      Why are var properties and methods without public left alone?

      Both break PSR-12, and both need a decision only the code owner should make. A var property behaves as public, though plenty of legacy code meant it as private. A method with no keyword is public too. Picking for you would bury a behavior change inside a formatting diff, so each one shows up under Left for you to decide with its line number.

      Will it format a WordPress theme file full of HTML?

      Yes, within limits. HTML passes through untouched, and each PHP block gets spacing and keyword fixes. One line blocks stay on one line. Indentation across separate blocks stays as written. WordPress core follows its own standard with tabs and spaces inside parentheses, so pick Tabs for indent on WordPress projects. The parenthesis spacing still follows PSR-12, since the two standards disagree there.

      Does it understand PHP 8 syntax?

      The tokenizer reads match, enums, attributes, named arguments, the nullsafe operator, readonly properties, constructor promotion, first class callables such as strlen(...), and union and intersection types. Enum declarations follow the class brace rule. A nullable type such as ?string keeps no space after the question mark, and a named argument keeps no space before its colon.

      Are tabs or two space indents an option?

      Yes. PSR-12 requires four spaces, and that stays the default. Tabs suit WordPress projects and two spaces suit Drupal, whose coding standard uses them. The indent setting only changes the unit. Nesting depth gets calculated the same way, and the Tab indented lines counter switches off once you pick Tabs.

      Is my code uploaded anywhere?

      No. The formatter runs in JavaScript inside the page and makes no network request after loading. Open file reads a local file through the browser FileReader, and Download builds the result in memory. Turn off your connection after the page appears and every button keeps working.

      Why does the output still have a 150 character line?

      Lines past 120 characters get counted and listed, never broken. PSR-12 calls 120 a soft limit, and splitting a long call means choosing where each argument goes and whether a trailing comma follows. That choice shapes every future diff on the block, so the page points at the lines and leaves the layout to you.

      What happens when I paste code without an opening tag?

      The input gets formatted as a snippet, and no tag gets added, so a method copied out of a class comes back ready to paste back in. The report notes the missing tag. Header rules such as the blank line after the namespace still apply when those lines are present.