Luc Shelton

SilverStripe: Generating URL Segments for DataObjects

SilverStripe: Generating URL Segments for DataObjects

SilverStripe: Generating URL Segments for DataObjects

SilverStripe: Generating URL Segments for DataObjects

4 Minute(s) to read
Posted 23 months ago Updated 23 months ago 4 Minute(s) to read 8 comments

As documented on the official SilverStripe website, you can render DataObjects as individual pages using controller actions. This is fine if you're comfortable in using the ID of the DataObject as part of the page URL, but if you're looking to create and use human-friendly URLs similar to that of SiteTree pages, then you will want to implement something that generates hyphenated and sanitized URL segments instead.

You can consider a "URL segment" to be a sanitized, human-friendly readable string generated from the Title field of the DataObject it is intended for. It doesn't contain any special URL encoding characters, and strictly only uses alpha-numeric characters. Anything that isn't alpha numeric is replaced by a hyphen.

By default, these are automatically generated for pages that derive from the SiteTree type. Unfortunately this same kind of behaviour does not exist for DataObjects, but this can be easily remedied by introducing a new data extension to your project and applying it to your DataObject type's list of extensions.

<?php

use SilverStripe\ORM\DataExtension;
use SilverStripe\View\Parsers\URLSegmentFilter;

class URLSegmentExtension extends DataExtension
{
    private static $db = [
        'URLSegment' => 'Varchar(255)'
    ];

    public function onBeforeWrite()
    {
        parent::onBeforeWrite();

        if ($this->owner->hasField('URLSegment')) {
            if (!$this->owner->URLSegment) {
                $this->owner->URLSegment = $this->generateURLSegment($this->owner->Title);
            }

            if (!$this->owner->isInDB() || $this->owner->isChanged('URLSegment')) {
                $this->owner->URLSegment = $this->generateURLSegment($this->owner->URLSegment);
                $this->makeURLSegmentUnique();
            }
        }
    }
    
    public function IsURLSegmentInUse($URLSegment)
    {
        $class = $this->owner;
        $items = $class::get()->filter('URLSegment', $URLSegment);

        if ($this->owner->ID > 0) {
            $items = $items->exclude('ID', $this->owner->ID);
        }

        return $items->exists();
    }

    public function makeURLSegmentUnique()
    {
        $count = 2;
        $currentURLSegment = $this->owner->URLSegment;

        while ($this->IsURLSegmentInUse($currentURLSegment)) {
            $currentURLSegment = preg_replace('/-[0-9]+$/', '', $currentURLSegment) . '-' . $count;
            ++$count;
        }

        $this->owner->URLSegment = $currentURLSegment;
    }

    public function generateURLSegment($title)
    {
        $filter = URLSegmentFilter::create();
        $filteredTitle = $filter->filter($title);

        $ownerClassName = $this->owner->ClassName;
        $ownerClassName = strtolower($ownerClassName);

        if (!$filteredTitle || $filteredTitle == '-' || $filteredTitle == '-1') {
            $filteredTitle = "$ownerClassName-$this->ID";
        }

        return $filteredTitle;
    }
}

Breakdown

There's a bit to digest here, but the summary of what's going on in this extension is the following.

  1. This extension registers the field "URLSegment" to whichever DataObject that this extension type is appended to through the .yml configuration file.
  2. Each time the DataObject is published or written through from the Object-Relational Mapping system, it will automatically generate a new URL segment string if none has been generated yet.
  3. It achieves this by checking the URLSegment field, and if it has not been changed or updated, then it will attempt to use the Title field of the DataObject to generate a string suitable for the URLSegment database field.
  4. It checks recursively to see if there is an existing DataObject in the database that has the generated URLSegment already.
  5. If there is an existing DataObject in the database with the generated URLSegment, it will instead increment an integer, append it to the string, and recursively check again to see if the newly generated URLSegment has already been assigned.
  6. Once it has determined that the generated string is not in use, it will then apply it to the DataObject's field and continue with committing the data to the database.

Usage

Now that we have an applicable URLSegment string for our DataObject, we can put it into use in a similar manner as what has already been described on these pages. Except, in this instance we will be making use of the URLSegment field for filtering (based on the $Action value) instead of the ID of the DataObject.

The code for achieving this would look something like this.

if (is_numeric($params['ID'])) {
    $eventImages = EventImage::get()->filter([
        'EventID' => $this->dataRecord->ID,
        'ID' => intval($params['ID'])
    ]);
} else {
    $eventImages = EventImage::get()->filter([
        'EventID' => $this->dataRecord->ID,
        'URLSegment' => $params['ID']
    ]);
}

Ensure that you have configured your project to use this data extension. In the example configuration file below, I am applying the extension that I've written above to the DataObject that I want the URL segments to be generated for.

Once the configuration file is saved, simply navigate to /dev/build?flush=all on your project, and this should now automatically generate the URL segments each time you publish modifications to your DataObjects.

---
Name: urlsegments
---
Portfolio\Models\TechnologyTag:
  extensions:
    - Portfolio\Extensions\URLSegmentExtension

And that's all there is to it. You could make further modifications if you like, including ensuring that the URLSegment field for a DataObject uses the appropriate form field when modifying it from the CMS admin.


Programming Languages:

PHP


Comments

Comments


You have made your point!
<a href="https://writinganessaycollegeservice.com/">essay writing service blog</a> executive resume writing service <a href="https://essayservicehelp.com/">unique essay writing service</a> best essay writing service australia


Fantastic advice. Kudos!
<a href="https://argumentativethesis.com/">example thesis</a> strong thesis statement <a href="https://bestmasterthesiswritingservice.com/">tentative thesis</a> thesis statements
<a href=https://ouressays.com/>parts of a research proposal</a> write my research paper <a href=https://researchpaperwriterservices.com/>thesis proposal</a> write my term paper
words to use in college essays https://topswritingservices.com


Cheers, I enjoy this.
<a href="https://argumentativethesis.com/">a thesis statement</a> thesis topic <a href="https://bestmasterthesiswritingservice.com/">thesis statment</a> thesis writing


Fantastic write ups. Appreciate it!
<a href="https://englishessayhelp.com/">essaytyper</a> medical school personal statement <a href="https://essaywritinghelperonline.com/">help with writing an essay</a> essay help


With thanks. Plenty of tips.
<a href="https://ouressays.com/">buy term paper</a> buy research paper <a href="https://researchpaperwriterservices.com/">research paper writers</a> research proposal cover page


Great tips. With thanks.
<a href="https://theessayswriters.com/">write essay for money</a> write my college essays <a href="https://bestcheapessaywriters.com/">essay writer com</a> do my essay online


Seriously lots of valuable knowledge.
<a href="https://quality-essays.com/">pay essay</a> buy essay online review <a href="https://buyanessayscheaponline.com/">buy a essay</a> essay paper for sale
<a href=https://topswritingservices.com/>best essay writing service 2016</a> seo article writing service <a href=https://essaywriting4you.com/>best essay writing service</a> customer service essay
how to write a dissertation https://cheapessaywriteronlineservices.com


Awesome forum posts, Regards!
write my law essay uk <a href="https://gseomail.com/">write my history essay</a> who can write a book for me