3.6 Data Seeding Documentation
Overview
The KMP data seeding system provides a comprehensive framework for populating the database with initial data, test data, and development data. The seeding system supports both initial application setup and ongoing development workflows.
Seeding Framework
Technology Stack
- Seeding Engine: Phinx seeding framework integrated with CakePHP
- Seed Files: PHP classes extending
BaseSeed
inapp/config/Seeds/
- Helper Functions: Centralized utilities in
app/config/Seeds/Lib/SeedHelpers.php
- Environment Support: Development, testing, and production seeding strategies
- Integration: Seamless integration with migration system
Seed File Structure
<?php
declare(strict_types=1);
use Migrations\BaseSeed;
use Cake\I18n\DateTime;
class ExampleSeed extends BaseSeed
{
/**
* Get data for seeding
* @return array
*/
public function getData(): array
{
return [
[
'name' => 'Example Name',
'created' => DateTime::now(),
'created_by' => 1,
]
];
}
/**
* Run seeding process
* @return void
*/
public function run(): void
{
$data = $this->getData();
$table = $this->table('example_table');
$table->insert($data)->saveData();
}
}
Seed Helper Functions
Core Utility Functions
The SeedHelpers
class provides centralized lookup functions for referential integrity:
class SeedHelpers
{
/**
* Get role ID by name
* @param string|null $name Role name
* @return int|null Role ID or null if name is null
*/
public static function getRoleId(?string $name): ?int
{
if ($name === null) {
return null;
}
$rolesTable = TableRegistry::getTableLocator()->get('Roles');
$role = $rolesTable->find()->where(['name' => $name])->firstOrFail();
return $role->id;
}
/**
* Get permission ID by name
* @param string $name Permission name
* @return int Permission ID
*/
public static function getPermissionId(string $name): int
{
$permissionsTable = TableRegistry::getTableLocator()->get('Permissions');
$permission = $permissionsTable->find()->where(['name' => $name])->firstOrFail();
return $permission->id;
}
/**
* Get member ID by email or SCA name
* @param string $emailOrScaName Email address or SCA name
* @return int Member ID
*/
public static function getMemberId(string $emailOrScaName): int
{
$membersTable = TableRegistry::getTableLocator()->get('Members');
$member = $membersTable->find()->where([
'OR' => ['email_address' => $emailOrScaName, 'sca_name' => $emailOrScaName]
])->firstOrFail();
return $member->id;
}
/**
* Get branch ID by name
* @param string|null $name Branch name
* @return int|null Branch ID or null if name is null
*/
public static function getBranchIdByName(?string $name): ?int
{
if ($name === null) {
return null;
}
$branchesTable = TableRegistry::getTableLocator()->get('Branches');
$branch = $branchesTable->find()->where(['name' => $name])->firstOrFail();
return $branch->id;
}
/**
* Get activity group ID by name
* @param string $name Activity group name
* @return int Activity group ID
*/
public static function getActivityGroupId(string $name): int
{
$activityGroupsTable = TableRegistry::getTableLocator()->get('Activities.ActivityGroups');
$activityGroup = $activityGroupsTable->find()->where(['name' => $name])->firstOrFail();
return $activityGroup->id;
}
}
Initialization Seeds
Master Seed Orchestrator
InitMigrationSeed.php
Coordinates the complete initial system setup:
class InitMigrationSeed extends BaseSeed
{
public function run(): void
{
// Core organizational structure
$this->call('InitBranchesSeed', ['source' => 'Seeds']);
// Member management
$this->call('InitMembersSeed', ['source' => 'Seeds']);
// RBAC system
$this->call('InitRolesSeed', ['source' => 'Seeds']);
$this->call('InitPermissionsSeed', ['source' => 'Seeds']);
$this->call('InitRolesPermissionsSeed', ['source' => 'Seeds']);
// Role assignments
$this->call('InitMemberRolesSeed', ['source' => 'Seeds']);
}
}
Core System Seeds
InitBranchesSeed.php
Establishes the organizational hierarchy foundation:
class InitBranchesSeed extends BaseSeed
{
public function getData(): array
{
return [
[
'name' => 'Kingdom',
'location' => 'Kingdom',
'parent_id' => null,
'type' => 'Kingdom',
'can_have_members' => true,
'lft' => 1, // Nested set left boundary
'rght' => 18, // Nested set right boundary
'created' => DateTime::now(),
'created_by' => 1,
]
];
}
public function run(): void
{
$data = $this->getData();
$table = $this->table('branches');
// Enable identity insert for controlled ID assignment
$options = $table->getAdapter()->getOptions();
$options['identity_insert'] = true;
$table->getAdapter()->setOptions($options);
$table->insert($data)->saveData();
}
}
InitRolesSeed.php
Creates the fundamental role structure:
class InitRolesSeed extends BaseSeed
{
public function getData(): array
{
return [
// System administration
[
'name' => 'Super User',
'is_system' => true,
'created' => DateTime::now(),
'created_by' => 1,
],
// Basic membership
[
'name' => 'Member',
'is_system' => true,
'created' => DateTime::now(),
'created_by' => 1,
],
// Administrative roles
[
'name' => 'Branch Admin',
'is_system' => false,
'created' => DateTime::now(),
'created_by' => 1,
],
// Officer roles
[
'name' => 'Branch Officer',
'is_system' => false,
'created' => DateTime::now(),
'created_by' => 1,
],
];
}
}
InitPermissionsSeed.php
Establishes the comprehensive permission framework:
class InitPermissionsSeed extends BaseSeed
{
public function getData(): array
{
return [
// Member management permissions
[
'name' => 'view members',
'require_active_membership' => true,
'require_active_background_check' => false,
'require_min_age' => 0,
'is_system' => false,
'is_super_user' => false,
'requires_warrant' => false,
'created' => DateTime::now(),
'created_by' => 1,
],
[
'name' => 'edit members',
'require_active_membership' => true,
'require_active_background_check' => true,
'require_min_age' => 18,
'is_system' => false,
'is_super_user' => false,
'requires_warrant' => true,
'created' => DateTime::now(),
'created_by' => 1,
],
// Administrative permissions
[
'name' => 'admin interface',
'require_active_membership' => true,
'require_active_background_check' => true,
'require_min_age' => 18,
'is_system' => false,
'is_super_user' => false,
'requires_warrant' => true,
'created' => DateTime::now(),
'created_by' => 1,
],
// System permissions
[
'name' => 'super user access',
'require_active_membership' => true,
'require_active_background_check' => true,
'require_min_age' => 21,
'is_system' => true,
'is_super_user' => true,
'requires_warrant' => true,
'created' => DateTime::now(),
'created_by' => 1,
],
];
}
}
InitMembersSeed.php
Creates the initial system administrator:
class InitMembersSeed extends BaseSeed
{
public function getData(): array
{
// Generate secure password hash
$hasher = new DefaultPasswordHasher();
$defaultPassword = $hasher->hash('change_this_password');
return [
[
'sca_name' => 'System Administrator',
'first_name' => 'System',
'last_name' => 'Administrator',
'email_address' => 'admin@example.com',
'password' => $defaultPassword,
'status' => 'active',
'warrantable' => true,
'branch_id' => 1, // Kingdom branch
'verified_date' => DateTime::now(),
'verified_by' => 1,
'additional_info' => json_encode([
'system_account' => true,
'initial_setup' => true
]),
'created' => DateTime::now(),
'created_by' => 1,
]
];
}
}
Development Seeds
Master Development Seed
DevLoad.php
Coordinates comprehensive development data population:
class DevLoad extends BaseSeed
{
public function run(): void
{
// Core organizational data
$this->call('DevLoadBranchesSeed', ['source' => 'Seeds']);
// RBAC system expansion
$this->call('DevLoadRolesSeed', ['source' => 'Seeds']);
$this->call('DevLoadPermissionsSeed', ['source' => 'Seeds']);
$this->call('DevLoadPoliciesSeed', ['source' => 'Seeds']);
// Member data
$this->call('DevLoadMembersSeed', ['source' => 'Seeds']);
$this->call('DevLoadMemberRolesSeed', ['source' => 'Seeds']);
// Application configuration
$this->call('DevLoadAppSettingsSeed', ['source' => 'Seeds']);
// Role-permission assignments
$this->call('DevLoadRolesPermissionsSeed', ['source' => 'Seeds']);
// Plugin data
$this->call('DevLoadActivityGroupsSeed', ['source' => 'Seeds']);
$this->call('DevLoadActivitiesSeed', ['source' => 'Seeds']);
$this->call('DevLoadDepartmentsSeed', ['source' => 'Seeds']);
$this->call('DevLoadOfficesSeed', ['source' => 'Seeds']);
$this->call('DevLoadOfficersSeed', ['source' => 'Seeds']);
// Awards system
$this->call('DevLoadAwardsDomainsSeed', ['source' => 'Seeds']);
$this->call('DevLoadAwardsLevelsSeed', ['source' => 'Seeds']);
$this->call('DevLoadAwardsAwardsSeed', ['source' => 'Seeds']);
$this->call('DevLoadAwardsEventsSeed', ['source' => 'Seeds']);
// Warrant system
$this->call('DevLoadWarrantsSeed', ['source' => 'Seeds']);
}
}
Organizational Development Data
DevLoadBranchesSeed.php
Creates a realistic branch hierarchy for development:
class DevLoadBranchesSeed extends BaseSeed
{
public function getData(): array
{
return [
// Principality level
[
'name' => 'Principality of Example',
'location' => 'Central Region',
'parent_id' => 1, // Kingdom
'type' => 'Principality',
'domain' => 'principality.example.com',
'can_have_members' => true,
'links' => json_encode([
'website' => 'https://principality.example.com',
'facebook' => 'https://facebook.com/principality'
]),
'lft' => 2,
'rght' => 17,
'created' => DateTime::now(),
'created_by' => 1,
],
// Local branch
[
'name' => 'Barony of Test',
'location' => 'Test City, State',
'parent_id' => 2, // Principality
'type' => 'Local',
'domain' => 'barony.example.com',
'can_have_members' => true,
'links' => json_encode([
'website' => 'https://barony.example.com',
'calendar' => 'https://calendar.google.com/calendar/...',
'newsletter' => 'https://newsletter.example.com'
]),
'lft' => 3,
'rght' => 10,
'created' => DateTime::now(),
'created_by' => 1,
],
// College branch
[
'name' => 'College of Example University',
'location' => 'University Town, State',
'parent_id' => 2, // Principality
'type' => 'College',
'can_have_members' => true,
'lft' => 11,
'rght' => 16,
'created' => DateTime::now(),
'created_by' => 1,
],
// Household
[
'name' => 'Household of Example',
'location' => 'Various Locations',
'parent_id' => 3, // Barony
'type' => 'Household',
'can_have_members' => true,
'lft' => 4,
'rght' => 9,
'created' => DateTime::now(),
'created_by' => 1,
],
];
}
}
Member Development Data
DevLoadMembersSeed.php
Creates diverse member profiles for testing:
class DevLoadMembersSeed extends BaseSeed
{
public function getData(): array
{
$hasher = new DefaultPasswordHasher();
$defaultPassword = $hasher->hash('password123');
return [
// Branch Officer example
[
'sca_name' => 'Aethelmearc Herald',
'first_name' => 'John',
'last_name' => 'Smith',
'email_address' => 'herald@example.com',
'password' => $defaultPassword,
'membership_number' => 'KM123456',
'membership_expires_on' => DateTime::now()->addYear(1),
'branch_id' => SeedHelpers::getBranchIdByName('Barony of Test'),
'background_check_expires_on' => DateTime::now()->addYear(2),
'status' => 'active',
'warrantable' => true,
'title' => 'Lord',
'pronouns' => 'he/him',
'pronunciation' => 'ETH-el-mark HAIR-ald',
'birth_month' => 6,
'birth_year' => 1985,
'additional_info' => json_encode([
'emergency_contact' => [
'name' => 'Jane Smith',
'phone' => '555-0123',
'relationship' => 'spouse'
],
'interests' => ['heraldry', 'scribal arts', 'teaching']
]),
'verified_date' => DateTime::now(),
'verified_by' => 1,
'created' => DateTime::now(),
'created_by' => 1,
],
// College member example
[
'sca_name' => 'Collegium Student',
'first_name' => 'Sarah',
'last_name' => 'Johnson',
'email_address' => 'student@university.edu',
'password' => $defaultPassword,
'membership_number' => 'KM789012',
'membership_expires_on' => DateTime::now()->addMonths(6),
'branch_id' => SeedHelpers::getBranchIdByName('College of Example University'),
'status' => 'active',
'warrantable' => false,
'pronouns' => 'she/her',
'birth_month' => 3,
'birth_year' => 2000,
'additional_info' => json_encode([
'student_status' => true,
'graduation_year' => 2024,
'interests' => ['archery', 'dancing', 'cooking']
]),
'verified_date' => DateTime::now(),
'verified_by' => 1,
'created' => DateTime::now(),
'created_by' => 1,
],
// Minor with parent
[
'sca_name' => 'Young Page',
'first_name' => 'Alex',
'last_name' => 'Smith',
'email_address' => 'parent@example.com', // Parent's email
'password' => $defaultPassword,
'branch_id' => SeedHelpers::getBranchIdByName('Barony of Test'),
'parent_id' => SeedHelpers::getMemberId('herald@example.com'),
'status' => 'active',
'warrantable' => false,
'pronouns' => 'they/them',
'birth_month' => 8,
'birth_year' => 2010,
'additional_info' => json_encode([
'minor_account' => true,
'interests' => ['youth combat', 'arts and crafts']
]),
'verified_date' => DateTime::now(),
'verified_by' => 1,
'created' => DateTime::now(),
'created_by' => 1,
],
];
}
}
Plugin-Specific Seeds
Officers Plugin Seeds
DevLoadDepartmentsSeed.php
Creates organizational departments:
class DevLoadDepartmentsSeed extends BaseSeed
{
public function getData(): array
{
return [
[
'name' => 'Operations',
'description' => 'Core operational offices and administration',
'created' => DateTime::now(),
'created_by' => 1,
],
[
'name' => 'Arts & Sciences',
'description' => 'Arts, sciences, and educational activities',
'created' => DateTime::now(),
'created_by' => 1,
],
[
'name' => 'Martial Activities',
'description' => 'Combat sports and martial activities',
'created' => DateTime::now(),
'created_by' => 1,
],
];
}
}
DevLoadOfficesSeed.php
Creates office position definitions:
class DevLoadOfficesSeed extends BaseSeed
{
public function getData(): array
{
return [
// Core required offices
[
'name' => 'Branch Officer',
'department_id' => SeedHelpers::getDepartmentId('Operations'),
'requires_warrant' => true,
'required_office' => true,
'only_one_per_branch' => true,
'grants_role_id' => SeedHelpers::getRoleId('Branch Officer'),
'term_length' => 24,
'applicable_branch_types' => json_encode(['Local', 'College']),
'default_contact_address' => 'officer@{branch.domain}',
'created' => DateTime::now(),
'created_by' => 1,
],
[
'name' => 'Deputy Branch Officer',
'department_id' => SeedHelpers::getDepartmentId('Operations'),
'deputy_to_id' => SeedHelpers::getOfficeId('Branch Officer'),
'reports_to_id' => SeedHelpers::getOfficeId('Branch Officer'),
'requires_warrant' => false,
'term_length' => 24,
'applicable_branch_types' => json_encode(['Local', 'College']),
'created' => DateTime::now(),
'created_by' => 1,
],
[
'name' => 'Herald',
'department_id' => SeedHelpers::getDepartmentId('Arts & Sciences'),
'requires_warrant' => true,
'required_office' => true,
'reports_to_id' => SeedHelpers::getOfficeId('Branch Officer'),
'term_length' => 12,
'applicable_branch_types' => json_encode(['Local', 'College']),
'default_contact_address' => 'herald@{branch.domain}',
'created' => DateTime::now(),
'created_by' => 1,
],
];
}
}
Awards Plugin Seeds
DevLoadAwardsLevelsSeed.php
Creates award precedence hierarchy:
class DevLoadAwardsLevelsSeed extends BaseSeed
{
public function getData(): array
{
return [
[
'name' => 'Kingdom Level',
'precedence' => 1000,
'created' => DateTime::now(),
'created_by' => 1,
],
[
'name' => 'Principality Level',
'precedence' => 800,
'created' => DateTime::now(),
'created_by' => 1,
],
[
'name' => 'Baronial Level',
'precedence' => 600,
'created' => DateTime::now(),
'created_by' => 1,
],
[
'name' => 'Local Level',
'precedence' => 400,
'created' => DateTime::now(),
'created_by' => 1,
],
];
}
}
DevLoadAwardsAwardsSeed.php
Creates sample awards with classification:
class DevLoadAwardsAwardsSeed extends BaseSeed
{
public function getData(): array
{
return [
// Service award
[
'name' => 'Award of Arms',
'abbreviation' => 'AoA',
'description' => 'Recognition of exceptional service to the Kingdom',
'domain_id' => SeedHelpers::getAwardsDomainId('Service'),
'level_id' => SeedHelpers::getAwardsLevelId('Kingdom Level'),
'branch_id' => SeedHelpers::getBranchIdByName('Kingdom'),
'charter' => 'Charter text here...',
'specialties' => json_encode([
'service_types' => ['event_steward', 'officer', 'teaching']
]),
'created' => DateTime::now(),
'created_by' => 1,
],
// Arts & Sciences award
[
'name' => 'Order of the Laurel',
'abbreviation' => 'OL',
'description' => 'Recognition of exceptional skill in the Arts and Sciences',
'domain_id' => SeedHelpers::getAwardsDomainId('Arts & Sciences'),
'level_id' => SeedHelpers::getAwardsLevelId('Kingdom Level'),
'branch_id' => SeedHelpers::getBranchIdByName('Kingdom'),
'charter' => 'Charter text here...',
'specialties' => json_encode([
'arts_sciences' => ['cooking', 'scribal', 'clothing', 'research']
]),
'created' => DateTime::now(),
'created_by' => 1,
],
];
}
}
Activities Plugin Seeds
DevLoadActivitiesSeed.php
Creates activity definitions with authorization requirements:
class DevLoadActivitiesSeed extends BaseSeed
{
public function getData(): array
{
return [
[
'name' => 'Youth Combat Marshal',
'description' => 'Authorization to marshal youth combat activities',
'activity_group_id' => SeedHelpers::getActivityGroupId('Youth Combat'),
'grants_role_id' => SeedHelpers::getRoleId('Youth Marshal'),
'permission_id' => SeedHelpers::getPermissionId('marshal youth combat'),
'created' => DateTime::now(),
'created_by' => 1,
],
[
'name' => 'Archery Marshal',
'description' => 'Authorization to marshal archery activities',
'activity_group_id' => SeedHelpers::getActivityGroupId('Target Archery'),
'grants_role_id' => SeedHelpers::getRoleId('Archery Marshal'),
'permission_id' => SeedHelpers::getPermissionId('marshal archery'),
'created' => DateTime::now(),
'created_by' => 1,
],
];
}
}
Configuration Seeds
Application Settings
DevLoadAppSettingsSeed.php
Populates application configuration:
class DevLoadAppSettingsSeed extends BaseSeed
{
public function getData(): array
{
return [
// Core application settings
[
'name' => 'application.title',
'value' => 'Kingdom Activity Management System',
'required' => true,
'created' => DateTime::now(),
],
[
'name' => 'application.version',
'value' => '3.0.0-dev',
'required' => true,
'created' => DateTime::now(),
],
// GitHub integration settings
[
'name' => 'github.repository_owner',
'value' => 'kingdom-org',
'required' => false,
'created' => DateTime::now(),
],
[
'name' => 'github.repository_name',
'value' => 'feedback',
'required' => false,
'created' => DateTime::now(),
],
// Email configuration
[
'name' => 'email.from_address',
'value' => 'noreply@kingdom.example.com',
'required' => true,
'created' => DateTime::now(),
],
// Features toggles
[
'name' => 'features.github_issue_submitter',
'value' => 'true',
'required' => false,
'created' => DateTime::now(),
],
];
}
}
Seeding Execution
Command Line Interface
# Run all development seeds
bin/cake seed run DevLoad
# Run specific seed
bin/cake seed run DevLoadMembersSeed
# Run initialization seeds (production setup)
bin/cake seed run InitMigrationSeed
# Plugin-specific seeding
bin/cake seed run -p Officers DevLoadOfficersSeed
bin/cake seed run -p Awards DevLoadAwardsAwardsSeed
bin/cake seed run -p Activities DevLoadActivitiesSeed
Environment-Specific Seeding
Development Environment
// Complete development data set
$this->call('DevLoad');
Testing Environment
// Minimal test fixtures
$this->call('TestDataSeed');
Production Environment
// Only essential initialization data
$this->call('InitMigrationSeed');
Advanced Seeding Patterns
1. Referential Integrity Management
public function getData(): array
{
$data = [];
// Get dependent record IDs
$branchId = SeedHelpers::getBranchIdByName('Kingdom');
$roleId = SeedHelpers::getRoleId('Super User');
$memberId = SeedHelpers::getMemberId('admin@example.com');
return [
[
'member_id' => $memberId,
'role_id' => $roleId,
'branch_id' => $branchId,
'start_on' => DateTime::now(),
'expires_on' => DateTime::now()->addYear(1),
'status' => 'current',
'created' => DateTime::now(),
'created_by' => $memberId,
]
];
}
2. Conditional Seeding
public function run(): void
{
$branchesTable = $this->getAdapter()->getConnection()
->query('SELECT COUNT(*) as count FROM branches')
->fetch();
if ($branchesTable['count'] == 0) {
// Only seed if no branches exist
$data = $this->getData();
$this->table('branches')->insert($data)->saveData();
}
}
3. Batch Seeding for Large Datasets
public function run(): void
{
$batchSize = 1000;
$data = $this->getData();
$batches = array_chunk($data, $batchSize);
foreach ($batches as $batch) {
$this->table('large_table')->insert($batch)->saveData();
}
}
4. JSON Field Seeding
public function getData(): array
{
return [
[
'name' => 'Complex Configuration',
'json_field' => json_encode([
'nested' => [
'configuration' => 'value',
'options' => ['option1', 'option2']
],
'feature_flags' => [
'new_feature' => true,
'beta_feature' => false
]
]),
'created' => DateTime::now(),
]
];
}
Testing Integration
Fixture Generation from Seeds
class MembersFixture extends TestFixture
{
public function init(): void
{
// Use seed data for test fixtures
$seed = new DevLoadMembersSeed();
$this->records = $seed->getData();
parent::init();
}
}
Seed Validation Tests
class SeedValidationTest extends TestCase
{
public function testSeedDataIntegrity()
{
$seed = new DevLoadMembersSeed();
$data = $seed->getData();
foreach ($data as $record) {
$this->assertNotEmpty($record['sca_name']);
$this->assertNotEmpty($record['email_address']);
$this->assertTrue(filter_var($record['email_address'], FILTER_VALIDATE_EMAIL));
}
}
}
Best Practices
1. Data Consistency
- Use helper functions for foreign key resolution
- Implement referential integrity checks
- Validate data before insertion
- Handle missing dependencies gracefully
2. Environment Awareness
- Separate production and development data
- Use environment-specific configurations
- Implement conditional seeding logic
- Document seeding requirements
3. Performance Optimization
- Use batch insertion for large datasets
- Minimize database queries in seed generation
- Cache frequently accessed lookups
- Profile seeding performance
4. Maintenance
- Keep seeds synchronized with schema changes
- Update test data regularly
- Document seed dependencies
- Version control seed data changes
This seeding documentation provides comprehensive guidance for populating the KMP database with initial and development data. For implementation details, refer to the specific seed files in app/config/Seeds/
and the Migration Documentation.