Skip to content

Introduction ​

Before diving into Doppar's data management capabilities, it’s important to familiarize yourself with some key model properties that shape how your data is handled. Doppar offers the flexibility to customize these properties to suit your specific needs. Key properties include $pageSize, which controls the number of records displayed per page; $primaryKey, which defines the unique identifier for your table; $table, which specifies the database table associated with the model; $creatable, which determines whether new records can be added; and $unexposable and $timeStamps, which allows you to hide sensitive or irrelevant data from being exposed and handle datetime columns. With Doppar, you have full control to tweak these properties, ensuring your data interactions are both efficient and secure. Let's see the User model as for example.

Creating Model Classes ​

To get started, let's create an Eloquent model. Models typically live in the app\Models directory and extend the Phaseolies\Database\Eloquent\Model class. You may use the make:model Pool command to generate a new model:

bash
php pool make:model Post

This command will generate a new model inside App\Models directory. Models generated by the make:model command will be placed in the app/Models directory. Let's see a basic model class.

php
<?php

namespace App\Models;

use Phaseolies\Database\Eloquent\Model;

class Post extends Model
{
    //
}

If you pass the --m option, it will create a migration file for your associative model, like:

bash
php pool make:model Post --m

$primaryKey ​

Specifies the column name that serves as the unique identifier for the table. By default, this is set to 'id', but it can be customized if your table uses a different primary key.

php
<?php

namespace App\Models;

use Phaseolies\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * The primary key associated with the table.
     *
     * @var int
     */
    protected $primaryKey = 'id';
}

$table ​

Specifies the database table associated with this model

php
<?php

namespace App\Models;

use Phaseolies\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'posts';
}

$creatable ​

Specifies which attributes can be mass-assigned when creating or updating records.This helps prevent mass assignment vulnerabilities by explicitly defining safe fields. Only the attributes listed here can be set in bulk operations.

php
<?php

namespace App\Models;

use Phaseolies\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Creatable Attributes
     *
     * @var array
     */
    protected $creatable = ['title', 'status', 'description'];
}

$unexposable ​

Specifies which attributes should be hidden when the model is converted to an array or JSON or Collection. This is particularly useful for hiding sensitive information, such as passwords, from being exposed in API responses or other outputs.

php
<?php

namespace App\Models;

use Phaseolies\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Unexposable Attributes
     *
     * @var array
     */
    protected $unexposable = ['password'];
}

$timeStamps ​

Indicates whether the model should maintain timest(created_at and updated_at fields.).

php
<?php

namespace App\Models;

use Phaseolies\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Indicates if the model should be timestamped.
     *
     * @var bool
     */
    protected $timeStamps = true;
}

#[CastToDate] ​

By default, $timeStamps uses the datetime format. However, you can convert it to a date format by using Phaseolies\Utilities\Casts\CastToDate like this:

php
<?php

namespace App\Models;

use Phaseolies\Utilities\Casts\CastToDate;
use Phaseolies\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Indicates if the model should be timestamped.
     *
     * @var bool
     */
    #[CastToDate]
    protected $timeStamps = true;
}

Properties Encryption ​

What is Property Encryption? ​

Property encryption refers to the automatic encryption of specific attributes (also called properties) of a model, and their automatic decryption when retrieved via the decrypt($property) method.

This means:

  • Sensitive fields (like name, email, phone, notes, etc.) are encrypted at rest, improving data security.
  • Developers can still interact with the model normally (e.g., $user->eemail), without needing to manually encrypt values.
  • Only selected fields are encrypted β€” not the entire model β€” allowing secure yet efficient storage and querying.

The Doppar provides Phaseolies\Support\Contracts\Encryptable interface for property encryption. When a model implements the Encryptable contract, the framework knows which fields to automatically encrypt based on your configuration.

Example: User Model ​

php
namespace App\Models;

use Phaseolies\Database\Eloquent\Model;
use Phaseolies\Support\Contracts\Encryptable;

class User extends Model implements Encryptable
{
    /**
     * Return an array of model attributes that should be encrypted
     *
     * @return array
     */
    public function getEncryptedProperties(): array
    {
        return [
            'email',
        ];
    }
}

In this case, the email field will always be encrypted when you will fecth data. Now When you perform a query like this using Eloquent.

