Quinn Labs: A website about building websites

SS2.4toSS3.1

Converting Quinn.com from SilverStripe 2.4 to 3.1

(This article first appeared as a SilverStripe.org blog post by Fred Condo.)

tl;dr

  • We did a 2.4 → 3.1-beta conversion
  • We made the minimal changes required to get the site running under the new framework
  • Bye, Data Object Manager!
  • The ORM’s new fluid syntax is great (unless you forget to save the result!)
  • It was easy!

About our upgrade

When QI began using SilverStripe, we converted our static HTML site to SilverStripe 2.3. Over time, we upgraded to 2.4, and eagerly awaited SilverStripe 3.

We’ve now done new sites starting with SilverStripe 3.0, and are ready to convert sites from 2.4. To prepare to upgrade our clients, we converted our own site. We also changed our make– and rsync-based deployment to something more robust, Capistrano.

We chose to convert to SilverStripe 3.1-beta, reasoning that, by the time our clients started approving upgrade projects, 3.1 would be the generally available release. We also did not want to optimize at this stage, and do only what was necessary to run under 3.1.

Converting our code base

The conversion took 91 git commits. Twelve were Capistrano-related, so 79 commits pertained to SilverStripe and code-management overhead, such as merge commits. Because this was our learning experience, we kept changes as separate commits, rather than squash them together.

Our mysite/code directory contained 2942 lines of code before the conversion, and 2820 afterwards, a reduction of 4%. Our template files had 1399 lines before, and 1366 afterwards, a 2.4% reduction.

Template changes

The new template engine is strict about include files, so it uncovered includes that referenced nonexistent files. We just removed those lines.

<% loop %> does what <% control %> once did. There is other new syntax, too.

-   <% if ShowInToolsPages %>
-        <% control ShowInToolsPages %>
+    <% if $ShowInToolsPages %>
+        <% loop $ShowInToolsPages %>
             <% if First %><% else %> | <% end_if %>
             <a href="$Link">$MenuTitle</a>
-        <% end_control %>
+        <% end_loop %>
     <% end_if %>

Directives now accept $ on variable names, and loop replaces control.

Template calls now take comma-separated parameters. Where we used a string with artificial delimiters, we now have a more familiar syntax. $Image.CroppedImage(165x165) becomes $Image.CroppedImage(165,165).

PHP changes

Module updates

We had to update modules to their 3.x-compatible versions. This often meant importing the code from the master branch. Many module repositories were not clearly tagged with 2.4– and 3.x-compatible versions. It required trial-and-error to get some modules running.

We could eliminate several modules, because the new framework is more capable:

  • Data Object Manager
  • Uploadify
  • Image Extension (our internal module of image-manipulation methods; we eventually kept 2 methods—see below)

Data Object Manager by Aaron “Uncle Cheese” Carlino is popular for SilverStripe 2.x. It is not available for 3.x, because GridField supersedes it. Here’s a sample change (from a getCMSFields() method) for replacing it:

-      $manager = new DataObjectManager(
-               $this, # Controller
-               'GalleryItems', # Source name
-               'GalleryItem', # Source class
-               array(
-                   'Thumbnail' => 'Thumbnail',
-                   'Title' => 'Title',
-                   'Link' => 'URL',
-               ), # Headings
-               'getCMSFields', # Detail fields (function name or FieldSet object)
-               null, # Filter clause
-               'SortOrder ASC'# Sort clause
-           # Join clause
+       # FeaturedProjects config
+       $manager_config = new GridFieldConfig_RelationEditor();
+
+       # Add sortable
+       $manager_config->addComponent(new GridFieldSortableRows('SortOrder'));
+
+       # FeaturedProjects field
+       $manager = new GridField(
+           'GalleryItems',
+           'Gallery Items',
+           $this->GalleryItems(),
+           $manager_config
        );

ORM changes and fluid syntax

The ORM has undergone a big overhaul. DataObjectSet is gone, and a new fluid syntax simplifies data operations. To set up paginated items, we once had to manage pagination-related GET parameters, but can now focus on our custom code. Below, we use a GET parameter only for filtering on a year. See this line:

$result = $result->filter('Date:StartsWith', $year);

We tripped over this when it wasn’t working without assigning back to $result. The new ORM returns a result rather than modifying the existing one. This works nicely with the fluid, chained syntax, but needs help when a call is conditional.

function PaginatedContent($limit = 10) {
    $result = ArticlePage::get()
            ->sort('Date', 'DESC')
            ->filterAny(array(
        'RealParentID' => $this->ID,
        'ParentID' => $this->ID,
    ));

    # add year if set
    if (isset($_GET['year']) && preg_match("/\d\d\d\d/", $_GET['year'])) {
        $year = $_GET['year'];
        $result = $result->filter('Date:StartsWith', $year);
    }
    $result = new PaginatedList($result, $this->request);
    $result->setPageLength($limit);
    return $result;
}

Redirection functions have moved classes

Director::redirect() and related functions have moved to Controller, and become instance methods.

               if ($parent->ClassName == 'ArticleIndex' && preg_match('/\d\d\d\d/', $this->URLSegment) ) {
-                   Director::redirect( $this->parent()->Link() . '?year=' .  $this->URLSegment);
+                   $this->redirect( $this->parent()->Link() . '?year=' .  $this->URLSegment);
                }
                # Otherwise just redirect to our parent
                else {
-                   Director::redirect( $this->parent()->Link() );
+                   $this->redirect( $this->parent()->Link() );
                }
            }
        }

Image extension

Here are the functions from our image extension that we still wanted after the 3.1 upgrade.

class ImageExtension extends DataExtension {

    public function TopCroppedImage($width, $height) {
        return $this->owner->getFormattedImage('TopCroppedImage', $width, $height);
    }

    public function generateTopCroppedImage(GD $gd, $width, $height) {

        # resize
        $gd = $gd->resizeByWidth($width);

        # crop to top
        $gd = $gd->crop(0, 0, $width, $height);

        return $gd;
    }

}

About the Author

  • Fred Condo

    Fred has a Ph.D. in the Management of Information Systems and Cognitive Psychology from Claremont Graduate School, and is Chief Engineer at Quinn Interactive. He taught a graduate level class, “Principles of Usability” at the Academy of Art in San Francisco, and was an Assistant Professor in the Communication Design department at California State University, Chico for 6 years, teaching web development and human factors. Fred got into SilverStripe in 2008. He has made 35 commits to the SilverStripe framework repository, which makes him the 36th-most prolific contributor to the project (as of November 2014). You should follow him on GitHub here.

    More from Fred Condo