Skip navigation
All Places > Developer > Blog > 2016 > July
2016

Sugar and older versions of PHP

 

The latest version of PHP that Sugar 6.5 supported was PHP 5.3 which was released back in 2009. That is a long time ago folks. A sizable portion of the Sugar Developer community was in grade school back when PHP 5.3 was cool.

 

While we like to keep Sugar on the cutting edge, we have not been able to keep up to date with the latest PHP release. We have needed to maintain an upgrade path in each new Sugar release for customers still on Sugar 6.5. That is, up until now.

 

PHP Support matrix

 

As of Sugar 7.7.1 release:

Sugar version Supported PHP versions
 6.5.x  5.2  5.3
 7.6.x  5.3  5.4
 7.7.0  5.3  5.4  5.5
 7.7.1  5.3  5.4  5.5  5.6

 

We do not plan on supporting 4 different versions of PHP forever, so you can expect to see support for older versions of PHP, such as PHP 5.3, to be dropped in upcoming Sugar releases.

 

For the complete and up-to-date reference of supported PHP versions, please view our Supported Platforms documentation.

 

Read on to learn more about why you should upgrade.

 

Benefits of PHP 5.6

 

Fact is, there are a ton of benefits of running Sugar on PHP 5.6 instead of earlier PHP versions. Anyone running Sugar On-Site should be evaluating how soon they can upgrade.

 

Improved Performance

 

Our performance team has estimated about a 30% improvement in performance in our test environment after upgrading from PHP 5.4 to PHP 5.6.

 

I don't know anybody who does not like their software fast.

 

Zend OPcache built-in

 

Who loves installing and configuring APC , Zend OPcache, or WinCache in order to get accelerated PHP?  Nobody.

 

With PHP 5.6, Zend OPcache is not only built-in, it is enabled by default.  It is also demonstrably faster than the alternatives.  Also worth mentioning for Windows users is that WinCache is now deprecated in favor of the built-in OPcache.

 

Here are some suggested settings for using Zend OPcache with Sugar instances from the Performance team. These settings are a good starting point then you can optimize based on your individual instance's needs.

 

Just add the following to your php.ini file.

; Suggested OPcache settings for Sugar

 

opcache.max_accelerated_files = 10000

 

opcache.memory_consumption = 256

 

opcache.fast_shutdown = 1

 

opcache.interned_strings_buffer = 16

 

Ultimately, utilizing the built-in PHP accelerator helps reduces the amount of work necessary to deploy and maintain a Sugar supported stack with accelerated PHP.

 

Long Term Support until the end of 2018

 

The community support windows for older versions of PHP have been expiring. But Long Term Support for PHP 5.6 ends in December 2018. So PHP 5.6 in production environments is a safe bet for the long haul.



 

Upgrading our Backbone

 

Have you done some Sidecar programming lately? Then you have been using Backbone. Backbone is the err... backbone of Sidecar. It provides all the base MVC classes which are extended by Sidecar to create the Sugar 7 UI. For example, all Sidecar controllers (Views, Layouts, Fields) extend from the Backbone View class.

 

Ultimately, a solid background in Backbone programming will turn you into a Sidecar wizard in no time.

 



 

But if you are a Backbone aficionado then you might have noticed that Sugar 7.7 and earlier versions uses an old version of Backbone (specifically Backbone 0.9.10). We have been missing out on bug fixes and miscellaneous feature improvements. So for Sugar 7.8 we will be moving to Backbone 1.2.3.  Since Backbone.js has a hard dependency on Underscore.js, we will also upgrade the Underscore library from 1.4.4 to 1.8.3.All Sugar Developers should check out the Backbone changelog and the Underscore changelog to see if their code customizations could be impacted by this long overdue library upgrade.

 

Read on to learn more about some adjustments you need to make to your Sugar code.

 

Changes to Sidecar controller DOM event delegation

 

In Backbone 1.2.0, there was an important change that affects how DOM events are delegated in Backbone Views. Emphasis mine.

Views now always delegate their events in setElement. You can no longer modify the events hash or your view's el property in initialize.

 

