From b4fdbfd6c4a4fb2f44213f69ba1e11794c7a30d6 Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Fri, 3 Apr 2015 16:56:46 +0100 Subject: [PATCH] Issue #2457405 by stefan.r, andypost: DateTimePlus violates substitution principle of DateTime. Make it support PHP 7 --- .../Component/Datetime/DateTimePlus.php | 66 +++++++++++++++---- .../Core/Datetime/Element/DateElementBase.php | 3 +- .../Component/Datetime/DateTimePlusTest.php | 2 +- 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/core/lib/Drupal/Component/Datetime/DateTimePlus.php b/core/lib/Drupal/Component/Datetime/DateTimePlus.php index fb52a800e50d..95f6ba5e908b 100644 --- a/core/lib/Drupal/Component/Datetime/DateTimePlus.php +++ b/core/lib/Drupal/Component/Datetime/DateTimePlus.php @@ -7,26 +7,26 @@ namespace Drupal\Component\Datetime; /** - * Extends DateTime(). + * Wraps DateTime(). * - * This class extends the PHP DateTime class with more flexible initialization + * This class wraps the PHP DateTime class with more flexible initialization * parameters, allowing a date to be created from an existing date object, * a timestamp, a string with an unknown format, a string with a known * format, or an array of date parts. It also adds an errors array * and a __toString() method to the date object. * - * This class is less lenient than the parent DateTime class. It changes + * This class is less lenient than the DateTime class. It changes * the default behavior for handling date values like '2011-00-00'. - * The parent class would convert that value to '2010-11-30' and report + * The DateTime class would convert that value to '2010-11-30' and report * a warning but not an error. This extension treats that as an error. * - * As with the base class, a date object may be created even if it has + * As with the DateTime class, a date object may be created even if it has * errors. It has an errors array attached to it that explains what the * errors are. This is less disruptive than allowing datetime exceptions * to abort processing. The calling script can decide what to do about * errors using hasErrors() and getErrors(). */ -class DateTimePlus extends \DateTime { +class DateTimePlus { const FORMAT = 'Y-m-d H:i:s'; @@ -91,6 +91,13 @@ class DateTimePlus extends \DateTime { */ protected $errors = array(); + /** + * The DateTime object. + * + * @var \DateTime + */ + protected $dateTimeObject = NULL; + /** * Creates a date object from an input date object. * @@ -126,7 +133,7 @@ class DateTimePlus extends \DateTime { $date_parts = static::prepareArray($date_parts, TRUE); if (static::checkArray($date_parts)) { // Even with validation, we can end up with a value that the - // parent class won't handle, like a year outside the range + // DateTime class won't handle, like a year outside the range // of -9999 to 9999, which will pass checkdate() but // fail to construct a date object. $iso_date = static::arrayToISO($date_parts); @@ -251,7 +258,7 @@ class DateTimePlus extends \DateTime { } if (empty($this->errors)) { - parent::__construct($prepared_time, $prepared_timezone); + $this->dateTimeObject = new \DateTime($prepared_time, $prepared_timezone); } } catch (\Exception $e) { @@ -266,7 +273,7 @@ class DateTimePlus extends \DateTime { /** * Implements __toString() for dates. * - * The base DateTime class does not implement this. + * The DateTime class does not implement this. * * @see https://bugs.php.net/bug.php?id=62911 * @see http://www.serverphorums.com/read.php?7,555645 @@ -276,6 +283,43 @@ class DateTimePlus extends \DateTime { return $this->format($format) . ' ' . $this->getTimeZone()->getName(); } + /** + * Implements the magic __call method. + * + * Passes through all unknown calls onto the DateTime object. + */ + public function __call($method, $args) { + // @todo consider using assert() as per https://www.drupal.org/node/2451793. + if (!isset($this->dateTimeObject)) { + throw new \Exception('DateTime object not set.'); + } + if (!method_exists($this->dateTimeObject, $method)) { + throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_class($this), $method)); + } + return call_user_func_array(array($this->dateTimeObject, $method), $args); + } + + /** + * Implements the magic __callStatic method. + * + * Passes through all unknown static calls onto the DateTime object. + */ + public static function __callStatic($method, $args) { + if (!method_exists('\DateTime', $method)) { + throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_called_class(), $method)); + } + return call_user_func_array(array('\DateTime', $method), $args); + } + + /** + * Implements the magic __clone method. + * + * Deep-clones the DateTime object we're wrapping. + */ + public function __clone() { + $this->dateTimeObject = clone($this->dateTimeObject); + } + /** * Prepares the input time value. * @@ -349,7 +393,7 @@ class DateTimePlus extends \DateTime { * @see http://us3.php.net/manual/en/time.getlasterrors.php */ public function checkErrors() { - $errors = $this->getLastErrors(); + $errors = \DateTime::getLastErrors(); if (!empty($errors['errors'])) { $this->errors += $errors['errors']; } @@ -541,7 +585,7 @@ class DateTimePlus extends \DateTime { // Format the date and catch errors. try { - $value = parent::format($format); + $value = $this->dateTimeObject->format($format); } catch (\Exception $e) { $this->errors[] = $e->getMessage(); diff --git a/core/lib/Drupal/Core/Datetime/Element/DateElementBase.php b/core/lib/Drupal/Core/Datetime/Element/DateElementBase.php index 56eb9cf7b48e..8e359158d549 100644 --- a/core/lib/Drupal/Core/Datetime/Element/DateElementBase.php +++ b/core/lib/Drupal/Core/Datetime/Element/DateElementBase.php @@ -35,7 +35,8 @@ abstract class DateElementBase extends FormElement { * described by this pattern. */ protected static function datetimeRangeYears($string, $date = NULL) { - $this_year = date_format(new DrupalDateTime(), 'Y'); + $datetime = new DrupalDateTime(); + $this_year = $datetime->format('Y'); list($min_year, $max_year) = explode(':', $string); // Valid patterns would be -5:+5, 0:+1, 2008:2010. diff --git a/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php b/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php index cdb68a20b5b7..08c8af792881 100644 --- a/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php +++ b/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php @@ -238,7 +238,7 @@ class DateTimePlusTest extends UnitTestCase { */ public function testDateTimezoneWithDateTimeObject() { // Create a date object with another date object. - $input = new DateTimePlus('now', 'Pacific/Midway'); + $input = new \DateTime('now', new \DateTimeZone('Pacific/Midway')); $timezone = NULL; $expected_timezone = 'Pacific/Midway'; $message = 'DateTimePlus uses the specified timezone if provided.';