Best Practices

30 minutes INTERMEDIATE

Master GEMVC best practices for writing clean, type-safe, secure, and maintainable code. Learn coding standards, architecture patterns, and quality guidelines.

What You'll Learn

Type Safety

PHPStan Level 9 and type-safe coding

Architecture

4-layer architecture patterns

Code Quality

Security, database, and general best practices

Video Coming Soon...

Why Follow Best Practices?

Following GEMVC best practices ensures your code is type-safe, secure, maintainable, and performant. GEMVC is the only PHP framework that supports PHPStan Level 9 - the highest level of static analysis. Following these practices helps you leverage this powerful feature.

  • Type Safety - Catch bugs before production with PHPStan Level 9
  • Security - Leverage GEMVC's automatic security features
  • Maintainability - Write clean, readable, self-documenting code
  • Performance - Optimize database queries and application logic
  • Team Consistency - Follow established patterns for better collaboration
1

Type Safety & PHPStan Level 9

Why PHPStan Level 9?

GEMVC is the only production-ready PHP framework that supports PHPStan Level 9. This catches type errors before runtime, prevents null pointer exceptions, and ensures your code is type-safe.

PHPStan Level 9 Catches:

  • ✓ Type errors before runtime
  • ✓ Null pointer exceptions
  • ✓ Undefined method calls
  • ✓ Incorrect array access
  • ✓ Type mismatches
  • ✓ Missing return types

✅ Always Use Strict Types

✅ GOOD - Strict types enabled
<?php
declare(strict_types=1);

namespace App\Api;

use Gemvc\Http\JsonResponse;

class Product extends ApiService
{
    public function create(): JsonResponse
    {
        // Type-safe code
    }
}

❌ Don't Skip Type Hints

❌ BAD - No type hints
<?php
// Missing declare(strict_types=1);
// Missing return types
// Missing parameter types

public function getUser($id)  // ❌ No type hint!
{
    return $this->selectById($id)->name;  // ❌ Might be null!
}

✅ Always Add Type Hints

✅ GOOD - Full type safety
<?php
declare(strict_types=1);

public function getUser(int $id): ?UserModel
{
    $user = $this->selectById($id);
    if (!$user) {
        return null;  // ✅ Explicit null handling
    }
    return $user;  // ✅ Type-safe!
}

✅ Run PHPStan Regularly

Terminal
# Run PHPStan analysis
vendor/bin/phpstan analyse

# Or use composer script
composer phpstan

Best Practice: Run PHPStan after every code change. Fix type errors immediately - don't ignore them!

2

Architecture Best Practices

Follow the 4-Layer Architecture

GEMVC uses a mandatory 4-layer architecture: API → Controller → Model → Table. Always follow this pattern - it ensures separation of concerns and makes your code maintainable.

✅ Always Extend Base Classes

✅ GOOD - Correct inheritance
<?php
// API Layer
class Product extends ApiService { }

// Controller Layer
class ProductController extends Controller { }

// Model Layer
class ProductModel extends Model { }

// Table Layer
class ProductTable extends Table { }

❌ Don't Skip Layers

❌ BAD - Skipping layers
<?php
// ❌ Don't call Table directly from API
class Product extends ApiService
{
    public function create(): JsonResponse
    {
        return (new ProductTable())->insert();  // ❌ Wrong! Use Controller!
    }
}

// ❌ Don't skip Model layer
class ProductController extends Controller
{
    public function create(): JsonResponse
    {
        return (new ProductTable())->insert();  // ❌ Wrong! Use Model!
    }
}

✅ Correct Request Flow

✅ GOOD - Proper layer flow
<?php
// API Layer - Validates and delegates
class Product extends ApiService
{
    public function create(): JsonResponse
    {
        if (!$this->request->definePostSchema(['name' => 'string'])) {
            return $this->request->returnResponse();
        }
        return (new ProductController($this->request))->create();
    }
}

// Controller Layer - Orchestrates business logic
class ProductController extends Controller
{
    public function create(): JsonResponse
    {
        $product = $this->mapPostToObject(ProductModel::class);
        return (new ProductModel($this->request))->create($product);
    }
}

