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
- Separation of Concerns
- Keep data access logic in the store class
- Move business logic to separate service classes when appropriate
-
Use events for extensibility
-
Error Handling
- Use
StoreException
for store-specific errors - Implement proper validation and error messages
-
Handle edge cases appropriately
-
Performance
- Implement caching when needed
- Optimize queries for large datasets
-
Use lazy loading for related data
-
Security
- Implement proper access control
- Validate all input data
-
Escape output data
-
Maintainability
- Document complex logic
- Use meaningful method and variable names
- 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;
}
}
}