php
User::find(1);

The Doppar will automatically encrypted fields inside your application. However, when you serialize the data β€” such as converting it to JSON for APIs or logging β€” the encrypted values are preserved unless explicitly decrypted before printing.

json
{
    "id": 1,
    "name": "Nure Yesmin",
    "email": "T0RvMnZqWUIzVWhURkNKdWZSN0ZPaDZvN3g2M0o0L21nUTZ1",
    "status": 1,
    "created_at": "2025-07-13 07:48:05",
    "updated_at": "2025-07-13 07:48:05"
}

The Doppar Framework’s support for encrypted model properties makes it easy to protect sensitive data with minimal code. Just declare what to encrypt β€” and the framework handles the rest.

Fetch Without Encryption ​

If you've enabled encryption in your model but need to retrieve data without applying encryption, Doppar provides the withoutEncryption method. This allows you to fetch unencrypted data when necessary.

php
User::query()
    ->withoutEncryption()
    ->get();

You can also use withoutEncryption when fetching relational data. For example:

php
User::query()
    ->embed('posts', fn($query) => $query->withoutEncryption())
    ->withoutEncryption()
    ->paginate(10);

In this example, both the parent (User) and the related (posts) data are retrieved without applying encryption.

WARNING

The withoutEncryption method is not intended for use with find(). It should only be used for queries that do not rely on find() to retrieve individual records.

Same-Origin Decryption ​

By default, encrypted properties on models like when you retrieve them (e.g., using User::all()), these properties will appear as encrypted unless you decrypt them manually.

Accessing Decrypted Values Manually ​

In some cases β€” especially when working with raw collections, external tools, or custom logic β€” you may want to manually decrypt encrypted values.

Here’s how you can do that safely:

php
$users = User::all();

foreach ($users as $item) {
    $encryptedName = $item->name; // This is encrypted
    $decryptedName = decrypt($item->name); // The original value
}

Cross-Origin Decryption ​

In the Doppar Framework, sensitive model properties are encrypted using a secure AES-256-CBC algorithm and stored as base64-encoded strings that combine the cipher text and IV (Initialization Vector), separated by ::.

While this protects data at rest, you may encounter situations where the encrypted data must be decrypted outside of the originating backend β€” for example:

  • On a separate frontend app (e.g., React, Vue)
  • On a different backend service (e.g., microservices)
  • For external APIs or partners with access to encrypted data

This is known as cross-origin decryption.

To successfully decrypt encrypted properties on a cross-origin system (such as a separate frontend, backend service, or external application), the receiving system must have access to the exact encryption configuration used by the Doppar backend.

Required Shared Data: ​

Encryption Cipher ​

Must match the cipher used during encryption.

php
$cipher = config('app.cipher') ?? 'AES-256-CBC';

Application Encryption Key ​

Must be the base64-decoded version of the application’s APP_KEY.

php
$key = base64_decode(getenv('APP_KEY'));

WARNING

⚠️ Security Warning: The APP_KEY is highly sensitive. It must only be shared with trusted systems over secure channels (e.g., HTTPS, encrypted environment variables, secure vaults).

For more detail, see the Phaseolies\Support\Encryption class for a detailed understanding of how the encryption key, IV, and cipher are generated and used internally.

Model Hooks ​

Model Hooks in the Doppar offer a powerful and elegant way to tap into the lifecycle of your models. They allow you to execute custom logic automatically when specific events occurβ€”such as when a model is being created, updated, deleted, or booted.

This hook system provides clean separation of concerns by letting you attach event-driven behavior directly to the model without cluttering your core business logic. Whether you need to validate data, trigger updated, log changes, or integrate with other services, model hooks give you the flexibility to respond to model changes consistently and predictably.

With support for both inline callbacks and custom handler classes, Doppar's model hook design encourages modular, testable, and maintainable codeβ€”perfect for scaling applications with confidence.

In the sections ahead, we’ll explore each supported hook type in detail, including:

  • booting and booted
  • before_created and after_created
  • before_updated and after_updated
  • before_deleted and after_deleted

Hook Syntax – 3 Supported Formats ​

The Doppar offers a flexible and powerful hook system that allows you to tap into various stages of a model’s lifecycle. Whether you're initializing a model, handling creation events, or cleaning up after deletion, Doppar lets you define hooks in three intuitive formats.

