SoloDB Documentation version 0.1-DEV Help

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(); } }

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

  1. Creating Parts:

    • User creates a run with initial parts

    • PartService.createPart() creates Part entities

    • Labels are generated using PartService.parsePartLabel()

  2. Splitting Parts:

    • User requests to split a part via PartController.splitAction()

    • PartService.createPart() creates child parts with parent reference

    • PartService.updateShortLabelOfRunParts() updates all labels

  3. Mapping Parts:

    • User assigns a layout to a part

    • User adds mappings via PartController.mapAction()

    • Mapping entities are created and associated with the part

  4. Viewing Part Hierarchy:

    • PartService.renderPartChildrenHierarchy() or renderRunPartChildrenHierarchy() generates HTML

    • Gedmo's Nested Tree functionality provides the hierarchical structure

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

Performance Considerations

  • 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