This means that modifying this.events in the initialize() function of a Backbone View to register DOM event handlers is no longer supported by Backbone. This is because DOM events set in the events hash (this.events) are delegated before initialize() is even called. However, since this was a common practice for Sugar code and customizations we have altered the default Backbone behavior within Sidecar for the Sugar 7.8 release.Sugar will continue to continue to call delegateEvents() during initialize() in Sugar 7.8 for compatibility but the practice is deprecated since Backbone no longer supports it. Sidecar controllers that modify this.events during initialize() will continue to work until this workaround is removed in an upcoming Sugar release.

 

Here is a simple example of a Sidecar view that uses this deprecated practice.

 

A simple example

./custom/clients/base/views/example/example.js

 

[code language="javascript"]

 

/** This approach is deprecated in Sugar 7.8 release  **/

 

({

 

    events: {...},

 

    initialize: function(options) {

 

        if (...) {

 

            this.events['click'] = function(e){...};

 

        }

 

        this._super('initialize', [options]);

 

    },

 

    ...

 

})

 

 

 

This will not work in a future Sugar release.

 

A Record View use case

 

Let's examine a common Sidecar customization use case.

 

Say we need to extend the out of the box Sugar RecordView controller to launch a wizard user interface on a mouse click.

 

We plan to listen for a special DOM click event but we also do not want to break any existing Record view event listeners.

 

To implement this feature, Sugar Developers commonly used code such as the following:./custom/..../clients/base/views/record/record.js

 

[code language="javascript"]

 

/** This approach is deprecated in Sugar 7.8 release  **/

 

({

 

    extendsFrom: 'RecordView',

 

    initialize: function(options) {

 

        // Extending the RecordView events in a deprecated fashion

 

        this.events = _.extend({}, this.events, {

 

            'click .wizard': '_launchWizard'

 

        });

 

        this._super('initialize', [options]);

 

    },

 

    _launchWizard: function(){

 

      // ... do something ...

 

    }

 

})

 

 

 

To reiterate, the examples above will no longer work in a future Sugar release. Sugar Developers should update any similar code to use alternative approaches listed below.

 

Event Delegation Alternatives

 

Here are some alternatives that you can use for delegating DOM events with your Sidecar controllers.

 

Statically define your events hash

 

Define your events all within the events object hash. Note that when extending controllers that this would override any events defined on a parent controller.

 

[code language="javascript"]

 

({

 

    events: {

 

        'mousedown .title': 'edit',

 

        'click .button': 'save',

 

        'click .open': function(e) { ... }

 

    }

 

    ...

 

})

 

 

 

Use a callback function for dynamic events

 

You can assign the events variable of Backbone controllers a function instead of an object hash.  This function will then be used to determine the event hash used when delegateEvents() is called by Backbone.

 

[code language="javascript"]

 

({

 

    events: function(){

 

        if (...) {

 

            return {'click .one': function(){...}};

 

        } else {

 

            return {'click .two': function(){...}};

 

        }

 

    }

 

    ...

 

})

 

 

 

If you must, then call delegateEvents() function directly

 

You can optionally pass an alternative event hash using this.delegateEvents(events). When unspecified, this.events is used by default. The delegateEvents() function removes any previously delegated events at same time so it is safe to call multiple times.

 

[code language="javascript"]

 

({

 

    extendsFrom: 'RecordView',

 

    oneEvents: {...},

 

    twoEvents: {...},

 

    isOne = true,

 

    toggleOneTwo: function(){

 

        if (this.isOne) {

 

            this.delegateEvents(_.extend({}, this.events, this.oneEvents));

 

        } else {

 

            this.delegateEvents(_.extend({}, this.events, this.twoEvents));

 

        }

 

        this.isOne = !this.isOne;

 

    }

 

    ...

 

})

 

 

 

Other important Backbone changes

  • Backbone.js no longer attaches options to the Backbone.View instance by default (as of 1.2.0). Sugar Developers should know we plan to deprecate this.options on Sidecar controllers in a future Sugar release.
  • This upgrade may also break customizations of Sidecar routes that expect URL parameters to be concatenated to the first argument passed to the Backbone router's callback. Sugar Developers should change the signature of their router callbacks to specify the additional argument for URL parameters.

 

For example:Old way:

 

[code language="javascript"]

 

