Faloude Faloude - 16 days ago 6
PHP Question

SilverStripe unique URL (debug)

In this DataObject there is a user supplied field Title which has to be converted to a unique URL slug.

Desired Result: Duplicate URL's should get a suffix to its value. So saving 2 records with Title

Foo
should result in one record with
foo
as its value for column
URL
and the second record should have value
foo-2
for the same column.

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

// Sanitize Title field to use for URL
$filter = URLSegmentFilter::create();
$this->URL = $filter->filter($this->Title);

// If URL is not unique, add suffix
$i = 1;
while($this->uniqueURL($this->URL)) {
$i++;
$this->URL = $this->URL . "-" . $i;
}
}


method: uniqueURL (within same class)

public function uniqueURL($URL) {

// Check if there is a record with the same URL
$existingURL = DataObject::get('NewsArticle', "URL = '$URL'");
if ($existingURL) {
// this is a duplicate URL
return false;
} else {
// this is a unique url
return true;
}
}


Saving
Foo
twice would result in
foo
and
foo-2
.

When saving two records with the same Title
Foo
results in two URL fields with
foo

Answer

Why do you have two foo urls?

If you check your DB before inserting all records, this means that the check will not work on your record batch.

Don't use a loop to count unique urls

You don't need to loop and check every time and increment the count ($i). Performance wise youre far better off doing a COUNT() in a query and just use that value for your next insert.

// The following does exactly the same with just 1 query. No loop needed.
$count = DB::query("SELECT COUNT(*) FROM Table WHERE Title LIKE '{$filteredTitle}'")->value();
if ($count > 1) {
    $filteredTitle .= "-" .  $count;
}
$this->URL = $filteredTitle

Solutions

To do it onBeforeWrite() the only possibility is to Query your data AND check your records before they are saved.

Or a simpler solution with the same results is that you can change the url in an onAfterWrite() , and check use the amount of same titles as number.

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

    // Sanitize Title field to use for URL
    $filter = URLSegmentFilter::create();
    $filteredTitle= $filter->filter($this->Title);

    $count = DB::query("SELECT COUNT(*) FROM Table WHERE Title LIKE '{$filteredTitle}'")->value();
    if ($count > 1) {
        $filteredTitle .= "-" .  $count;
    }
    $this->URL = $filteredTitle
}
Comments