// Model Layer - Business validations
class ProductModel extends Model
{
    public function create(ProductModel $product): JsonResponse
    {
        // Business logic here
        return (new ProductTable())->insertSingleQuery($product);
    }
}

// Table Layer - Database operations
class ProductTable extends Table
{
    // Database methods
}
3

Security Best Practices

Always Validate and Authenticate

While 90% of GEMVC security is automatic, you should always use schema validation and authentication for the remaining 10%.

✅ Always Use Schema Validation

✅ GOOD - Validate before processing
<?php
public function create(): JsonResponse
{
    // Always validate first!
    if (!$this->request->definePostSchema([
        'name' => 'string',
        'price' => 'float',
        '?description' => 'string'
    ])) {
        return $this->request->returnResponse();  // 400 Bad Request
    }
    
    // ✅ Only valid requests reach here
    return (new ProductController($this->request))->create();
}

❌ Don't Skip Validation

❌ BAD - No validation
<?php
public function create(): JsonResponse
{
    // ❌ Dangerous! No validation!
    $name = $this->request->post['name'];  // Could be anything!
    $price = $this->request->post['price'];  // Could be malicious!
    
    // ❌ Mass assignment possible!
    return (new ProductController($this->request))->create();
}

✅ Always Use Authentication

✅ GOOD - Protect sensitive endpoints
<?php
public function create(): JsonResponse
{
    // Check authentication
    if (!$this->request->auth(['admin', 'salesManager'])) {
        return $this->request->returnResponse();  // 401/403
    }
    
    // ✅ Only authenticated users reach here
    return (new ProductController($this->request))->create();
}

✅ Use Type-Safe Getters

✅ GOOD - Type-safe getters
<?php
// ✅ Type-safe
$id = $this->request->intValueGet('id');
$email = $this->request->stringValuePost('email');
$price = $this->request->floatValuePost('price');

// ❌ BAD - No type checking
$id = $this->request->get['id'];  // Could be anything!
$email = $this->request->post['email'];  // No validation!
4

Database Best Practices

Table Layer Guidelines

Follow these practices when working with the Table layer to ensure type safety, performance, and security.

✅ Match Property Names to Columns

✅ GOOD - Property matches column
<?php
class ProductTable extends Table
{
    public string $name;   // ✅ Maps to 'name' column
    public float $price;   // ✅ Maps to 'price' column
    public ?string $description;  // ✅ Maps to 'description' column
}

❌ Don't Mismatch Names

❌ BAD - Property doesn't match column
<?php
class ProductTable extends Table
{
    public string $productName;  // ❌ Should be: $name
    public float $productPrice;  // ❌ Should be: $price
}

✅ Include All Properties in TypeMap

✅ GOOD - Complete type map
<?php
class ProductTable extends Table
{
    public int $id;
    public string $name;
    public float $price;
    
    protected array $_type_map = [
        'id' => 'int',        // ✅ All properties included
        'name' => 'string',
        'price' => 'float',
    ];
}

✅ Use Protected for Sensitive Data

✅ GOOD - Password protected
<?php
class UserTable extends Table
{
    public string $email;
    protected string $password;  // ✅ Not returned in SELECT queries
    
    // Password is protected from accidental exposure
}

✅ Define Schema for Performance

✅ GOOD - Indexes defined
<?php
public function defineSchema(): array
{
    return [
        Schema::index('email'),      // ✅ Fast lookups
        Schema::unique('email'),     // ✅ Data integrity
        Schema::index('created_at'), // ✅ Fast sorting
    ];
}

✅ Use Nullable Types for Optional Columns

✅ GOOD - Nullable for optional fields
<?php
class ProductTable extends Table
{
    public string $name;
    public ?string $description;  // ✅ Can be NULL
    public ?int $category_id;     // ✅ Optional foreign key
}
5

Code Quality Best Practices

Write Clean, Maintainable Code

Follow these practices to write code that is easy to read, maintain, and extend.