// in a sugar7.js equivalent file

 

{

 

    name: 'search',

 

    route: 'search(/)(:termAndParams)',

 

    callback: function(termAndParams) {

 

        // termAndParams => "?module=Accounts&foo=bar"

 

        // commence ugly URL parsing...

 

    }

 

}

 

New way:

 

[code language="javascript"]

 

// in a sugar7.js equivalent file

 

{

 

    name: 'search',

 

    route: 'search(/)(:term)',

 

    callback: function(term, urlParams) {

 

        // term => "this is a search term"

 

        // urlParams => "module=Accounts&foo=bar"

 

        // no more ugly URL parsing!

 

    }

 

}

 

 

  • Potential Breaking Change: Sugar customizations that override the sync method on any instances of Backbone.Model and Backbone.Collection should should be updated to match Backbone's new signatures for the internal success/error callbacks for Model#fetch, Model#destroy, Model#save, and Collection#fetch methods.

 

For example:Old way, Backbone < 0.9.10:

 

[code language="javascript"]

 

    // in a custom sidecar controller:

 

    sync: function(method, model, options) {

 

        // custom sync method

 

        ...

 

    options.success = _.bind(function(model, data, options) {

 

        this.collection.reset(model, data, options);

 

    }, this);

 

// in Backbone.js's Collection#fetch method...

 

    fetch: function(options) {

 

        options = options ? _.clone(options) : {};

 

        if (options.parse === void 0) options.parse = true;

 

        var success = options.success;

 

        // *** Note: 'collection', 'resp', 'options' are passed ***

 

        options.success = function(collection, resp, options) {

 

            var method = options.update ? 'update' : 'reset';

 

            collection[method](resp, options);

 

            if (success) success(collection, resp, options);

 

        };

 

        return this.sync('read', this, options);

 

    },

 

New way, Backbone > 1.x:

 

[code language="javascript"]

 

    // in a custom sidecar controller:

 

    sync: function(method, model, options) {

 

        // custom sync method

 

        ...

 

    // *** Only data should now be passed here ***

 

    options.success = _.bind(function(data) {

 

        this.collection.reset(data);

 

    }, this);

 

    // in Backbone.js's Collection#fetch method...

 

    fetch: function(options) {

 

        options = _.extend({parse: true}, options);

 

        var success = options.success;

 

        var collection = this;

 

        // Note: the success callback is now only passed 'resp'

 

        options.success = function(resp) {

 

            var method = options.reset ? 'reset' : 'set';

 

            collection[method](resp, options);

 

            if (success) success.call(options.context, collection, resp, options);

 

            collection.trigger('sync', collection, resp, options);

 

        };

 

        wrapError(this, options);

 

        return this.sync('read', this, options);

 

    },

 

 

  • Potential Breaking Change: The method signature for error callbacks for Model#fetch, Model#destroy, Model#save, and Collection#fetch has changed. The Backbone.Model or Backbone.Collection is passed as the first parameter and the second parameter is now the HttpError XHR object.

 

For example:Old way:

 

[code language="javascript"]

 

    this.model.save({}, {

 

        error: function(error) {

 

            ...

 

        }

 

    });

 

New Way:

 

[code language="javascript"]

 

    this.model.save({}, {

 

        error: (model, error) {

 

            ...

 

        }

 

    });

 

 

  • Potential Breaking Change: Sugar customizations that set the id property directly on a Backbone.Model will not work with Backbone Collections. Sugar Developers should always use Backbone's internal APIs/methods, meaning they should be using model.set('id', ...) instead.

 

For example:

 

[code language="javascript"]

 

var model = app.data.createBean('Accounts', {id: 'foo'});

 

var collection = app.data.createBeanCollection('Accounts');

 

collection.add(model);

 

model.id = 'bar';

 

console.log(collection.get('bar'));

 

 

Output >> undefined

 

Use model.set('id', 'bar'); instead:

 

[code language="javascript"]

 

var model = app.data.createBean('Accounts', {id: 'foo'});

 

var collection = app.data.createBeanCollection('Accounts');

 

collection.add(model);

 

model.set('id', 'bar');

 

console.log(collection.get('bar'));

 

 

Output >> model