DGS Class Approach

Overview

The DGS Class Approach is recommended when building advanced or feature-rich data grid stores (DGS). Instead of defining all logic inline, create a dedicated class that extends either Store or PluginStore to encapsulate business logic, complex conditions, validations, and custom behaviors.

Benefits

  • Better code organization and maintainability
  • Reusable business logic
  • Type hinting and IDE support
  • Easier testing and debugging
  • Separation of concerns
  • Extended functionality through inheritance

Basic Implementation

Creating a Store Class

class ProductsStore extends Store 
{
    protected function onPrepareOptions(array &$options): void
    {
        parent::onPrepareOptions($options);

        // Add custom options
        $options['rowsForPage'] = 25;
        $options['defaultOrderField'] = 'created_at';
    }
}

Creating a Plugin Store

class OrdersStore extends PluginStore
{
    public function __construct(string $ident, ?IPlugin $plugin = null, array $options = array())
    {
        parent::__construct($ident, $plugin, $options);

        // Initialize store-specific settings
        $this->addEventListener(self::EVENT_BEFORE_INSERT, array($this, 'onBeforeInsert'));
    }
}

Advanced Features

Custom Event Handlers

class InventoryStore extends PluginStore 
{
    public function __construct(string $ident, ?IPlugin $plugin = null, array $options = array())
    {
        parent::__construct($ident, $plugin, $options);

        // Register event listeners
        $this->addEventListener(self::EVENT_BEFORE_INSERT, array($this, 'validateStock'));
        $this->addEventListener(self::EVENT_AFTER_UPDATE, array($this, 'updateInventoryLevels'));
    }

    protected function validateStock(StoreEvent $event): void 
    {
        $values = &$event->target['values'];
        if ($values['quantity'] < 0) {
            throw new StoreException("Quantity cannot be negative");
        }
    }

    protected function updateInventoryLevels(StoreEvent $event): void 
    {
        // Update inventory calculations
    }
}

Custom Data Loading

class ReportsStore extends Store 
{
    public function load(bool $isAllColumns = false): array
    {
        // Custom data loading logic
        $baseData = parent::load($isAllColumns);

        return $this->enrichReportData($baseData);
    }

    protected function enrichReportData(array $data): array
    {
        // Add calculated fields or additional data
        foreach ($data as &$row) {
            $row['profit_margin'] = $this->calculateProfitMargin($row);
        }

        return $data;
    }
}

Custom Validation Rules

class UsersStore extends PluginStore 
{
    protected function onPrepareValues(array &$values): void
    {
        parent::onPrepareValues($values);

        $this->validateEmail($values);
        $this->validatePassword($values);
    }

    protected function validateEmail(array &$values): void
    {
        if (!filter_var($values['email'], FILTER_VALIDATE_EMAIL)) {
            throw new StoreException("Invalid email format");
        }
    }
}

Business Intelligence Extensions

class AnalyticsStore extends Store 
{
    protected array $metrics = [];
    protected array $dimensions = [];

    public function addMetric(string $name, callable $calculator): void
    {
        $this->metrics[$name] = $calculator;
    }

    public function addDimension(string $name, array $options = []): void
    {
        $this->dimensions[$name] = $options;
    }

    public function aggregate(&$sourceData): ?array
    {
        $baseAggregations = parent::aggregate($sourceData);

        // Add custom metrics
        foreach ($this->metrics as $name => $calculator) {
            $baseAggregations[$name] = $calculator($sourceData);
        }

        return $baseAggregations;
    }

    public function getAnalyticsData(array $filters = []): array
    {
        $data = $this->load();

        return [
            'metrics' => $this->calculateMetrics($data),
            'dimensions' => $this->processDimensions($data),
            'raw_data' => $data
        ];
    }
}

Usage Examples

Basic Store Implementation

// Create store instance
$store = new ProductsStore($db, 'products', [
    'model' => [
        'table' => ['name' => 'products', 'primaryKey' => 'id'],
        'fields' => [/* field definitions */]
    ]
]);

// Use the store
$response = new Response();
$store->onRequest($response);

Analytics Store Implementation

$analyticsStore = new AnalyticsStore($db, 'sales_analytics');

// Add custom metrics
$analyticsStore->addMetric('average_order_value', function($data) {
    return array_sum(array_column($data, 'total')) / count($data);
});

// Get analytics data
$report = $analyticsStore->getAnalyticsData([
    'date_from' => '2024-01-01',
    'date_to' => '2024-12-31'
]);

Best Practices

  1. Separation of Concerns
  2. Keep data access logic in the store class
  3. Move business logic to separate service classes when appropriate
  4. Use events for extensibility

  5. Error Handling

  6. Use StoreException for store-specific errors
  7. Implement proper validation and error messages
  8. Handle edge cases appropriately

  9. Performance

  10. Implement caching when needed
  11. Optimize queries for large datasets
  12. Use lazy loading for related data

  13. Security

  14. Implement proper access control
  15. Validate all input data
  16. Escape output data

  17. Maintainability

  18. Document complex logic
  19. Use meaningful method and variable names
  20. Keep methods focused and single-purpose

Common Patterns

Repository Pattern

class ProductRepository extends Store 
{
    public function findByCategory(int $categoryId): array
    {
        return $this->search(['category_id' => $categoryId]);
    }

    public function findActiveProducts(): array
    {
        return $this->search(['status' => 'active']);
    }
}

Service Layer Pattern

class OrderService 
{
    private OrdersStore $store;

    public function __construct(OrdersStore $store) 
    {
        $this->store = $store;
    }

    public function processOrder(array $orderData): array
    {
        // Business logic for order processing
        $this->validateOrder($orderData);
        $this->store->begin();

        try {
            $result = $this->store->insert($orderData);
            $this->store->commit();
            return $result;
        } catch (Exception $e) {
            $this->store->rollback();
            throw $e;
        }
    }
}