✅ Always Return JsonResponse

✅ GOOD - Consistent return type
<?php
public function create(): JsonResponse
{
    // Always return JsonResponse
    return Response::created($data, 1, 'Product created');
}

public function read(): JsonResponse
{
    return Response::success($data, 1, 'Product retrieved');
}

✅ Use Aggregation with `_` Convention

✅ GOOD - Aggregation pattern
<?php
class ProductModel extends Model
{
    public int $id;
    public string $name;
    
    // Properties starting with _ are ignored in CRUD operations
    public ?UserModel $_created_by;  // ✅ Aggregated data
    public ?CategoryModel $_category;  // ✅ Related entity
}

Properties starting with _ are ignored in CRUD operations, allowing you to include related data without affecting database operations.

✅ Use Descriptive Names

✅ GOOD - Clear, descriptive names
<?php
// ✅ PascalCase for classes
class ProductController extends Controller { }
class UserProfileModel extends Model { }

// ✅ camelCase for methods
public function createProduct(): JsonResponse { }
public function getUserById(int $id): ?UserModel { }

✅ Don't Use `mixed` Without Reason

✅ GOOD - Specific types
<?php
// ✅ Specific return type
public function getProducts(): array
{
    return [];  // Array of ProductModel
}

// ❌ BAD - Too generic
public function getProducts(): mixed  // ❌ What does this return?
{
    return [];
}
6

CLI Best Practices

Use CLI Commands Effectively

Follow these practices when using GEMVC CLI commands to generate code efficiently.

✅ Use create:crud for Complete Setup

Terminal
# ✅ Recommended - Generates all 4 layers
gemvc create:crud Product

# ❌ Not recommended - More steps
gemvc create:service Product -cmt

✅ Always Migrate After Creating Tables

Terminal
# Create CRUD
gemvc create:crud Product

# Always migrate to create database table
gemvc db:migrate ProductTable

✅ Run PHPStan After Generation

Terminal
# Generate code
gemvc create:crud Product

# Check code quality
vendor/bin/phpstan analyse

# Fix any type errors immediately!

✅ Use PascalCase for Names

Terminal
# ✅ Good - PascalCase
gemvc create:crud Product
gemvc create:crud UserProfile

# ❌ Avoid - lowercase or snake_case
gemvc create:crud product
gemvc create:crud user_profile

Quick Reference: Do's and Don'ts

✅ Do's

  • Extend ApiService, Controller, Model, Table
  • Use definePostSchema() for validation
  • Use $request->auth() for authentication
  • Use PHPStan Level 9 - Write type-safe code!
  • Add type hints to all methods and properties
  • Use declare(strict_types=1);
  • Use _ prefix for aggregation
  • Return JsonResponse objects
  • Run PHPStan regularly
  • Use fluent query builder for database operations

❌ Don'ts

  • Don't use Laravel conventions
  • Don't create routes files
  • Don't use Eloquent-style syntax
  • Don't skip the 4-layer architecture
  • Don't manually sanitize inputs (it's automatic!)
  • Don't skip PHPStan - Run it regularly!
  • Don't ignore type errors - Fix them!
  • Don't use mixed types without reason
  • Don't skip schema validation
  • Don't skip authentication on sensitive endpoints

Important Notes

  • GEMVC is NOT Laravel: Don't expect Laravel conventions. Follow GEMVC has its own patterns and architecture.
  • 4-Layer Architecture is Mandatory: Always follow API → Controller → Model → Table flow. Don't skip layers!
  • PHPStan Level 9: GEMVC is the only framework that supports Level 9. Use it to catch bugs before production!
  • Automatic Security: 90% of security is automatic. Use definePostSchema() and auth() for the remaining 10%.
  • Type Safety First: Always add type hints, use strict types, and run PHPStan regularly. Type errors are bugs waiting to happen!

⭐ Best Practices Mastered!

Excellent! You've learned GEMVC best practices for writing clean, type-safe, secure, and maintainable code. Remember: follow the 4-layer architecture, use PHPStan Level 9, and always validate and authenticate!