fromholdio/silverstripe-resourceful
最新稳定版本:2.0.0
Composer 安装命令:
composer require fromholdio/silverstripe-resourceful
包简介
关键字:
README 文档
README
A powerful configuration-driven module for SilverStripe that eliminates repetitive inheritance pattern setup. Resourceful provides a consistent UX and ORM structure for fields and relations that can inherit from parent objects or fall back to site-wide defaults.
Table of Contents
- Overview
- Why This Module?
- Requirements
- Installation
- Core Concepts
- Quick Start
- Configuration
- Usage
- Advanced Features
- API Reference
- Real-World Examples
- License
Overview
Resourceful solves a common problem in hierarchical SilverStripe projects: providing users with the ability to inherit values or relations from parent pages or use site-wide defaults, without writing repetitive boilerplate code for each field.
What it provides:
- Configuration-driven inheritance - Define inheritance patterns in YAML
- Automatic CMS fields - Generates "Inherit from parent" checkboxes and source selectors
- Flexible source options - Local, parent, site, or custom sources
- Consistent UX - Same interface pattern across all inheritable fields
- Zero boilerplate - No repetitive code for each inheritable field
Why This Module?
Without Resourceful, implementing field inheritance requires:
-
Database fields for each inheritable value:
'SidebarArea_DoInherit' => 'Boolean', 'SidebarArea_Source' => 'Varchar(10)', 'SidebarArea_Local' => 'has_one relation',
-
Getter methods with inheritance logic:
public function getSidebarArea() { if ($this->SidebarArea_DoInherit && $this->Parent()->exists()) { return $this->Parent()->getSidebarArea(); } switch ($this->SidebarArea_Source) { case 'site': return SiteConfig::curr()->SidebarArea(); case 'local': return $this->SidebarArea_Local(); case 'none': return null; } }
-
CMS fields with display logic:
$fields->addFieldToTab('Root.Sidebar', CheckboxField::create('SidebarArea_DoInherit', 'Inherit from parent') ); $wrapper = Wrapper::create( OptionsetField::create('SidebarArea_Source', 'Source', [...]) ); $wrapper->displayIf('SidebarArea_DoInherit')->isNotChecked(); // ... more field logic
With Resourceful, all of this becomes:
Page: resourceful: SidebarArea: sources: inherit: 'parent' select: 'site|local|none' default: 'site' relations: site: '->getSidebarConfig' cms_fields: tab_path: 'Root.Sidebar'
public function getSidebarArea() { return $this->getResourcefulValue('SidebarArea'); }
That's it. Resourceful handles everything else.
Requirements
- SilverStripe CMS ^6.0
- PHP 8.3+
- fromholdio/silverstripe-checkboxfieldgroup ^1.2.0
- fromholdio/silverstripe-cms-fields-placement ^1.2.0
- unclecheese/display-logic ^4.0.0
Installation
composer require fromholdio/silverstripe-resourceful
Core Concepts
Sources
A source is where a value comes from. Resourceful provides built-in sources:
local- Value stored directly on this objectparent- Inherited from parent object (hierarchical)site- Site-wide default (from SiteConfig or custom)none- No value (null)default- Use the configured default source
Inheritance
When inherit is enabled, the value is retrieved from the parent object recursively until a non-inheriting value is found.
Source Selection
Users can select which source to use via CMS fields (unless forced by configuration).
Configuration Structure
Each resourceful field has:
- Name - The field name (e.g., 'SidebarArea')
- Sources - Which sources are available and which is default
- Values - How to retrieve values from each source (field names or methods)
- Relations - How to traverse to parent/site objects
- CMS Fields - Where to place fields in the CMS
Quick Start
1. Apply Extension
use Fromholdio\Resourceful\Extensions\ResourcefulExtension; use SilverStripe\CMS\Model\SiteTree; class Page extends SiteTree { private static $extensions = [ ResourcefulExtension::class, ]; }
2. Configure Resourceful Field
Page: resourceful: SidebarContent: sources: inherit: 'parent' select: 'site|local|none' default: 'site' relations: site: 'Site' cms_fields: tab_path: 'Root.Sidebar'
3. Add Database Fields
class Page extends SiteTree { private static $db = [ 'SidebarContent_DoInherit' => 'Boolean', 'SidebarContent_Source' => 'Varchar(10)', 'SidebarContent_Local' => 'HTMLText', ]; private static $defaults = [ 'SidebarContent_DoInherit' => true, 'SidebarContent_Source' => 'default', ]; }
4. Create Getter Method
public function getSidebarContent(): ?string { return $this->getResourcefulValue('SidebarContent'); }
5. Use in Templates
<% if $SidebarContent %> <aside> $SidebarContent </aside> <% end_if %>
That's it! Resourceful automatically:
- Generates CMS fields with inheritance checkbox
- Handles source selection UI
- Traverses parent hierarchy
- Falls back to site default
- Manages display logic
Configuration
Basic Configuration
Page: resourceful: FieldName: enabled: true # Enable/disable this resourceful field sources: force: null # Force a specific source (overrides user selection) inherit: 'parent' # Enable inheritance from parent select: 'site|local|none' # Available sources for user selection default: 'site' # Default source when none selected values: local: 'FieldName_Local' # Field name for local value parent: 'FieldName' # Field name on parent site: 'FieldName' # Field name on site config relations: parent: 'Parent' # Relation to parent object site: 'Site' # Relation to site object cms_fields: tab_path: 'Root.Main' # Where to place CMS fields source_field_class: 'SilverStripe\Forms\OptionsetField' # Field class for source selector
Source Configuration
sources.force - Force a specific source, ignoring user selection:
sources: force: 'site' # Always use site default
sources.inherit - Enable inheritance from parent:
sources: inherit: 'parent' # Inherit from parent when checkbox checked
sources.select - Available sources for user selection:
sources: select: 'site|local|none' # User can choose between these
sources.default - Default source when none selected:
sources: default: 'site' # Default to site-wide value
Value Configuration
Field names:
values: local: 'FieldName_Local' # Database field on this object parent: 'FieldName' # Field name on parent object site: 'FieldName' # Field name on site config
Method names (prefix with ->):
values: local: '->getLocalFieldName' # Call method instead of field site: '->getSiteFieldName'
Multiple fallbacks (pipe-separated):
values: local: '->getFieldName|FieldName_Local' # Try method first, then field
Forced inheritance (set {inherit} to true):
values: '{inherit}': true # Force inheritance, no checkbox shown
When '{inherit}': true:
- No DoInherit checkbox shown in CMS
- Inheritance always active (automatic fallback)
- Local field still shown for user input
- No DoInherit database field needed
Relation Configuration
Relation names:
relations: parent: 'Parent' # has_one relation to parent site: 'Site' # has_one relation to site
Method names (prefix with ->):
relations: parent: '->getParentObject' # Call method to get parent site: '->getSiteConfig' # Call method to get site
Required relations:
relations: '{require}': 'parent|site' # These sources require relation to exist
CMS Fields Configuration
Tab path:
cms_fields: tab_path: 'Root.Main' # Place in Main tab
Field placement (relative to another field):
cms_fields: placement: 'before' # or 'after' field: 'Content' # Place before/after this field
Settings fields (for Settings tab):
settings_fields: tab_path: 'Root.Settings'
Usage
In PHP
Getting Values
// Get resourceful value (handles inheritance automatically) $value = $page->getResourcefulValue('FieldName'); // Check if field is enabled if ($page->isResourcefulEnabled('FieldName')) { // Field is enabled } // Check if field is inheritable if ($page->isResourcefulInheritable('FieldName')) { // Field can inherit from parent } // Check if field is currently inheriting if ($page->isResourcefulInherited('FieldName')) { // Field is inheriting from parent }
Working with Resourceful Instance
// Get Resourceful instance $resourceful = $page->getResourceful('FieldName'); // Get current source $source = $resourceful->getSource(); // 'local', 'parent', 'site', 'none' // Get value from specific source $localValue = $resourceful->getSourceValue('local'); $siteValue = $resourceful->getSourceValue('site'); // Check if source is available if ($resourceful->isSourceAvailable('parent')) { // Parent source is available } // Get available sources $sources = $resourceful->getAvailableSources(); // ['local', 'site', 'none']
In Templates
<!-- Use resourceful value --> <% if $SidebarContent %> <aside> $SidebarContent </aside> <% end_if %> <!-- Check if inheriting --> <% if $isResourcefulInherited('SidebarContent') %> <p>Inheriting from parent</p> <% end_if %> <!-- Conditional based on source --> <% if $SidebarContent %> <% if $isResourcefulInherited('SidebarContent') %> <p class="inherited">Inherited content</p> <% else %> <p class="local">Local content</p> <% end_if %> <% end_if %>
Custom Getter Methods
Create custom getter methods for cleaner templates:
public function getSidebarContent(): ?string { return $this->getResourcefulValue('SidebarContent'); } public function getSidebarArea(): ?EvoElementalArea { return $this->getResourcefulValue('SidebarArea'); }
Then in templates:
<% if $SidebarContent %> $SidebarContent <% end_if %> <% if $SidebarArea %> <% loop $SidebarArea.Elements %> $Me <% end_loop %> <% end_if %>
Advanced Features
Custom Source Methods
Define custom methods to retrieve values:
Page: resourceful: SidebarArea: values: local: '->getLocalSidebarArea' site: '->getSiteSidebarArea' relations: site: '->getSidebarConfig'
public function getLocalSidebarArea(): ?EvoElementalArea { return $this->SidebarArea_Local(); } public function getSiteSidebarArea(): ?EvoElementalArea { return $this->getSidebarConfig()->SidebarArea(); } public function getSidebarConfig(): SiteConfig { return SiteConfig::current_site_config(); }
Custom CMS Fields
Override automatic field generation:
public function getSidebarAreaCMSFields(): FieldList { return FieldList::create( // Your custom fields ); }
Per-Source CMS Fields
Add fields that appear only when a specific source is selected:
public function getCMSFields_SidebarArea_local( bool $isInherited, ?string $selectedSource, bool $isDefault, ?string $fieldName, ?string $relationName ): FieldList { return FieldList::create( // Fields for local source HTMLEditorField::create('SidebarContent_Local', 'Sidebar Content') ); } public function getCMSFields_SidebarArea_site( bool $isInherited, ?string $selectedSource, bool $isDefault, ?string $fieldName, ?string $relationName ): FieldList { return FieldList::create( // Fields for site source (read-only preview, etc.) ReadonlyField::create('SitePreview', 'Site Default', $this->getSiteConfig()->SidebarContent ) ); }
Forcing Sources
Force a specific source via configuration:
HomePage: resourceful: SidebarArea: sources: force: 'none' # Home page never has sidebar
Or force with fallback:
Page: resourceful: SidebarArea: sources: force: 'parent|site' # Try parent, fallback to site
Forced Inheritance (No Checkbox)
Force inheritance without showing a checkbox:
Page: resourceful: HeroHeadline: sources: inherit: 'page' select: 'local' default: 'local' values: '{inherit}': true # Force inheritance, no checkbox page: 'Title' # Inherit from Title field
Result:
- No DoInherit checkbox shown
- Only local text field shown
- Inheritance always active (automatic fallback)
- When local field is empty, uses Title field value
- No DoInherit database field needed
Use Case: Automatic fallback to another field without user choice (e.g., use page title as hero headline if custom headline is empty)
Database Fields:
private static $db = [ 'HeroHeadline_Local' => 'Varchar(255)', // Only local field needed ];
Comparison with Optional Inheritance:
# Optional inheritance (shows checkbox) HeroLede: values: '{inherit}': 'HeroLede_DoInherit' # Field name = checkbox shown page: 'Lede' # Forced inheritance (no checkbox) HeroHeadline: values: '{inherit}': true # Boolean true = no checkbox page: 'Title'
Disabling Auto-Placement
Disable automatic CMS field placement:
Page: do_auto_place_resourceful_cms_fields: false
Then manually place fields:
public function getCMSFields(): FieldList { $fields = parent::getCMSFields(); // Manually place resourceful fields $resourceful = $this->getResourceful('SidebarArea'); $fields = $resourceful->placeCMSFields($fields); return $fields; }
Multiple Resourceful Fields
Configure multiple fields at once:
Page: resourceful: SidebarArea: sources: inherit: 'parent' select: 'site|local|none' default: 'site' cms_fields: tab_path: 'Root.Sidebar' FooterArea: sources: inherit: 'parent' select: 'site|local|none' default: 'site' cms_fields: tab_path: 'Root.Footer' HeaderImage: sources: inherit: 'parent' select: 'local|none' default: 'local' cms_fields: tab_path: 'Root.Main'
Multisite Support
Resourceful automatically detects multisite modules:
Page: resourceful: SidebarArea: relations: site: 'Site' # Automatically uses Site relation if multisite installed
If multisite is not installed, falls back to SiteConfig::current_site_config().
API Reference
ResourcefulExtension
Applied to DataObjects that use resourceful fields.
Methods
// Get Resourceful instance public function getResourceful(string $name): Resourceful // Check if enabled public function isResourcefulEnabled(string $name): bool // Get value public function getResourcefulValue(string $name) // Check if inheritable public function isResourcefulInheritable(string $name): bool // Check if inherited public function isResourcefulInherited(string $name): bool // Get source field options public function getResourcefulSourceFieldOptions(string $name): ?array
Resourceful Class
Core class that handles resourceful logic.
Factory Methods
// Create instance public static function inst(DataObject $dObj, string $name): Resourceful // Get all resourceful field names public static function getAllNames(DataObject $dObj): ?array // Set defaults for all fields public static function setAllFieldDefaults(DataObject $dObj): void // Place all CMS fields public static function placeAllCMSFields(FieldList $fields, DataObject $dObj): FieldList // Place all settings fields public static function placeAllSettingsFields(FieldList $fields, DataObject $dObj): FieldList
Configuration Methods
// Get configuration data public function getConfigData(): ?array // Get specific config value public function getConfigValue(string $key, ?array $data = null) // Check if enabled public function isEnabled(): bool // Get field names public function getFieldName(): string public function getDoInheritFieldName(): ?string public function getSourceFieldName(): ?string
Source Methods
// Get current source public function getSource(): ?string // Get value public function getValue() // Check inheritance public function isInheritable(): bool public function isInherited(): bool // Get specific sources public function getInheritSource(): ?string public function getDefaultSource(): ?string public function getForceSource(): ?string public function getSelectSources(): ?array public function getSelectedSource(): ?string // Get value from source public function getSourceValue(?string $source) // Get relation for source public function getSourceRelation(?string $source): ?DataObject // Check source availability public function isSourceAvailable(?string $source): bool public function getAvailableSources(): ?array
CMS Field Methods
// Get CMS fields public function getCMSFields(): ?FieldList public function getDoInheritCMSField(): ?CheckboxFieldGroup public function getSourceCMSField(): ?FormField public function getCMSFieldsForSource(string $source): ?FieldList // Place CMS fields public function placeCMSFields(FieldList $fields, bool $doRemoveFields = false): FieldList public function placeSettingsFields(FieldList $fields, bool $doRemoveFields = false): FieldList public function removeCMSFields(FieldList $fields): FieldList
Real-World Examples
Example 1: Inheritable Sidebar
Page: extensions: - Fromholdio\Resourceful\Extensions\ResourcefulExtension resourceful: SidebarArea: sources: inherit: 'parent' select: 'site|local|none' default: 'site' relations: site: '->getSidebarConfig' cms_fields: tab_path: 'Root.Sidebar'
class Page extends SiteTree { private static $db = [ 'SidebarArea_DoInherit' => 'Boolean', 'SidebarArea_Source' => 'Varchar(10)', ]; private static $has_one = [ 'SidebarArea_Local' => SidebarElementalArea::class, ]; private static $defaults = [ 'SidebarArea_DoInherit' => true, 'SidebarArea_Source' => 'default', ]; public function getSidebarArea(): ?EvoElementalArea { return $this->getResourcefulValue('SidebarArea'); } public function getSidebarConfig(): SiteConfig { return SiteConfig::current_site_config(); } }
SilverStripe\SiteConfig\SiteConfig: extensions: - Fromholdio\Resourceful\Extensions\ResourcefulExtension has_one: SidebarArea: SidebarElementalArea
Result: Pages can inherit sidebar from parent, use site-wide default, use custom local sidebar, or have no sidebar.
Example 2: Header Image with Parent Inheritance
Page: resourceful: HeaderImage: sources: inherit: 'parent' select: 'local|none' default: 'local' cms_fields: placement: 'before' field: 'Content'
class Page extends SiteTree { private static $db = [ 'HeaderImage_DoInherit' => 'Boolean', 'HeaderImage_Source' => 'Varchar(10)', ]; private static $has_one = [ 'HeaderImage_Local' => Image::class, ]; public function getHeaderImage(): ?Image { return $this->getResourcefulValue('HeaderImage'); } }
Result: Pages can inherit header image from parent or use their own.
Example 3: Multiple Areas with Resourceful
Page: resourceful: SideArea: sources: inherit: 'parent' select: 'site|local|none' default: 'site' relations: site: '->getSideAreaConfig' cms_fields: tab_path: 'Root.SidebarTabSet.SidebarMainTab' BelowArea: sources: inherit: 'parent' select: 'site|local|none' default: 'site' relations: site: '->getBelowAreaConfig' cms_fields: tab_path: 'Root.FooterTabSet.FooterMainTab'
class Page extends SiteTree { private static $db = [ 'SideArea_DoInherit' => 'Boolean', 'SideArea_Source' => 'Varchar(10)', 'BelowArea_DoInherit' => 'Boolean', 'BelowArea_Source' => 'Varchar(10)', ]; private static $has_one = [ 'SideArea_Local' => SideElementalArea::class, 'BelowArea_Local' => BlocksElementalArea::class, ]; public function getSideArea(): ?EvoElementalArea { return $this->getResourcefulValue('SideArea'); } public function getBelowArea(): ?EvoElementalArea { return $this->getResourcefulValue('BelowArea'); } public function getSideAreaConfig(): SiteConfig { return SiteConfig::current_site_config(); } public function getBelowAreaConfig(): SiteConfig { return SiteConfig::current_site_config(); } }
Result: Both sidebar and footer areas can independently inherit from parent or use site defaults.
Example 4: Forced Inheritance for Hero Fields
Page: resourceful: HeroHeadline: sources: inherit: 'page' select: 'local' default: 'local' values: '{inherit}': true # Forced inheritance page: 'Title' cms_fields: tab_path: 'Root.HeroTabSet.HeroMainTab' HeroLede: sources: inherit: 'page' select: 'local' default: 'local' values: '{inherit}': 'HeroLede_DoInherit' # Optional inheritance page: 'Lede' cms_fields: tab_path: 'Root.HeroTabSet.HeroMainTab'
class Page extends SiteTree { private static $db = [ 'HeroHeadline_Local' => 'Varchar(255)', // No DoInherit field 'HeroLede_DoInherit' => 'Boolean', // Has DoInherit field 'HeroLede_Local' => 'Text', ]; public function getHeroHeadline(): string { return $this->getResourcefulValue('HeroHeadline'); } public function getHeroLede(): string { return $this->getResourcefulValue('HeroLede'); } }
Result:
- HeroHeadline: Always falls back to page Title if empty (no checkbox)
- HeroLede: User can choose to inherit from page Lede or use custom (checkbox shown)
License
BSD-3-Clause
Support
- GitHub: https://github.com/fromholdio/silverstripe-resourceful
- Issues: https://github.com/fromholdio/silverstripe-resourceful/issues
Contributing
Contributions are welcome! Please open an issue or pull request on GitHub.
统计信息
- 总下载量: 263
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 1
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: BSD-3-Clause
- 更新时间: 2024-01-06