Field Manual PHP / Laravel reference

SCAN / COPY / KEEP MOVING

Cheat Sheet

Compact syntax. Open a title for context and pitfalls.

PHP

Syntax

<?php
declare(strict_types=1);

Arrays

$names = array_map(fn ($user) => $user["name"], $users);

Filtering and sorting

$active = array_values(array_filter($users, fn ($u) => $u["active"]));
usort($active, fn ($a, $b) => $a["name"] <=> $b["name"]);

Classes

class User
{
    public function __construct(public string $name) {}
}

Interfaces

interface PaymentGateway
{
    public function charge(int $amount): bool;
}

Static

public static function fromCents(int $cents): self {}

Exceptions

try {
    // operation
} catch (Throwable $e) {
    // handle or rethrow
}

Namespaces

namespace App\Services;
use App\Models\Order;

Closures

$apply = function (int $value) use ($fee): int { return $value + $fee; };

Null handling

$name = $input["name"] ?? "Guest";
$city = $user?->address?->city;

Match expressions

$label = match ($status) {
    "paid" => "Settled",
    "pending" => "Awaiting payment",
    default => "Unknown",
};

Laravel

Routes

Route::get("/orders/{order}", [OrderController::class, "show"]);

Controllers

public function show(Order $order): JsonResponse {}

Form Requests

public function authorize(): bool {}
public function rules(): array {}

Validation

$data = $request->validate(["email" => "required|email|max:254"]);

Models

class Order extends Model { protected $fillable = ["reference", "total_cents"]; }

Eloquent

$order = Order::findOrFail($id);
$order->save();
$order->delete();

Relationships

public function user(): BelongsTo { return $this->belongsTo(User::class); }

Query Builder

DB::table("orders")->where("status", "paid")->get();

Transactions

DB::transaction(function () {
    // database operations
});

API responses

return response()->json(["id" => $order->id], 201);

Pagination

Order::where("user_id", $user->id)->orderBy("id")->paginate(20);

SQL

SELECT

SELECT id, name FROM users;

INSERT

INSERT INTO orders (user_id, total_cents, status) VALUES (?, ?, ?);

UPDATE

UPDATE orders SET status = ? WHERE id = ? AND status = ?;

DELETE

DELETE FROM orders WHERE id = ?;

WHERE

WHERE user_id = ? AND status IN (?, ?)

JOIN

SELECT o.id, u.name
FROM orders o JOIN users u ON u.id = o.user_id;

GROUP BY

SELECT user_id, SUM(total_cents) FROM orders GROUP BY user_id;

ORDER BY

ORDER BY created_at DESC, id DESC

LIMIT

SELECT id FROM orders ORDER BY id LIMIT 20 OFFSET 40;

Transactions

BEGIN;
-- writes
COMMIT;
-- On failure: ROLLBACK;

HTTP / API

HTTP methods

GET /orders          // read
POST /orders         // create/action
PUT /orders/42       // replace
PATCH /orders/42     // partial update
DELETE /orders/42    // delete

Status codes

200 OK
201 Created
204 No Content
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
409 Conflict
422 Unprocessable Content
429 Too Many Requests
500 Internal Server Error

Security

SQL injection

// Bad:
DB::select("SELECT * FROM users WHERE id = $id");
// Good:
DB::select("SELECT * FROM users WHERE id = ?", [$id]);
// Preferred:
User::whereKey($id)->firstOrFail();

Password hashing

// Bad: md5($password) or plaintext
// Good:
$hash = password_hash($password, PASSWORD_DEFAULT);
$valid = password_verify($password, $hash);

Mass assignment

// Bad:
$user->update($request->all());
// Good:
$user->update($request->safe()->only(["name", "email"]));

Financial-system patterns

Monetary values

$amountInCents = 1250; // USD 12.50
// Avoid careless financial arithmetic with $amount = 12.50;

Optimistic locking

UPDATE accounts SET balance_cents = ?, version = version + 1
WHERE id = ? AND version = ?;

Pessimistic locking

// PostgreSQL/MySQL transactional engine, not SQLite row locking:
Account::whereKey($id)->lockForUpdate()->firstOrFail();