Skip to content

Instantly share code, notes, and snippets.

@khalwat
Last active December 6, 2023 12:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save khalwat/40207181865d7bdb79a71dd170ec4f15 to your computer and use it in GitHub Desktop.
Save khalwat/40207181865d7bdb79a71dd170ec4f15 to your computer and use it in GitHub Desktop.
Craft CMS content migration that creates & deletes Sections, Field Groups, and Fields, and adds them to the Section, and then seeds the newly created Section with faked data. Also has a `safeDown()` method to revert what the migration adds
<?php
namespace craft\contentmigrations;
use Craft;
use craft\base\Element;
use craft\base\Field;
use craft\db\Migration;
use craft\elements\Entry;
use craft\errors\ElementNotFoundException;
use craft\errors\EntryTypeNotFoundException;
use craft\errors\SectionNotFoundException;
use craft\errors\SiteNotFoundException;
use craft\fieldlayoutelements\CustomField;
use craft\fields\PlainText;
use craft\models\EntryType;
use craft\models\FieldGroup;
use craft\models\Section;
use craft\models\Section_SiteSettings;
use yii\base\Exception;
use yii\base\InvalidConfigException;
use yii\db\Schema;
/**
* m231102_025445_seed_db_data migration.
*/
class m231102_025445_seed_db_data extends Migration
{
private const SECTION_CONFIGS = [
[
'class' => Section::class,
'name' => 'Demo',
'handle' => 'demo',
'type' => Section::TYPE_CHANNEL,
],
];
private const FIELD_GROUP_CONFIGS = [
[
'class' => FieldGroup::class,
'name' => 'Demo'
],
];
private const FIELD_CONFIGS = [
[
'class' => PlainText::class,
'name' => 'Demo Data',
'handle' => 'demoData',
'translationMethod' => Field::TRANSLATION_METHOD_NONE,
'multiline' => 0,
'columnType' => Schema::TYPE_STRING,
],
];
private const ENTRY_TYPE_HANDLE = 'default';
private const USER_NAME = 'admin';
private const NUM_ENTRIES = 100000;
/**
* @inheritdoc
*/
public function safeUp(): bool
{
try {
$this->createSections(self::SECTION_CONFIGS);
$this->createFieldGroups(self::FIELD_GROUP_CONFIGS);
$this->createFields(self::FIELD_CONFIGS, self::FIELD_GROUP_CONFIGS[0]['name']);
$this->addFieldsToSection(self::FIELD_CONFIGS, self::SECTION_CONFIGS[0]['handle'], self::ENTRY_TYPE_HANDLE);
$this->createEntryData(self::FIELD_CONFIGS, self::SECTION_CONFIGS[0]['handle'], self::ENTRY_TYPE_HANDLE, self::USER_NAME, self::NUM_ENTRIES);
} catch (\Throwable $e) {
return false;
}
return true;
}
/**
* @inheritdoc
*/
public function safeDown(): bool
{
try {
$this->deleteEntryData(self::SECTION_CONFIGS[0]['handle']);
$this->removeFieldsFromSection(self::FIELD_CONFIGS, self::SECTION_CONFIGS[0]['handle'], self::ENTRY_TYPE_HANDLE);
$this->deleteFields(self::FIELD_CONFIGS);
$this->deleteFieldGroups(self::FIELD_GROUP_CONFIGS);
$this->deleteSections(self::SECTION_CONFIGS);
} catch (\Throwable $e) {
return false;
}
return true;
}
/**
* Create Sections based on the $sectionConfigs
*
* @param array $sectionConfigs
* @return void
* @throws \Throwable
* @throws SectionNotFoundException
* @throws SiteNotFoundException
*/
protected function createSections(array $sectionConfigs): void
{
foreach ($sectionConfigs as $sectionConfig) {
$handle = $sectionConfig['handle'];
if (Craft::$app->sections->getSectionByHandle($handle)) {
echo "Section {$handle} already exists" . PHP_EOL;
continue;
}
$section = Craft::createObject(array_merge($sectionConfig, [
'siteSettings' => [
new Section_SiteSettings([
'siteId' => Craft::$app->sites->getPrimarySite()->id,
'enabledByDefault' => true,
'hasUrls' => true,
'uriFormat' => "{$handle}/{slug}",
'template' => "{$handle}/_entry",
]),
]
]));
if (!Craft::$app->sections->saveSection($section)) {
echo "Section {$handle} could not be saved" . PHP_EOL;
}
}
}
/**
* Delete Sections based on the $sectionConfigs
*
* @param array $sectionConfigs
* @return void
* @throws \Throwable
*/
protected function deleteSections(array $sectionConfigs): void
{
foreach ($sectionConfigs as $sectionConfig) {
$handle = $sectionConfig['handle'];
$section = Craft::$app->sections->getSectionByHandle($handle);
if ($section) {
Craft::$app->sections->deleteSection($section);
}
}
}
/**
* Create FieldGroups based on the $fieldGroupConfigs
*
* @param array $fieldGroupConfigs
* @return void
* @throws InvalidConfigException
*/
protected function createFieldGroups(array $fieldGroupConfigs): void
{
foreach ($fieldGroupConfigs as $fieldGroupConfig) {
$name = $fieldGroupConfig['name'];
if ($this->getFieldGroupByName($name)) {
echo "Group {$name} already exists" . PHP_EOL;
continue;
}
$group = Craft::createObject($fieldGroupConfig);
Craft::$app->getFields()->saveGroup($group);
}
}
/**
* Delete FieldGroups based on the $fieldGroupConfigs
*
* @param array $fieldGroupConfigs
* @return void
*/
protected function deleteFieldGroups(array $fieldGroupConfigs): void
{
foreach ($fieldGroupConfigs as $fieldGroupConfig) {
$name = $fieldGroupConfig['name'];
$group = $this->getFieldGroupByName($name);
if ($group) {
Craft::$app->getFields()->deleteGroup($group);
}
}
}
/**
* Create Fields based on $fieldConfigs
*
* @param array $fieldConfigs
* @param string $groupName
* @return void
* @throws InvalidConfigException
* @throws \Throwable
*/
protected function createFields(array $fieldConfigs, string $groupName): void
{
$group = $this->getFieldGroupByName($groupName);
if (!$group) {
echo "FieldGroup {$groupName} doesn't exist" . PHP_EOL;
return;
}
$fieldsService = Craft::$app->getFields();
foreach ($fieldConfigs as $fieldConfig) {
$handle = $fieldConfig['handle'];
if ($fieldsService->getFieldByHandle($handle)) {
echo "Field {$handle} already exists" . PHP_EOL;
continue;
}
$field = Craft::createObject(array_merge($fieldConfig, [
'groupId' => $group->id,
]));
$fieldsService->saveField($field);
}
}
/**
* Delete Fields based on $fieldConfigs
*
* @param array $fieldConfigs
* @return void
* @throws \Throwable
*/
protected function deleteFields(array $fieldConfigs): void
{
$fieldsService = Craft::$app->getFields();
foreach ($fieldConfigs as $fieldConfig) {
$handle = $fieldConfig['handle'];
$field = $fieldsService->getFieldByHandle($handle);
if ($field) {
Craft::$app->getFields()->deleteField($field);
}
}
}
/**
* Add the Fields to the $sectionHandle
*
* @param array $fieldConfigs
* @param string $sectionHandle
* @param string $entryTypeHandle
* @return void
* @throws \Throwable
* @throws EntryTypeNotFoundException
*/
protected function addFieldsToSection(array $fieldConfigs, string $sectionHandle, string $entryTypeHandle): void
{
$section = Craft::$app->sections->getSectionByHandle($sectionHandle);
if (!$section) {
echo "Section {$sectionHandle} doesn't exist" . PHP_EOL;
return;
}
$entryType = $this->getEntryTypeByHandle($section, $entryTypeHandle);
if (!$entryType) {
echo "EntryType {$entryTypeHandle} doesn't exist" . PHP_EOL;
return;
}
// Get all of our fields
$elements = [];
foreach ($fieldConfigs as $fieldConfig) {
$handle = $fieldConfig['handle'];
$field = Craft::$app->getFields()->getFieldByHandle($handle);
if (!$field) {
echo "Field {$handle} doesn't exist" . PHP_EOL;
continue;
}
$elements[] = Craft::createObject([
'class' => CustomField::class,
'fieldUid' => $field->uid,
'required' => false,
]);
}
// Just assign the fields to the first tab
$layout = $entryType->getFieldLayout();
$tabs = $layout->getTabs();
$tabs[0]->setElements(array_merge($tabs[0]->getElements(), $elements));
$layout->setTabs($tabs);
$entryType->setFieldLayout($layout);
Craft::$app->sections->saveEntryType($entryType);
}
/**
* Remove the Fields from the Section
* @param array $fieldConfigs
* @param string $sectionHandle
* @param string $entryTypeHandle
* @return void
*/
protected function removeFieldsFromSection(array $fieldConfigs, string $sectionHandle, string $entryTypeHandle): void
{
// Do nothing, these will be destroyed along with the Section
}
/**
* Create the Entry data
*
* @param array $fieldConfigs
* @param string $sectionHandle
* @param string $entryTypeHandle
* @param string $userName
* @param int $numEntries
* @return void
* @throws InvalidConfigException
* @throws \Throwable
* @throws ElementNotFoundException
* @throws Exception
*/
protected function createEntryData(array $fieldConfigs, string $sectionHandle, string $entryTypeHandle, string $userName, int $numEntries)
{
$faker = \Faker\Factory::create();
$section = Craft::$app->getSections()->getSectionByHandle($sectionHandle);
if (!$section) {
echo "Section {$sectionHandle} doesn't exist" . PHP_EOL;
return;
}
$entryType = $this->getEntryTypeByHandle($section, $entryTypeHandle);
if (!$entryType) {
echo "EntryType {$entryTypeHandle} doesn't exist" . PHP_EOL;
return;
}
$user = Craft::$app->users->getUserByUsernameOrEmail($userName);
if (!$user) {
echo "User {$userName} doesn't exist" . PHP_EOL;
return;
}
for ($i = 0; $i < $numEntries; $i++) {
$name = $faker->unique()->name();
/* @var Entry $entry */
$entry = Craft::createObject([
'class' => Entry::class,
'sectionId' => $section->id,
'typeId' => $entryType->id,
'authorId' => $user->id,
'title' => $name,
]);
// Just the essentials for bulk import/creation
$entry->setScenario(Element::SCENARIO_ESSENTIALS);
foreach ($fieldConfigs as $fieldConfig) {
$handle = $fieldConfig['handle'];
$entry->setFieldValue($handle, $name);
}
if (Craft::$app->elements->saveElement($entry)) {
echo "#{$i} - Added entry {$name}" . PHP_EOL;
} else {
echo "#{$i} - Failed to add entry {$name}" . PHP_EOL;
}
}
}
/**
* Delete all entries from $sectionHandle
*
* @param string $sectionHandle
* @return void
* @throws \Throwable
*/
protected function deleteEntryData(string $sectionHandle): void
{
// There are more efficient ways to do this, but whatever
$section = Craft::$app->getSections()->getSectionByHandle($sectionHandle);
if (!$section) {
echo "Section {$sectionHandle} doesn't exist" . PHP_EOL;
return;
}
$i = 0;
foreach (Entry::find()->sectionId($section->id)->ids() as $entryId) {
if (Craft::$app->elements->deleteElementById($entryId, null, null, true)) {
echo "#{$i} - Deleted entry id {$entryId}" . PHP_EOL;
} else {
echo "#{$i} - Failed to delete entry id {$entryId}" . PHP_EOL;
}
$i++;
}
}
/**
* Get an EntryType by $entryTypeHandle
*
* @param Section $section
* @param string $entryTypeHandle
* @return EntryType|null
*/
private function getEntryTypeByHandle(Section $section, string $entryTypeHandle): ?EntryType
{
$entryTypes = $section->getEntryTypes();
foreach ($entryTypes as $entryType) {
if ($entryType->handle === $entryTypeHandle) {
return $entryType;
}
}
return null;
}
/**
* Get a field group by $name
*
* @param string $name
* @return ?FieldGroup
*/
private function getFieldGroupByName(string $name): ?FieldGroup
{
$groups = Craft::$app->getFields()->getAllGroups();
foreach ($groups as $group) {
if ($group->name === $name) {
return $group;
}
}
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment