Create Model

20 minutes BEGINNER

Learn how to create the Model layer - the business logic and data validation layer. Master business validations, data transformations, and database operations.

What You'll Learn

Understanding Model

What is the Model layer and its responsibilities

Business Validations

Validate business rules and data

Creating Model

Create your first Model class

Video Coming Soon...

What is Model Layer?

The Model Layer contains business logic, validations, and data transformations in GEMVC. It's the third layer in the 4-layer architecture and sits between the Controller and Table layers.

  • Business Validations - Validates business rules (e.g., duplicate email check, price validation)
  • Data Transformations - Transforms data before saving (e.g., password hashing, price calculations)
  • Error Handling - Handles errors and returns appropriate responses
  • Database Operations - Uses Table layer methods to perform database operations

Key Principle: The Model layer extends the Table layer and adds business logic on top of database operations. It never directly queries the database - it uses Table layer methods.

1

Understanding the Structure

Basic Model Structure

Every Model must extend its corresponding Table class (e.g., ProductModel extends ProductTable). Here's the basic structure:

app/model/ProductModel.php
<?php
namespace App\Model;

use App\Table\ProductTable;
use Gemvc\Http\JsonResponse;
use Gemvc\Http\Response;

class ProductModel extends ProductTable
{
    public function createModel(): JsonResponse
    {
        // Business validation
        if ($this->price < 0) {
            return Response::unprocessableEntity("Price cannot be negative");
        }
        
        // Data transformation (if needed)
        $this->name = trim($this->name);
        
        // Perform database operation
        $success = $this->insertSingleQuery();
        if ($this->getError()) {
            return Response::internalError($this->getError());
        }
        
        return Response::created($this, 1, "Product created successfully");
    }
}

Key Points:

  • ✓ Extends corresponding Table class (e.g., ProductModel extends ProductTable)
  • ✓ Methods return JsonResponse objects
  • ✓ Contains business validations and data transformations
  • ✓ Uses Table layer methods: insertSingleQuery(), updateSingleQuery(), deleteByIdQuery()
  • ✓ Uses Response factory for standardized responses
2

Business Validations

Validating Business Rules

The Model layer is responsible for business validations that go beyond simple schema validation. These are validations that require database queries or complex business logic.

app/model/ProductModel.php - Validation Example
<?php
public function createModel(): JsonResponse
{
    // Business validation: Check duplicate name
    $this->name = trim($this->name);
    $existing = $this->selectByName($this->name);
    if ($existing) {
        return Response::unprocessableEntity("Product with this name already exists");
    }
    
    // Business validation: Price must be positive
    if ($this->price <= 0) {
        return Response::unprocessableEntity("Price must be greater than zero");
    }
    
    // Business validation: Description length
    if ($this->description && strlen($this->description) > 1000) {
        return Response::unprocessableEntity("Description cannot exceed 1000 characters");
    }
    
    // All validations passed - proceed with database operation
    $success = $this->insertSingleQuery();
    if ($this->getError()) {
        return Response::internalError($this->getError());
    }
    
    return Response::created($this, 1, "Product created successfully");
}

Common Validation Patterns:

  • Duplicate Checks - Query database to check if record already exists
  • Range Validations - Check if values are within acceptable ranges
  • Format Validations - Validate data formats (length, patterns, etc.)
  • Business Rules - Enforce business-specific rules and constraints
3

Data Transformations

Transforming Data Before Saving

The Model layer can transform data before it's saved to the database. Common transformations include trimming strings, hashing passwords, calculating values, etc.

app/model/ProductModel.php - Transformation Example
<?php
public function createModel(): JsonResponse
{
    // Data transformation: Trim and normalize name
    $this->name = trim($this->name);
    $this->name = ucwords(strtolower($this->name));
    
    // Data transformation: Round price to 2 decimal places
    $this->price = round($this->price, 2);
    
    // Data transformation: Clean description
    if ($this->description) {
        $this->description = trim($this->description);
    }
    
    // Perform database operation
    $success = $this->insertSingleQuery();
    if ($this->getError()) {
        return Response::internalError($this->getError());
    }
    
    return Response::created($this, 1, "Product created successfully");
}

Common Transformations:

  • String Operations - trim(), strtolower(), ucwords(), etc.
  • Numeric Operations - round(), abs(), calculations
  • Password Hashing - Use CryptHelper::hashPassword()
  • Date Formatting - Format dates before saving
4

Complete Example

Full CRUD Model

Here's a complete example of a Model with all CRUD operations:

app/model/ProductModel.php - Complete Example
<?php
namespace App\Model;

use App\Table\ProductTable;
use Gemvc\Http\JsonResponse;
use Gemvc\Http\Response;

class ProductModel extends ProductTable
{
    // Create - Business validation and transformation
    public function createModel(): JsonResponse
    {
        // Data transformation
        $this->name = trim($this->name);
        $this->price = round($this->price, 2);
        
        // Business validation
        if ($this->price <= 0) {
            return Response::unprocessableEntity("Price must be greater than zero");
        }
        
        // Database operation
        $success = $this->insertSingleQuery();
        if ($this->getError()) {
            return Response::internalError($this->getError());
        }
        
        return Response::created($this, 1, "Product created successfully");
    }