This section outlines the three supported ways to register hooks:

  • Callback methods defined directly in the model class.
  • Dedicated hook classes with a handle() method for better modularity.
  • Conditional hooks that only run when specific criteria are met.

Each format is designed to fit different use casesβ€”from quick logic to clean, testable, and reusable components. See the breakdown below to choose the best format for your scenario.

HOOK VALUE (3 Supported Formats)

1️⃣  Callback Method (Inline method call)
β”œβ”€β”€ 'event_name' => [ClassName::class, 'methodName']

    Example:
    'booting' => [self::class, 'callbackBeforeBooting']


2️⃣  Class-Based Hook (Hook class with handle() method)
β”œβ”€β”€ 'event_name' => HookHandlerClass::class

    Example:
    'booted' => App\Hooks\UserBootedHook::class


3️⃣  Conditional Hook (Run only if a condition is true)
└── 'event_name' => [
       'handler' => [ClassName::class, 'methodName'] | HookClass::class,
       'when' => [ClassName::class, 'conditionMethod']
   ]

    Examples:
    'booting' => [
        'handler' => [self::class, 'bootingHook'],
        'when' => [self::class, 'shouldTriggerBooting']
    ]

    'before_created' => [
        'handler' => App\Hooks\CreateLogger::class,
        'when' => [self::class, 'shouldLog']
    ]

This format makes it crystal clear that Doppar supports 3 flexible ways to define model hooks, and users can quickly choose the style that best fits their needs: simple callbacks, reusable hook classes, or conditionally applied logic.

booting and booted Hooks ​

The booting and booted hooks in the Doppar are lifecycle events that are triggered during the initialization of a model. These hooks are especially useful when you need to configure or prepare a model before it is fully bootstrapped and ready for use.

booting ​

The booting hook is fired before the model is fully initialized. This is the ideal place to set default values or bind custom behaviors to the model before any other logic kicks in.

Example:

php
<?php

namespace App\Models;

use Phaseolies\Database\Eloquent\Model;

class User extends Model
{
    protected $hooks = [
        'booting' => [self::class, 'callbackBeforeBooting']
    ];

    public static function callbackBeforeBooting(Model $model): void
    {
        //
    }
}

In this example, the callbackBeforeBooting method in the same class will be invoked when the model is starting its boot process.

Conditional booting ​

In addition to the basic syntax, Doppar also supports a conditional format for hooks, allowing you to define when a particular hook should be executed. This is done by specifying:

  • handler: The callback or class that will handle the hook.
  • when: A callable that determines whether the hook should trigger.
php
 protected $hooks = [
    'booting' => [
        'handler' => [self::class, 'callbackBeforeBooting'],
        'when' => [self::class, 'shouldTrigger']
    ],
 ];

Here handler Points to the method that contains your booting logic. when returns a boolean. If it returns true, the hook executes; if false, it is skipped.

Suppose you want to initialize special behavior only in a staging environment:

php
public static function shouldTrigger(): bool
{
    return app()->environment('staging');
}

Class-Based booting Hook ​

To keep your hook logic organized, it’s good practice to define a base hook class that other hook classes can extend. Here's a sample. doppar provides make:hook command to create a model hook.

Create a TagModelBootingHook class, run this command

bash
php pool make:hook TagModelBootingHook

A hook TagModelBootingHook class will be created like that

php
<?php

namespace App\Hooks;

use Phaseolies\Database\Eloquent\Model;

class TagModelBootingHook
{
    public function handle(Model $model): void
    {
       //
    }
}

Now you can register this hook class like this way.

php
protected $hooks = [
    'booting' => TagModelBootingHook::class,
];

booted ​

The booted hook is fired after the model has been fully initialized. At this point, the model is completely bootstrapped, and you have access to all its properties and relationships. This makes it the perfect place to finalize setup, attach behaviors, or perform post-boot logic.

Example:

php
<?php

namespace App\Models;

use Phaseolies\Database\Eloquent\Model;

class User extends Model
{
    protected $hooks = [
        'booted' => [self::class, 'callbackAfterBooted']
    ];

    public static function callbackAfterBooted(Model $model): void
    {
        // Perform logic after the model has been booted
    }
}

