Preparing for the Library Upgrades coming on 12.3 (Q1 2023)
Sugar utilizes multiple PHP libraries in its core that will undergo major version upgrades in the 12.3 (Q1 2023) release. Some of those libraries have significant and potentially breaking changes.
Note that 12.3 also includes the new UI Redesign, therefore, you must plan accordingly to incorporate the new UI changes as well as these library upgrades in your customizations.
In this article, we will highlight a subset of libraries upgraded in this release and how you can prepare/plan for them. These are the most impactful major version library upgrades in 12.3.
Library |
From |
To |
doctrine/dbal |
^2.13.2 |
3.3.7 |
monolog/monolog |
1.22.0 |
^2.8.0 |
smarty/smarty |
3.1.45 |
^4.2.0 |
moontoast/math |
1.1.0 |
brick/math@0.9.3 |
guzzlehttp/guzzle |
6.3.3 |
^6.5.8 |
You will notice in some versions Sugar has added the prefix character caret (“^”). It allows Composer to resolve to the latest minor version of the package, you can check more details about it in this blog post. Basically, minor version upgrades to these libraries that do not introduce backward compatibility issues are possible in subsequent Sugar releases.
Who is affected by this change?
For most customers, we expect this change to be seamless and without a noticeable impact. However, if you have customized Sugar and used low-level 3rd party classes in the vendor library then you will be affected by these upgrades. SugarCloud customers will be impacted first with the 12.3 upgrades. On-premise customers should expect the upgrade in Sugar 13.0 based on Sugar's typical release cadence.
What will happen during the upgrade?
The Sugar upgrader will examine your instance’s filesystem looking for incompatible code. The upgrader will identify known incompatibilities with Doctrine DBAL and Monolog. For other minor upgrades, SugarCRM doesn’t know of any issues therefore no actions will be taken by the upgrader.
Smarty on the other hand has gone through major upgrades that introduce breaking changes. Sugar is working on a solution to minimize the impact, and our final recommendation will be posted in our customization guide. For now, we suggest you prepare and plan for this change.
What action do I need to take?
At the very least, you should plan to test the customizations that exist in your Sugar instance and ensure they still work as expected after the upgrade.
If you've built an app or integration for Sugar that uses a Module Loadable Package (MLP) that includes code related to:
- PSR-3 Logger or Monolog custom handlers
- Doctrine DBAL or DBManager Object
- Any custom Smarty Templates (especially if using
{php}
and{include_php}
)
Then you will need to update your code manually to use the new library version’s syntax. You will also need to update your module manifest to indicate compatibility with Sugar 12.3+.
To proactively identify and resolve library upgrade issues prior to GA, we encourage you to participate in the upcoming Sugar Release Previews for Sugar 12.3 (SugarCloud release) and Sugar 13.0 (on-premise release). If you are not a member of the Sugar Release Preview program and would like to be added, get in touch with developers@sugarcrm.com.
Most changes cannot be verified until you are running Sugar 12.3. But depending on your particular customizations, you may find some things that you can fix right now or plan to test when the release preview is out. Continue reading below to see if you think you can get a head start on any of the changes we are highlighting.
Significant breaking changes Doctrine DBAL 2.x and 3.x
The official library upgrade notes contain all the breaking changes between releases.
The Doctrine\DBAL\DBALException
class has been renamed to Doctrine\DBAL\Exception
.
Replace |
With |
$id = '1234-abcde-fgh45-6789'; $query = 'SELECT * FROM accounts WHERE id = :id'; $conn = $GLOBALS['db']->getConnection(); // before try { $result = $conn->executeQuery($query, ['id' => $id]); } catch (\Doctrine\DBAL\DBALException $e) { // ... } |
$id = '1234-abcde-fgh45-6789'; $query = 'SELECT * FROM accounts WHERE id = :id'; $conn = $GLOBALS['db']->getConnection(); // after $result = $conn->executeQuery($query, ['id' => $id]); try { $result = $conn->executeQuery($query, ['id' => $id]); } catch (\Doctrine\DBAL\Exception $e) { // ... } |
The usage of the colon prefix when binding named parameters is no longer supported.
Replace |
With |
$id = '1234-abcde-fgh45-6789'; $query = 'SELECT * FROM accounts WHERE id = :id'; $conn = $GLOBALS['db']->getConnection(); // before $result = $conn->executeQuery($query, [':id' => $id]); |
|
Dropped handling of one-based numeric arrays of parameters in Statement::execute()
The statement implementations no longer detect whether $params
is a zero- or one-based array. A zero-based numeric array is expected.
Replace |
With |
$id = '1234-abcde-fgh45-6789'; $query = 'SELECT * FROM accounts WHERE id = ?'; $conn = $GLOBALS['db']->getConnection(); // before $stmt = $conn->prepare($query); $result = $stmt->execute([1 => $id]); |
$id = '1234-abcde-fgh45-6789'; $query = 'SELECT * FROM accounts WHERE id = ?'; $conn = $GLOBALS['db']->getConnection(); // after $stmt = $conn->prepare($query); $result = $stmt->executeQuery([$id]); |
Removed FetchMode
and the corresponding methods
- The
FetchMode
class and thesetFetchMode()
method of the Connection and Statement interfaces are removed. - The
Statement::fetch()
method is replaced withResult::fetchNumeric(),
Result::fetchAssociative()
andResult::fetchOne().
- The
Statement::fetchColumn()
method is replaced with
when fetching the first column or more generally withfetchOne()
fetchNumeric()
to get the required column by number from its result
Replace |
With |
$conn = $GLOBALS['db']->getConnection(); // before $result = $conn->executeQuery("SELECT * FROM accounts WHERE id='1234-abcde-fgh45-6789'")->fetch(FetchMode::NUMERIC); $result = $conn->executeQuery("SELECT * FROM accounts WHERE id='1234-abcde-fgh45-6789'")->fetch(FetchMode::ASSOCIATIVE); $result = $conn->executeQuery("SELECT name FROM accounts WHERE id='1234-abcde-fgh45-6789'")->fetch(FetchMode::COLUMN); |
$conn = $GLOBALS['db']->getConnection(); // after $result = $conn->executeQuery("SELECT * FROM accounts WHERE id='1234-abcde-fgh45-6789'")->fetchNumeric(); $result = $conn->executeQuery("SELECT * FROM accounts WHERE id='1234-abcde-fgh45-6789'")->fetchAssociative(); $result = $conn->executeQuery("SELECT name FROM accounts WHERE id='1234-abcde-fgh45-6789'")->fetchOne(); |
The Statement::fetchAll()
method is replaced with Result::fetchAllNumeric(), Result::fetchAllAssociative()
and Result::fetchFirstColumn().
Replace |
With |
$conn = $GLOBALS['db']->getConnection(); // before $result = $conn->executeQuery("SELECT * FROM accounts WHERE parent_id='1234-abcde-fgh45-6789'")->fetchAll(FetchMode::NUMERIC); $result = $conn->executeQuery("SELECT * FROM accounts WHERE parent_id='1234-abcde-fgh45-6789'")->fetchAll(FetchMode::ASSOCIATIVE); $result = $conn->executeQuery("SELECT name FROM accounts WHERE parent_id='1234-abcde-fgh45-6789'")->fetchAll(FetchMode::COLUMN); |
$conn = $GLOBALS['db']->getConnection(); // after $result = $conn->executeQuery("SELECT * FROM accounts WHERE parent_id='1234-abcde-fgh45-6789'")->fetchAllNumeric(); $result = $conn->executeQuery("SELECT * FROM accounts WHERE parent_id='1234-abcde-fgh45-6789'")->fetchAllAssociative(); $result = $conn->executeQuery("SELECT name FROM accounts WHERE parent_id='1234-abcde-fgh45-6789'")->fetchFirstColumn(); |
The Connection::fetchArray()
and fetchAssoc()
methods are replaced with fetchNumeric()
and fetchAssociative()
respectively.
Replace |
With |
$conn = $GLOBALS['db']->getConnection(); // before $result = $conn->fetchArray("SELECT * FROM accounts WHERE id='1234-abcde-fgh45-6789'"); $result = $conn->fetchAssoc("SELECT * FROM accounts WHERE id='1234-abcde-fgh45-6789'"); |
$conn = $GLOBALS['db']->getConnection(); // after $result = $conn->fetchNumeric("SELECT * FROM accounts WHERE id='1234-abcde-fgh45-6789'"); $result = $conn->fetchAssociative("SELECT * FROM accounts WHERE id='1234-abcde-fgh45-6789'"); |
The functionality previously available via Statement::closeCursor()
is now available via Result::free().
Replace |
With |
$conn = $GLOBALS['db']->getConnection(); // before $result = $conn->fetchAll(); $result->closeCursor(); |
$conn = $GLOBALS['db']->getConnection(); // before $result = $conn->fetchAllAssociative(); $result->free(); |
The StatementIterator
class is removed. The usage of a Statement object as Traversable is no longer possible. Use iterateNumeric(), iterateAssociative()
and iterateColumn()
instead.
Replace |
With |
$conn = $GLOBALS['db']->getConnection(); // before $stmt = $conn->executeQuery("SELECT * FROM accounts"); foreach ($stmt as $row) { $name = $row['name']; // do something } |
$conn = $GLOBALS['db']->getConnection(); // after $result = $conn->executeQuery("SELECT * FROM accounts"); foreach ($result->iterateAssociative() as $row) { $name = $row['name']; // do something } |
Note: Fetching data in mixed mode (former FetchMode::MIXED
) is no longer possible.
Significant breaking changes Monolog
The official library upgrade notes contain all the breaking changes between releases. If you have used Monolog Handlers in your customizations, pay attention to those:
- In Monolog v2 some class methods have changed signatures (and related Mango methods too), namely, they have declared return type - this breaks inheritance. Please review the customizations code and if you extend classes or implement interfaces from Monolog, make sure that your methods have compatible signatures, usually it means adding a return type.
Replace |
With |
// Monolog LineFormatter method, before public function format(array $record) |
// Monolog LineFormatter method, after public function format(array $record): string |
// Custom formatter - return type is wider than parent, it causes PHP fatal error public function format(array $record) |
// Custom formatter fixed - compatible with both Monolog v1 and v2 public function format(array $record): string |
List of affected Classes:
//All classes in Monolog\Handler and Monolog\Processor namespaces Sugarcrm\Sugarcrm\Logger\Formatter Sugarcrm\Sugarcrm\Logger\Formatter\BackwardCompatibleFormatter Monolog\Formatter\FlowdockFormatter Monolog\Formatter\FluentdFormatter Monolog\Formatter\GelfMessageFormatter Monolog\Formatter\HtmlFormatter Monolog\Formatter\JsonFormatter Monolog\Formatter\LineFormatter Monolog\Formatter\LogglyFormatter Monolog\Formatter\LogstashFormatter Monolog\Formatter\MongoDBFormatter Monolog\Formatter\ScalarFormatter
HandlerInterface
now requires the close method to be implemented. This only impacts you if you implement the interface yourself, but you can extend the newMonolog\Handler\Handler
base class too.- Some handlers have been renamed/removed/reconfigured, see upgrade notes if you instantiate or inherit them (
LogglyFormatter, AmqpHandler, RotatingFileHandler, LogstashFormatter, HipChatHandler, SlackbotHandler, RavenHandler, ElasticSearchHandler
) - Removed non-PSR-3 methods to add records, all the add* (e.g.
addWarning
) methods as well asemerg
,crit, err
andwarn
. It may affect those customers, who use PSR-3 compatible logger in Sugar, but call Monolog-specific methods likeaddWarning
instead of standard methods, listed in Sugar documentation (seems to be a very uncommon case)
Replace |
With |
use \Sugarcrm\Sugarcrm\Logger\Factory; $logger = Factory::getLogger('default'); // no longer supported $logger->addDebug('something happened'); $logger->addInfo('something happened'); $logger->addNotice('something happened'); $logger->addWarning('something happened'); $logger->addError('something happened'); $logger->addCritical('something happened'); $logger->addAlert('something happened'); $logger->addEmergency('something happened'); $logger->warn('something happened'); $logger->err('something happened'); $logger->crit('something happened'); $logger->emerg('something happened'); |
use \Sugarcrm\Sugarcrm\Logger\Factory; $logger = Factory::getLogger('default'); // replace with these $logger->debug('something happened'); $logger->info('something happened'); $logger->notice('something happened'); $logger->warning('something happened'); $logger->error('something happened'); $logger->critical('something happened'); $logger->alert('something happened'); $logger->emergency('something happened'); |