    // Read - Get product by ID
    public function readModel(int $id): JsonResponse
    {
        $product = $this->selectById($id);
        if (!$product) {
            return Response::notFound("Product not found");
        }
        
        return Response::success($product, 1, "Product retrieved successfully");
    }

    // Update - Business validation and transformation
    public function updateModel(): JsonResponse
    {
        // Data transformation
        if (isset($this->name)) {
            $this->name = trim($this->name);
        }
        if (isset($this->price)) {
            $this->price = round($this->price, 2);
        }
        
        // Business validation
        if (isset($this->price) && $this->price <= 0) {
            return Response::unprocessableEntity("Price must be greater than zero");
        }
        
        // Database operation
        $success = $this->updateSingleQuery();
        if ($this->getError()) {
            return Response::internalError($this->getError());
        }
        
        return Response::updated($this, 1, "Product updated successfully");
    }

    // Delete - Delete product by ID
    public function deleteModel(int $id): JsonResponse
    {
        $product = $this->selectById($id);
        if (!$product) {
            return Response::notFound("Product not found");
        }
        
        $success = $this->deleteByIdQuery($id);
        if ($this->getError()) {
            return Response::internalError($this->getError());
        }
        
        return Response::deleted(true, 1, "Product deleted successfully");
    }
}

⚠️ ProductTable Not Created Yet

The use App\Table\ProductTable; import and extends ProductTable will cause errors until we create the Table class. This is normal! We'll create ProductTable in the next step: Create Table.

Key Methods Used:

  • insertSingleQuery() - Insert record into database
  • updateSingleQuery() - Update existing record
  • deleteByIdQuery($id) - Delete record by ID
  • selectById($id) - Select record by ID (from Table layer)
  • getError() - Get database error if operation failed
  • Response::created() - Return 201 Created response
  • Response::updated() - Return 209 Updated response
  • Response::deleted() - Return 210 Deleted response

Aggregation with `_` Convention

Properties starting with _ (underscore) are ignored in all CRUD operations. This allows you to aggregate related models or store complex data structures without affecting database operations.

Aggregation Example
<?php
class ProductModel extends ProductTable
{
    // Regular properties (saved to database)
    public int $id;
    public string $name;
    public float $price;
    
    // Aggregation properties (ignored in CRUD)
    public ?CategoryModel $_category = null;  // Related category
    public array $_tags = [];                  // Array of tags
    
    public function readModel(int $id): JsonResponse
    {
        $product = $this->selectById($id);
        if (!$product) {
            return Response::notFound("Product not found");
        }
        
        // Load related data (won't be saved to database)
        $product->_category = (new CategoryModel())->selectById($this->category_id);
        $product->_tags = (new TagModel())->selectByProductId($id);
        
        return Response::success($product, 1, "Product retrieved");
    }
}

Note: Properties starting with _ are completely ignored when using insertSingleQuery(), updateSingleQuery(), and other CRUD operations. This is useful for relationships and complex data structures. For more details, see the advanced documentation on model aggregation.

Important Notes

  • Extends Table: Model must extend its corresponding Table class (e.g., ProductModel extends ProductTable).
  • Business Logic Only: Model contains business validations and transformations, not database schema definitions.
  • Always Check Errors: Always check $this->getError() after database operations.
  • Naming Convention: Use PascalCase + "Model" suffix (e.g., ProductModel.php, UserModel.php).
5

Create Your Model

Manual Creation

To create a Model, simply create a new PHP class file in the app/model/ directory. For example, to create a Product Model:

File Location:

Create app/model/ProductModel.php

The class name should match the filename (e.g., ProductModel.phpclass ProductModel)

Example: Create a Product Model

app/model/ProductModel.php
<?php
namespace App\Model;

use App\Table\ProductTable;
use Gemvc\Http\JsonResponse;
use Gemvc\Http\Response;

class ProductModel extends ProductTable
{
    public function createModel(): JsonResponse
    {
        // Data transformation
        $this->name = trim($this->name);
        $this->price = round($this->price, 2);
        
        // Business validation
        if ($this->price <= 0) {
            return Response::unprocessableEntity("Price must be greater than zero");
        }
        
        // Database operation
        $success = $this->insertSingleQuery();
        if ($this->getError()) {
            return Response::internalError($this->getError());
        }
        
        return Response::created($this, 1, "Product created successfully");
    }
}

⚠️ ProductTable Not Created Yet

The use App\Table\ProductTable; import and extends ProductTable will cause an error because the Table class doesn't exist yet. This is expected! We'll create the ProductTable class in the next step: Create Table.

✨ Model Flow:

The Model receives mapped data from the Controller layer, validates business rules, transforms data if needed, and uses Table layer methods to perform database operations.

Request Flow:

1. Controller maps POST data to Model → Calls Model method
2. Model validates business rules
3. Model transforms data if needed
4. Model uses Table methods for database operations
5. Model returns JsonResponse

Alternative: Use CLI Command

You can also use the GEMVC CLI command to generate the Model file automatically:

Terminal
gemvc create:model Product

Tip: Use gemvc create:model Product -t to also create the Table layer!

🎉 Model Created!

Great job! You've learned how to create Model layer. Now let's move to the Table layer to define the database structure and query methods.