In this example, the callbackAfterBooted method will be triggered after the model has completed the booting process.

Just like booting, the Doppar supports a conditional format for the booted hook using the handler and when structure. Doppar recommends using dedicated hook classes. These are especially helpful when your hook logic becomes complex or needs to be reused.

after_updated ​

Doppar uses a consistent hook structure across all lifecycle events, a single well-explained example (like your after_updated case) is enough to demonstrate how hooks work. Once users understand the handler and optional when format, they can easily apply it to other events (before_created, after_deleted, etc.).

The after_updated hook is triggered after a model has been successfully updated in the database. This is the perfect point to log changes, sync with external services, or trigger notifications.

Example: Class-Based Hook with Condition ​
php
protected $hooks = [
    'after_updated' => [
        'handler' => UserUpdatedHook::class,
        'when' => [self::class, 'shouldTriggerAfterUpdated']
    ],
];

In this example:

  • handler points to the UserUpdatedHook class, which contains the logic to run after an update.
  • when is a conditional method that returns a boolean to determine whether the hook should execute.

Hook Class: UserUpdatedHook

php
<?php

namespace App\Hooks;

use Phaseolies\Database\Eloquent\Model;

class UserUpdatedHook
{
    /**
     * Handle the incoming model hook
     *
     * @param Model $model
     * @return void
     */
    public function handle(Model $model): void
    {
        if ($model->isDirtyAttr('name')) {
            $originalName = $model->getOriginal('name');
            $updatedName = $model->name;

            info("Name changed from {$originalName} to {$updatedName}");
        }
    }
}

This hook logs the change in the model’s name field after an update.

WARNING

Important: Only the save() or updateOrCreate() or update() using model object triggers model hooks in Doppar for update data. Hooks will not be executed if you update the model directly in the database using query builder methods like update().

To ensure hooks are triggered, always use $model->save() for updating models when relying on hook behavior.

Skip Hooks on Update ​

If you want to update a model without triggering any registered hooks, Doppar provides a convenient method: withoutHook().

Updating without triggering hooks

php
// Calling withoutHook() will skip before/after updated hooks

$user = User::find($id);
$user->withoutHook();
$user->save();

// Or
User::find($id)
    ->withoutHook()
    ->update([
        'name' => $request->name
    ]);

or if you use updateOrCreate() method then you can follow like this

php
User::withoutHook()
    ->updateOrCreate(
        ['id' => $id],
        [
            'name' => $request->name,
            'email' => $request->email
        ]
    );

Useful in cases like bulk updates, silent changes, or system-level operations.

before_created and after_created ​

The before_created and after_created hooks work just like other model hooks in Doppar β€” allowing you to execute logic right before or immediately after a model is created. These hooks are ideal for validating input, setting defaults, sending notifications, or triggering side effects after a new record is added.

WARNING

The before_created and after_created hooks are only triggered when using create() or save()or updateOrCreate() methods to insert data. They will NOT be triggered when inserting data using others ways:

Skip Hooks on Create ​

Sometimes you may want to save or create a model without running any hooks β€” for example, to bypass logging, notifications, or other side effects temporarily.

Doppar provides the withoutHook() method to disable all hooks for a single operation.

php
$tag = new Tag();
$tag->name = $request->name;

// Disable hooks for this save operation
$tag->withoutHook();

$tag->save();

Or if you use create() method to save records, call like this

php
// This will NOT trigger any hooks
Tag::withoutHook()->create([
    'name' => $request->name ?? 'default name',
]);

Use withoutHook() before save() or create() whenever you want to skip all model hooks.

before_deleted and after_deleted ​

The before_deleted and after_deleted hooks allow you to run custom logic just before or immediately after a model is deleted. These hooks are useful for cleaning up related data, logging deletions, or sending notifications.

Skip Hook on Delete ​
php
// Delete without triggering hooks
User::withoutHook()->find($id)->delete();

// Delete with hooks triggered
User::find($id)->delete();

WARNING

The before_deleted and after_deleted hooks are only triggered when using the model’s delete() method.

If you want to skip hooks, you can call withoutHook() before delete(). Hooks will not trigger when deleting records using query builder methods like User::query()->where('id', $id)->delete() or raw SQL queries.

Always use Model Eloquent’s delete() method to ensure hooks run as expected.