PHP Formatting


Creative, simple and structured code to read and write. Some of the following guides were inspired by Jason McCreary's talk during Laracon 2019.

  1. Reformatting
  2. Dead Code
  3. Guard Clauses
  4. Nested Code
  5. Using Objects
  6. Naming & Removing Comments
  7. Rule of Three

Reformatting:

Once code has been completed that is complex or that is a part of a long process, it may be useful to go back and refactor it.

Why reformat?

Dead Code

Similarly like Reformatting, once a task/method/event has been completed, go back. You may find that there is unnecessary 'Dead Code' that is not being used within the event.

What is Dead Code?

Guard Clauses

Guard clauses are when preconditions are met to avoid both errors during the execution period and nested conditions entirely.

There is much debate on how if statements can be used for better code clarity and readability. However, when used appropriately both complexity and memory load can be reduced.

/**
 * Traditional If/Else
 * -- In this example $total is doing nothing.
 */
function calculations(int $a, int $b)
{
    $total = 0;

    if ($b) {
        $total = $a;
    } else {
      $total = $a + $b;
    }

    return $total;
}

/**
 * If/Else and Early Return
 * -- $total has now been removed, yet we now have a pointless else statement.
 */
function calculations(int $a, int $b = null)
{
    if ($b) {
        return $a;
    } else {
      return $a + $b;
    }
}

/**
 * Without Else
 * -- By removing the else statement, we now get the expected result of this
 * -- method at the end and a catch for user error.
 */
function calculations(int $a, int $b = null) : int
{
    if ($b) {
        return $a;
    }

    return $a + $b;
}

/**
 * By class
 * -- Even better, by thinking further ahead this method can be used multiple
 * -- times; why not refactor into it's own class.
 */
class Calculator
{
  public $total = 0;

  public function __construct(int $total = null)
  {
      $this->total = $total;
  }

  public function add(...$numbers) : Calculator
  {
      foreach ($numbers as $number) {
          $this->sum = $this->sum + $number;
      }

      return $this;
  }

  public function subtract(...$numbers) : Calculator;

  public function multiply(...$numbers) : Calculator;

  public function divide(...$numbers) : Calculator;
}

Nested Code

If possible, remove any nested code into it's own block. This makes code more readable for future developers to take over legacy code (Reformatted 1x).

Using Objects

Something big to take away is the use of objects and storing methods, these may be reusable in the build/project again. For example, making use of implicit Route Model Binding to get a specific row and therefore removing code such as $record = Model::findOrFail($id).

Naming & Removing Comments

Conventions of naming are very important and relate to comments. Sometimes commenting is unnecessary where methods have specific, relatable names. If this is applied you'll find comments not necessary as the/a task is obvious by the method name. For example:

public function store()
{
  // Get all items that are active
  $items = Item::where('active', 1)->get();

  // or simply create a scope
  $items = Item::active(true)->get();
}

Rule of Three

Within a method it's important to keep to a structure because to make clear of what the outcome is.

Again this rule can be applied during the refactoring stage, please see some examples below.

True/False (boolean) statements:

// This piece of code is fine and there is nothing wrong with it.
// It does make sense
public function isEmpty()
{

  if( $this->size === 0 ) {
    return true;
  } else {
    return false;
  }
}

// However it can easily be reformatted to return the
// raw Boolean value directly this because if $size
// is empty when returned then we should see false anyways
public function isEmpty()
{

  return $this->size === 0;
}

Primary actions should appear at the end of a method

By the end of this section, you should understand why actions should follow at the end of a method and that nested code should be avoided if possible

// There are many things that can be done about this code.
public function add($item)
{
  if ($item === null) {
    // code...
  } else {
    if (!$this->contains($item)) {
      return $this->items[] = $item;
    }
  }
}

// Reformatted 1x
// The first argument is that the first Boolean returns nothing,
// so lets remove and invert that logic.
// The nested if statement is more readable...
public function add($item)
{
  if ($item !== null) {
    if (!$this->contains($item)) {
      return $this->items[] = $item;
    }
  }
}

// Reformatted 2x
// Furthermore these nested if statement can be on the same line
public function add($item)
{
  if ( $item !== null && ! $this->contains($item) ) {
    return $this->items[] = $item;
  }
}

// Reformatted 3x
// Now there was nothing wrong with the previous code, however
// it is not very readable. Instead we should really see the final
// action at the end of this method
public function add($item)
{
  if ( $item === null && $this->contains($item) ) {
    return false;
  }

  return $this->items[] = $item;
}

Controller actions

By the end of this section, you should understand why code blocks are separated into different files to handle particular methods

class EnrollmentController extends Controller
{
    // Globally if the Request were to contain these
    // Validation rules, they can be stored into
    // an object and therefore simplify the Controller.
    public function store(Request $request, $id)
    {
       // Rules
       Validator::make($request->all(), [
         'first_name' => 'request|string'
         'last_name' => 'request|string',
         'email' => 'request|email',
         'password' => 'request|confirmed|min:8',
       ])->validate();
    }

    // Similarly although this is only one line of code,
    // Route Model Binding will perform the same task as the method
    // findOrFail(id)
    public function store(EnrollUser $request, $id)
    {
       $course = Course::findOrFail($id);
    }

    // Now that the Controller method is simplified further, we can may use
    // of another method to separate further lines of code that may be global
    // to other methods of the same Controller
    public function redirectIfAuthenticatedToCourse($course)
    {
      if( ! Auth::check() ) {
        return redirect($course->path());
      }
    }

    // Similarly to Validation, we can store this particular instance of
    // creating a new User from this Request into it's own method of
    // the User object
    public function store(EnrollUser $request, Course $course)
    {
      $this->redirectIfAuthenticatedToCourse($course);

      $user = User::create([
        'first_name' => request('first_name')
        'last_name' => request('last_name'),
        'email' => request('email'),
        'password' => bcrypt(request('password')),
      ])
    }

    // Final Result
    public function store(EnrollUser $request, Course $course)
    {
      $this->redirectIfAuthenticatedToCourse($course);

      $user = User::createFromEnrollmentRequest($request, $course);

      return redirect($course->path())->with('success', 'message');
    }
}