Skip to content

Database Models

To avoid writing SQL queries in most cases XELOS comes with an integrated Model architecture to give you a powerful object based access to your data.

Scopes

While the integrated find_* functions directly execute the SQL query and create the resulting model objects you can also use the Scoping feature to create "Lazy Model-Collections". These collections do not hold any model until the collection is loaded. They can be very useful if you need to dynamically construct the required filters / conditions as you can add and remove conditions and finally run the query. Scopes can also be used to reduce the memory consumption for large dataset operations by using the batchProcess() Method.

Basics

Scopes are available on every model object. If you call a scope you will always get a scoped model collection as return. While running scope calls on a model collection will modify this collection, running a scope on a model will keep the source model untouched.

scope_by_*()

Similar to the magic find_by_*() functions you can also use the same syntax with a scope_by prefix to just add a scope without running the query.

<?php
  $BaseModel = $this->model->contact;

  // Scope: Creates a new collection (The BaseModel is not touched!)
  $ScopedCollection = $BaseModel->scope_by_status('active'); 

  // Add additional Scope
  $ScopedCollection->scope_by_lastname(['like'=>'wil%']);

  // Is the collection filled? 
  $ScopedCollection->count(); // Not yet, returns 0 

  // Finally: Run query and fill collection
  $ScopedCollection->all(); // Executes the query with the conditions "status = 'active' AND lastname LIKE 'wil%'"

  // Is the collection filled? 
  $ScopedCollection->count(); // Yes! Now it returns something

  // Use unscope() to remove all scopes if necessary 
  $ScopedCollection->unscope();


  // All scope commands can also be chained
  $Result = $BaseModel->scope_by_id(2)->scope_by_name('John')->all();


?>

Named Scopes

While the scope_by approach is quite handy there are often situations where you need to use a specific scope several times. Instead of distributing this logic across your code you can use Named Scopes which basically allow you to define Scopes in your model and reuse them. It simplifies addition additional filters later on at a central place in your code.

To define scopes you simply need to add a _scopeActive() method where "Active" is the custom name of your scope. This function gets a Query object as parameter which can then be modified as you wish. If you e.g. later on decide to define "Active" records to have not onle the status field active = 1 but also to be not expired (e.g. expiry > NOW()) this condition can be added to the named scope definition. Note the prefix underscore as this function is not called directly but through the internal scoping logic.

Note: Only the where conditions of the QueryBuilder are currently supported for named scopes.

<?php

  // MODEL CLASS
  public function _scopeActive(Query $query){
    $query->where('active', '=', '1');
  }

  // SOMEWHERE

  // Create a collection with your new named scope
  $scopedCollection = $myModel->scopeActive();

  // Add further custom scopes 
  $scopedCollection->scope_by_category_id(2); 

  // Run query and get result
  $scopedCollection->all();

?>

Default Scopes

Default Scopes are basically Named Scopes which will be used for all model queries (including all find_* functions) by default. If you need to run the functions without any scope use the unscope(ScopeName) function first.

<?php

  // MODEL CLASS

  public function __constructor(){
    ...
    $this->registerDefaultScope('Active');
    ...
  }

  public function _scopeActive(Query $query){
    $query->where('active', '=', '1');
  }

  // SOMEWHERE

  // Run a regular find_by
  // -> Will only return models which are in category_id 2 AND have active = 1 (DEFAULT SCOPE)
  $myModel->find_by_category_id(2); 

  // Create a collection
  // -> Will return an empty, lazy model collection with two scopes (category_id and active) - loading will return the same result as the line above
  $myModel->scope_by_category_id(2); 

  // Unscope
  // -> Will return models which are in category_id 2, ignoring the default scope 'Active'
  $myModel->unscope('Active')->find_by_category_id(2); 

?>

Batch processing

With the help of Scopes and the chunk() function you can process large model sets without the need to load all models into memory. For this a lazy collection should be used, although the chunk() function also works on preloaded collections. But only with a lazy collection you gain the full benefits as not all models need to be created at once.

The following example would only load 100 models at a time although the full contact collection may have hundred thousands of items. The callback function is called as long as not all items have been processed.

Note: Please keep in mind that this function uses a SQL based "LIMIT" to page through the results and therefore may have overlaps during long running processes on changing datasets as not all data is loaded at once.

<?php

  // Run a function across all contact models in chunks of 100 items
  // Note: scopeAll() is a helper to get an empty, lazy collection without a custom scope (only default scopes are applied). It is the same as all() but without preloading 
  $this->model->contact->scopeAll()->batchProcess(100, function(ModelCollection $collection){
    foreach ($collection as $model){
      // DO SOMETHING - e.g. Send Notifications 
    }
  });

?>