Experimental Split Feature - Technical Documentation
Overview
The Experimental Split feature in SoloDB allows users to divide a run into multiple parts, organize them in a hierarchical structure, and map elements on each part. This document provides technical details about the implementation of this feature.
Architecture
The Experimental Split feature is implemented in the run
module and consists of several components:
Routes
The feature is accessible through the following routes defined in module/run/config/module.config.routes.php
:
'part' => [
'type' => Segment::class,
'options' => [
'route' => '/part',
'defaults' => [
'controller' => PartController::class,
],
],
'may_terminate' => false,
'child_routes' => [
'view' => [...],
'edit' => [...],
'split' => [...],
'map' => [...]
]
]
These routes map to actions in the PartController
class.
Entities
Part Entity
The core entity is Run\Entity\Part
, which represents a physical portion of an experiment run:
#[ORM\Table(name: 'run_part')]
#[ORM\Entity(repositoryClass: PartRepository::class)]
#[Gedmo\Tree(type: 'nested')]
class Part extends AbstractEntity
{
// Primary key
#[ORM\Column(type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
private ?int $id = null;
// Relationship to Run
#[ORM\ManyToOne(targetEntity: Run::class, cascade: ['persist'], inversedBy: 'part')]
private Run $run;
// Part identifier within its level
#[ORM\Column(type: 'smallint')]
private int $partId = 1;
// Automatically generated label
#[ORM\Column]
private string $shortLabel = '';
// User-defined label
#[ORM\Column(nullable: true)]
private ?string $label = null;
// Layout relationship
#[ORM\ManyToOne(targetEntity: Part\Layout::class, cascade: ['persist'], inversedBy: 'runPart')]
private ?Part\Layout $layout = null;
// Mappings collection
#[ORM\OneToMany(mappedBy: 'part', targetEntity: Mapping::class, cascade: ['persist'], orphanRemoval: true)]
private Collection $mapping;
// Tree structure fields (Gedmo Nested Tree)
#[Gedmo\TreeLeft]
#[ORM\Column(name: '`left`', type: Types::INTEGER)]
private int $left = 0;
#[Gedmo\TreeLevel]
#[ORM\Column(name: '`level`', type: Types::INTEGER)]
private int $level = 0;
#[Gedmo\TreeRight]
#[ORM\Column(name: '`right`', type: Types::INTEGER)]
private int $right = 0;
#[Gedmo\TreeRoot]
#[ORM\ManyToOne(targetEntity: Part::class)]
private ?Part $root = null;
#[Gedmo\TreeParent]
#[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'children')]
private ?Part $parent = null;
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: Part::class)]
#[ORM\OrderBy(['left' => Order::Ascending->value])]
private Collection $children;
// Methods for managing the entity
// ...
}
Key features of the Part entity:
Uses Gedmo's Nested Tree for hierarchical structure
Has relationships to Run, Layout, and Mappings
Maintains parent-child relationships between parts
Includes methods for checking state (isRootLevel, hasLayout, hasMapping, hasChildren, hasParent)
Layout Entity
The Run\Entity\Part\Layout
entity defines the physical arrangement of a part:
class Layout extends AbstractEntity
{
private ?int $id = null;
private string $title = '';
private ?string $description = null;
private string $format = '';
private int $width = 0;
private int $height = 0;
private Collection $runPart;
// Methods for managing the entity
// ...
}
Mapping Entity
The Run\Entity\Part\Mapping
entity defines specific elements on a part's layout:
class Mapping extends AbstractEntity
{
private ?int $id = null;
private Part $part;
private string $label = '';
private int $width = 0;
private int $height = 0;
private int $xCoordinate = 0;
private int $yCoordinate = 0;
// Methods for managing the entity
// ...
}
Controllers
PartController
The Run\Controller\Run\PartController
handles the main actions for the feature:
final class PartController extends AbstractRunController
{
public function __construct(
private readonly PartService $partService,
private readonly RunService $runService,
private readonly FormService $formService,
private readonly TranslatorInterface $translator
) {
}
// View a part's details
public function viewAction(): ViewModel
{
// Find part by ID
// Return view model with part data
}
// Edit a part's properties
public function editAction(): ViewModel|Response
{
// Find part by ID
// Process form submission
// Save changes
// Redirect or return view model
}
// Split a part into multiple child parts
public function splitAction(): ViewModel|Response
{
// Find part by ID
// Process form submission
// Create child parts
// Update labels
// Redirect or return view model
}
// Map elements on a part's layout
public function mapAction(): ViewModel|Response
{
// Find part by ID
// Process form submission
// Save mappings
// Redirect or return view model
}
}
JSON Controller
The Run\Controller\Json\PartController
provides JSON API endpoints for the feature.
Services
PartService
The Run\Service\Run\PartService
provides the core functionality for managing parts:
class PartService extends AbstractService
{
// Find a part by ID
public function findPartById(int $id): ?Part
{
return $this->entityManager->getRepository(entityName: Part::class)->find(id: $id);
}
// Create a new part
public function createPart(Run $run, int $partId, ?Part $parent = null): Part
{
$runPart = new Part();
$runPart->setRun(run: $run);
$runPart->setParent(parent: $parent);
$runPart->setPartId(partId: $partId);
$this->save(entity: $runPart);
$this->refresh(entity: $runPart);
$runPart->setShortLabel(shortLabel: $this->parsePartLabel(part: $runPart));
$this->save(entity: $runPart);
return $runPart;
}
// Update labels of all parts in a run
public function updateShortLabelOfRunParts(Run $run, int $level = 0): void
{
// Recursively update labels at each level
}
// Render part hierarchy as HTML
public function renderPartChildrenHierarchy(Part $part, array $options)
{
// Use repository to build and render hierarchy
}
// Render run parts hierarchy as HTML
public function renderRunPartChildrenHierarchy(Run $run, array $options)
{
// Use repository to build and render hierarchy
}
// Generate label for a part
public function parsePartLabel(Part $part): string
{
// Complex logic to generate appropriate label
}
// Other utility methods
// ...
}
Key features of the PartService:
CRUD operations for parts
Hierarchy management
Label generation and updating
Rendering part hierarchies
View Helpers
PartLabel
The Run\View\Helper\PartLabel
helper renders part labels in the UI:
final class PartLabel extends AbstractHelper
{
public function __invoke(Part $partLabel): string
{
if ($partLabel->getRun()->getLabelType() === 0) {
$alphabet = range(start: 'a', end: 'z');
if (array_key_exists(key: $partLabel->getPartId() - 1, array: $alphabet)) {
return $alphabet[$partLabel->getPartId() - 1];
}
return (string)$partLabel->getPartId();
}
return (string)$partLabel->getPartId();
}
}
PartLink
The Run\View\Helper\Run\PartLink
helper generates links to part pages:
final class PartLink extends AbstractLink
{
// Generate link to part view page
// ...
}
Repository
The Run\Repository\PartRepository
extends Gedmo's NestedTreeRepository
to provide specialized methods for working with the part hierarchy:
final class PartRepository extends NestedTreeRepository
{
// Find parts by run
public function findPartsByRun(Run $run)
{
// Query to find parts belonging to a run
}
// Other specialized query methods
// ...
}
Data Flow
Creating Parts:
User creates a run with initial parts
PartService.createPart() creates Part entities
Labels are generated using PartService.parsePartLabel()
Splitting Parts:
User requests to split a part via PartController.splitAction()
PartService.createPart() creates child parts with parent reference
PartService.updateShortLabelOfRunParts() updates all labels
Mapping Parts:
User assigns a layout to a part
User adds mappings via PartController.mapAction()
Mapping entities are created and associated with the part
Viewing Part Hierarchy:
Technical Considerations
Nested Tree Implementation
The part hierarchy uses Gedmo's Nested Tree pattern, which:
Stores left, right, level values for efficient tree operations
Provides methods for tree traversal and manipulation
Maintains referential integrity
Label Generation
Part labels are generated based on:
The run's label type (number, letter, sample type)
The part's position in the hierarchy
Parent part information when relevant
Tree operations can be expensive for deep hierarchies
The renderTreeAsTable() method optimizes rendering for display
Batch updates are used when updating multiple parts
Conclusion
The Experimental Split feature is implemented as a comprehensive system for managing hierarchical part structures. It leverages Doctrine ORM with Gedmo extensions for tree management, provides a complete set of CRUD operations through controllers and services, and includes specialized view helpers for UI rendering.
21 May 2025