Best Practices
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
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
<?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
<?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
<?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
# 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!
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
<?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
<?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
<?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
}
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
<?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
<?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
<?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
<?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!
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
<?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
<?php
class ProductTable extends Table
{
public string $productName; // ❌ Should be: $name
public float $productPrice; // ❌ Should be: $price
}
✅ Include All Properties in TypeMap
<?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
<?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
<?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
<?php
class ProductTable extends Table
{
public string $name;
public ?string $description; // ✅ Can be NULL
public ?int $category_id; // ✅ Optional foreign key
}
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
<?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
<?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
<?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
<?php
// ✅ Specific return type
public function getProducts(): array
{
return []; // Array of ProductModel
}
// ❌ BAD - Too generic
public function getProducts(): mixed // ❌ What does this return?
{
return [];
}
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
# ✅ Recommended - Generates all 4 layers
gemvc create:crud Product
# ❌ Not recommended - More steps
gemvc create:service Product -cmt
✅ Always Migrate After Creating Tables
# Create CRUD
gemvc create:crud Product
# Always migrate to create database table
gemvc db:migrate ProductTable
✅ Run PHPStan After Generation
# Generate code
gemvc create:crud Product
# Check code quality
vendor/bin/phpstan analyse
# Fix any type errors immediately!
✅ Use PascalCase for Names
# ✅ 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
JsonResponseobjects - 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
mixedtypes 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()andauth()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!