diff --git a/.travis.yml b/.travis.yml index 705f43493..c5df530f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,6 +56,9 @@ before_script: - phpenv config-rm xdebug.ini - echo 'memory_limit = 2048M' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini +# Temporarily update to 1.5.x-dev of composer + - composer self-update --snapshot + # Install composer dependencies - composer validate - composer install --prefer-dist diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index 451397217..4ba1342f5 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -30,6 +30,7 @@ guide developers in preparing existing 3.x code for compatibility with 4.0 arrangement of templates, as well as other references to classes via string literals or configuration. Automatic upgrading tools have been developed to cope with the bulk of these changes (see [upgrading notes](#upgrading)). +* Object class has been removed. * Asset storage has been abstracted, and a new concept of `DBFile` references via database column references now exists in addition to references via the existing `File` dataobject. File security and protected files are now a core feature. @@ -161,6 +162,90 @@ Note also that `$_FILE_TO_URL_MAPPING` has been removed and replaced with `SS_BA See [Environment Management docs](/getting-started/environment_management/) for full details. +#### Replace usages of Object class + +Object has been superseded by a trio of traits which replace components of this legacy class: + + - Injectable: Provides `MyClass::create()` and `MyClass::singleton()` + - Configurable: Provides `MyClass::config()` + - Extensible: Provides all methods related to extensions (E.g. add_extension()). Note: + All classes which use this trait MUST invoke `constructExtensions()` in its constructor. + +In particular specific Object class usages should be replaced as below: + + +Upgrade subclasses + + + :::php + // old + class MyClass extends Object {} + // new + class MyClass { + use Extensible; + use Injectable; + use Configurable; + + public function __construct() { + // Only needed if using Extensible trait + $this->constructExtensions(); + } + } + + +References to $this->class + + + :::php + // old + $obj->class + $this->class; + // new + get_class($obj); + static::class; + + +Upgrade parse_class_spec() + + + :::php + // old + $spec = Object::parse_class_spec($spec); + // new + $spec = ClassInfo::parse_class_spec($spec); + + +Upgrade create_from_string() + + + :::php + // old + $obj = Object::create_from_string('Varchar(100)'); + // new + $obj = Injector::inst()->create('Varchar(100)'); + + +Extensions + + + :::php + // old + Object::add_extension('File', 'Versioned'); + $has = Object::has_extension('File', 'Versioned'); + $extensions = Object::get_extensions('File'); + // new + File::add_extension(Versioned::class); + $has = File::has_extension(Versioned::class) + $extensions = File::get_extensions(); + + // new (alternate form) + // Note: The class the extension method is called on MUST be a parent class + // of the first argument. + DataObject::add_extension(File::class, Versioned::class); // alternate + $has = DataObject::has_extension(File::class, Versioned::class); // alternate + $extensions = DataObject::get_extensions(File::class); + + #### Compatibility with the new front-end building tools If you are using Requirements from 3.x then your scripts should continue to work as they did before. @@ -1203,6 +1288,10 @@ After (`mysite/_config/config.yml`): * Introduced new ModuleLoader manifest, which allows modules to be found via composer name. E.g. `$cms = ModuleLoader::inst()->getManifest()->getModule('silverstripe/cms')` * `ClassManifest::getOwnerModule()` now returns a `Module` object instance. +* `Object` class has been removed. + * `parse_class_spec` moved to `ClassInfo` + * `create_from_spec` functionality removed, but now supportede by `Injector` natively. + * `Injectable`, `Extensible` and `Configurable` traits added to support other methods. * Certain methods have been moved from `Controller` to `RequestHandler`: * `Link` * `redirect` diff --git a/src/Control/CliController.php b/src/Control/CliController.php index e63503d34..de67e28fe 100644 --- a/src/Control/CliController.php +++ b/src/Control/CliController.php @@ -32,7 +32,7 @@ abstract class CliController extends Controller public function index() { - foreach (ClassInfo::subclassesFor($this->class) as $subclass) { + foreach (ClassInfo::subclassesFor(static::class) as $subclass) { echo $subclass . "\n"; /** @var CliController $task */ $task = Injector::inst()->create($subclass); diff --git a/src/Control/ContentNegotiator.php b/src/Control/ContentNegotiator.php index 601e2b5e1..c3f532cbb 100644 --- a/src/Control/ContentNegotiator.php +++ b/src/Control/ContentNegotiator.php @@ -3,7 +3,8 @@ namespace SilverStripe\Control; use SilverStripe\Core\Config\Config; -use SilverStripe\Core\Object; +use SilverStripe\Core\Config\Configurable; +use SilverStripe\Core\Injector\Injectable; /** * The content negotiator performs "text/html" or "application/xhtml+xml" switching. It does this through @@ -31,8 +32,10 @@ use SilverStripe\Core\Object; * Some developers might know what they're doing and don't want ContentNegotiator messing with their * HTML4 doctypes, but still find it useful to have self-closing tags removed. */ -class ContentNegotiator extends Object +class ContentNegotiator { + use Injectable; + use Configurable; /** * @config diff --git a/src/Control/Controller.php b/src/Control/Controller.php index a521bc5c9..8701510a0 100644 --- a/src/Control/Controller.php +++ b/src/Control/Controller.php @@ -3,7 +3,6 @@ namespace SilverStripe\Control; use SilverStripe\Core\ClassInfo; -use SilverStripe\Core\Object; use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\Debug; use SilverStripe\ORM\DataModel; @@ -122,8 +121,9 @@ class Controller extends RequestHandler implements TemplateGlobalProvider $this->baseInitCalled = false; $this->init(); if (!$this->baseInitCalled) { + $class = static::class; user_error( - "init() method on class '$this->class' doesn't call Controller::init()." + "init() method on class '{$class}' doesn't call Controller::init()." . "Make sure that you have parent::init() included.", E_USER_WARNING ); @@ -231,18 +231,22 @@ class Controller extends RequestHandler implements TemplateGlobalProvider { if ($response instanceof HTTPResponse) { if (isset($_REQUEST['debug_request'])) { + $class = static::class; Debug::message( - "Request handler returned HTTPResponse object to $this->class controller;" + "Request handler returned HTTPResponse object to {$class} controller;" . "returning it without modification." ); } $this->setResponse($response); } else { - if ($response instanceof Object && $response->hasMethod('getViewer')) { + // Could be Controller, or ViewableData_Customised controller wrapper + if (ClassInfo::hasMethod($response, 'getViewer')) { if (isset($_REQUEST['debug_request'])) { + $class = static::class; + $responseClass = get_class($response); Debug::message( - "Request handler $response->class object to $this->class controller;" - . "rendering with template returned by $response->class::getViewer()" + "Request handler {$responseClass} object to {$class} controller;" + . "rendering with template returned by {$responseClass}::getViewer()" ); } $response = $response->getViewer($this->getAction())->process($response); @@ -396,26 +400,22 @@ class Controller extends RequestHandler implements TemplateGlobalProvider } elseif ($this->template) { $templates = $this->template; } else { - // Add action-specific templates for inheritance chain - $templates = array(); - if ($action && $action != 'index') { - $parentClass = $this->class; - while ($parentClass != __CLASS__) { - $templates[] = strtok($parentClass, '_') . '_' . $action; - $parentClass = get_parent_class($parentClass); + // Build templates based on class hierarchy + $actionTemplates = []; + $classTemplates = []; + $parentClass = static::class; + while ($parentClass !== parent::class) { + // _action templates have higher priority + if ($action && $action != 'index') { + $actionTemplates[] = strtok($parentClass, '_') . '_' . $action; } - } - // Add controller templates for inheritance chain - $parentClass = $this->class; - while ($parentClass != __CLASS__) { - $templates[] = strtok($parentClass, '_'); + // class templates have lower priority + $classTemplates[] = strtok($parentClass, '_'); $parentClass = get_parent_class($parentClass); } - $templates[] = __CLASS__; - - // remove duplicates - $templates = array_unique($templates); + // Add controller templates for inheritance chain + $templates = array_unique(array_merge($actionTemplates, $classTemplates)); } return new SSViewer($templates); @@ -469,7 +469,7 @@ class Controller extends RequestHandler implements TemplateGlobalProvider return $definingClass; } - $class = get_class($this); + $class = static::class; while ($class != 'SilverStripe\\Control\\RequestHandler') { $templateName = strtok($class, '_') . '_' . $action; if (SSViewer::hasTemplate($templateName)) { @@ -496,7 +496,7 @@ class Controller extends RequestHandler implements TemplateGlobalProvider return true; } - $parentClass = $this->class; + $parentClass = static::class; $templates = array(); while ($parentClass != __CLASS__) { @@ -614,8 +614,9 @@ class Controller extends RequestHandler implements TemplateGlobalProvider if ($this === self::$controller_stack[0]) { array_shift(self::$controller_stack); } else { + $class = static::class; user_error( - "popCurrent called on $this->class controller, but it wasn't at the top of the stack", + "popCurrent called on {$class} controller, but it wasn't at the top of the stack", E_USER_WARNING ); } diff --git a/src/Control/Email/Email.php b/src/Control/Email/Email.php index 3e0908e87..a01bf6f0d 100644 --- a/src/Control/Email/Email.php +++ b/src/Control/Email/Email.php @@ -573,7 +573,8 @@ class Email extends ViewableData { $this->render(); - return "

Email template {$this->class}:

\n" . '
' . $this->getSwiftMessage()->toString() . '
'; + $class = static::class; + return "

Email template {$class}:

\n" . '
' . $this->getSwiftMessage()->toString() . '
'; } /** diff --git a/src/Control/RSS/RSSFeed.php b/src/Control/RSS/RSSFeed.php index 6b31d9763..3fe5f586e 100644 --- a/src/Control/RSS/RSSFeed.php +++ b/src/Control/RSS/RSSFeed.php @@ -272,7 +272,7 @@ class RSSFeed extends ViewableData */ public function getTemplates() { - $templates = SSViewer::get_templates_by_class(get_class($this), '', __CLASS__); + $templates = SSViewer::get_templates_by_class(static::class, '', __CLASS__); // Prefer any custom template if ($this->getTemplate()) { array_unshift($templates, $this->getTemplate()); diff --git a/src/Control/RSS/RSSFeed_Entry.php b/src/Control/RSS/RSSFeed_Entry.php index 3afa70bf8..f2d733b09 100644 --- a/src/Control/RSS/RSSFeed_Entry.php +++ b/src/Control/RSS/RSSFeed_Entry.php @@ -130,7 +130,7 @@ class RSSFeed_Entry extends ViewableData } throw new BadMethodCallException( - $this->failover->class . + get_class($this->failover) . " object has neither an AbsoluteLink nor a Link method." . " Can't put a link in the RSS feed", E_USER_WARNING diff --git a/src/Control/RequestHandler.php b/src/Control/RequestHandler.php index b2b2bf313..43fdfef95 100644 --- a/src/Control/RequestHandler.php +++ b/src/Control/RequestHandler.php @@ -5,7 +5,6 @@ namespace SilverStripe\Control; use InvalidArgumentException; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; -use SilverStripe\Core\Object; use SilverStripe\Dev\Debug; use SilverStripe\ORM\DataModel; use SilverStripe\Security\Security; @@ -164,7 +163,7 @@ class RequestHandler extends ViewableData { // $handlerClass is used to step up the class hierarchy to implement url_handlers inheritance if ($this->brokenOnConstruct) { - $handlerClass = get_class($this); + $handlerClass = static::class; throw new BadMethodCallException( "parent::__construct() needs to be called on {$handlerClass}::__construct()" ); @@ -205,7 +204,7 @@ class RequestHandler extends ViewableData user_error("Non-string method name: " . var_export($action, true), E_USER_ERROR); } - $classMessage = Director::isLive() ? 'on this handler' : 'on class '.get_class($this); + $classMessage = Director::isLive() ? 'on this handler' : 'on class '.static::class; try { if (!$this->hasAction($action)) { @@ -263,7 +262,7 @@ class RequestHandler extends ViewableData */ protected function findAction($request) { - $handlerClass = ($this->class) ? $this->class : get_class($this); + $handlerClass = static::class; // We stop after RequestHandler; in other words, at ViewableData while ($handlerClass && $handlerClass != ViewableData::class) { @@ -272,14 +271,18 @@ class RequestHandler extends ViewableData if ($urlHandlers) { foreach ($urlHandlers as $rule => $action) { if (isset($_REQUEST['debug_request'])) { - Debug::message("Testing '$rule' with '" . $request->remaining() . "' on $this->class"); + $class = static::class; + $remaining = $request->remaining(); + Debug::message("Testing '{$rule}' with '{$remaining}' on {$class}"); } if ($request->match($rule, true)) { if (isset($_REQUEST['debug_request'])) { + $class = static::class; + $latestParams = var_export($request->latestParams(), true); Debug::message( - "Rule '$rule' matched to action '$action' on $this->class. ". - "Latest request params: " . var_export($request->latestParams(), true) + "Rule '{$rule}' matched to action '{$action}' on {$class}. ". + "Latest request params: {$latestParams}" ); } @@ -304,7 +307,7 @@ class RequestHandler extends ViewableData */ protected function handleAction($request, $action) { - $classMessage = Director::isLive() ? 'on this handler' : 'on class '.get_class($this); + $classMessage = Director::isLive() ? 'on this handler' : 'on class '.static::class; if (!$this->hasMethod($action)) { return new HTTPResponse("Action '$action' isn't available $classMessage.", 404); @@ -469,7 +472,7 @@ class RequestHandler extends ViewableData $isAllowed = true; } elseif (substr($test, 0, 2) == '->') { // Determined by custom method with "->" prefix - list($method, $arguments) = Object::parse_class_spec(substr($test, 2)); + list($method, $arguments) = ClassInfo::parse_class_spec(substr($test, 2)); $isAllowed = call_user_func_array(array($this, $method), $arguments); } else { // Value is a permission code to check the current member against @@ -564,7 +567,7 @@ class RequestHandler extends ViewableData // no link defined by default trigger_error( - 'Request handler '.get_class($this). ' does not have a url_segment defined. '. + 'Request handler '.static::class. ' does not have a url_segment defined. '. 'Relying on this link may be an application error', E_USER_WARNING ); diff --git a/src/Core/ClassInfo.php b/src/Core/ClassInfo.php index 0a8a2ab8b..bc4c22a8d 100644 --- a/src/Core/ClassInfo.php +++ b/src/Core/ClassInfo.php @@ -2,6 +2,7 @@ namespace SilverStripe\Core; +use Exception; use SilverStripe\Control\Director; use SilverStripe\Core\Manifest\ClassLoader; use SilverStripe\Dev\Deprecation; @@ -43,14 +44,26 @@ class ClassInfo /** * Cache for {@link hasTable()} + * + * @internal + * @var array */ private static $_cache_all_tables = array(); /** + * @internal * @var array Cache for {@link ancestry()}. */ private static $_cache_ancestry = array(); + /** + * Cache for parse_class_spec + * + * @internal + * @var array + */ + private static $_cache_parse = []; + /** * @todo Move this to SS_Database or DB * @@ -358,4 +371,148 @@ class ClassInfo } return method_exists($object, 'hasMethod') && $object->hasMethod($method); } + + /** + * Parses a class-spec, such as "Versioned('Stage','Live')", as passed to create_from_string(). + * Returns a 2-element array, with classname and arguments + * + * @param string $classSpec + * @return array + * @throws Exception + */ + public static function parse_class_spec($classSpec) + { + if (isset(static::$_cache_parse[$classSpec])) { + return static::$_cache_parse[$classSpec]; + } + + $tokens = token_get_all("defineMethods(); } @@ -151,12 +151,12 @@ trait CustomMethods /** * Get meta-data details on a named method * - * @param array $method + * @param string $method * @return array List of custom method details, if defined for this method */ protected function getExtraMethodConfig($method) { - $class = get_class($this); + $class = static::class; if (isset(self::$extra_methods[$class][strtolower($method)])) { return self::$extra_methods[$class][strtolower($method)]; } @@ -171,7 +171,7 @@ trait CustomMethods */ public function allMethodNames($custom = false) { - $class = get_class($this); + $class = static::class; if (!isset(self::$built_in_methods[$class])) { self::$built_in_methods[$class] = array_map('strtolower', get_class_methods($this)); } @@ -198,10 +198,11 @@ trait CustomMethods $extension->clearOwner(); } } else { - if (!isset(self::$built_in_methods[$extension->class])) { - self::$built_in_methods[$extension->class] = array_map('strtolower', get_class_methods($extension)); + $class = get_class($extension); + if (!isset(self::$built_in_methods[$class])) { + self::$built_in_methods[$class] = array_map('strtolower', get_class_methods($extension)); } - $methods = self::$built_in_methods[$extension->class]; + $methods = self::$built_in_methods[$class]; } return $methods; @@ -216,7 +217,7 @@ trait CustomMethods */ protected function addMethodsFrom($property, $index = null) { - $class = get_class($this); + $class = static::class; $extension = ($index !== null) ? $this->{$property}[$index] : $this->$property; if (!$extension) { @@ -253,7 +254,7 @@ trait CustomMethods protected function removeMethodsFrom($property, $index = null) { $extension = ($index !== null) ? $this->{$property}[$index] : $this->$property; - $class = get_class($this); + $class = static::class; if (!$extension) { throw new InvalidArgumentException( @@ -286,7 +287,7 @@ trait CustomMethods */ protected function addWrapperMethod($method, $wrap) { - $class = get_class($this); + $class = static::class; self::$extra_methods[$class][strtolower($method)] = array ( 'wrap' => $wrap, 'method' => $method diff --git a/src/Core/Extensible.php b/src/Core/Extensible.php index 7b4e4d893..dc8254267 100644 --- a/src/Core/Extensible.php +++ b/src/Core/Extensible.php @@ -3,8 +3,10 @@ namespace SilverStripe\Core; use InvalidArgumentException; +use SilverStripe\Control\RequestHandler; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; +use SilverStripe\View\ViewableData; /** * Allows an object to have extensions applied to it. @@ -46,9 +48,8 @@ trait Extensible * @var array */ private static $unextendable_classes = array( - 'SilverStripe\\Core\\Object', - 'SilverStripe\\View\\ViewableData', - 'SilverStripe\\Control\\RequestHandler' + ViewableData::class, + RequestHandler::class, ); /** @@ -110,7 +111,7 @@ trait Extensible protected function constructExtensions() { - $class = get_class($this); + $class = static::class; // Register this trait as a method source $this->registerExtraMethodCallback('defineExtensionMethods', function () { @@ -126,9 +127,9 @@ trait Extensible if ($extensions) { foreach ($extensions as $extension) { - $instance = Object::create_from_string($extension); + $instance = Injector::inst()->create($extension); $instance->setOwner(null, $class); - $this->extension_instances[$instance->class] = $instance; + $this->extension_instances[get_class($instance)] = $instance; } } } @@ -177,7 +178,7 @@ trait Extensible */ public static function add_extension($classOrExtension, $extension = null) { - if (func_num_args() > 1) { + if ($extension) { $class = $classOrExtension; } else { $class = get_called_class(); @@ -285,14 +286,18 @@ trait Extensible } /** - * @param string $class + * @param string $class If omitted, will get extensions for the current class * @param bool $includeArgumentString Include the argument string in the return array, * FALSE would return array("Versioned"), TRUE returns array("Versioned('Stage','Live')"). * @return array Numeric array of either {@link DataExtension} class names, * or eval'ed class name strings with constructor arguments. */ - public static function get_extensions($class, $includeArgumentString = false) + public static function get_extensions($class = null, $includeArgumentString = false) { + if (!$class) { + $class = get_called_class(); + } + $extensions = Config::forClass($class)->get('extensions', Config::EXCLUDE_EXTRA_SOURCES); if (empty($extensions)) { return array(); @@ -315,9 +320,15 @@ trait Extensible } + /** + * Get extra config sources for this class + * + * @param string $class Name of class. If left null will return for the current class + * @return array|null + */ public static function get_extra_config_sources($class = null) { - if ($class === null) { + if (!$class) { $class = get_called_class(); } @@ -340,7 +351,7 @@ trait Extensible $sources = array(); foreach ($extensions as $extension) { - list($extensionClass, $extensionArgs) = Object::parse_class_spec($extension); + list($extensionClass, $extensionArgs) = ClassInfo::parse_class_spec($extension); $sources[] = $extensionClass; if (!class_exists($extensionClass)) { @@ -367,16 +378,16 @@ trait Extensible * Return TRUE if a class has a specified extension. * This supports backwards-compatible format (static Object::has_extension($requiredExtension)) * and new format ($object->has_extension($class, $requiredExtension)) - * @param string $classOrExtension if 1 argument supplied, the class name of the extension to - * check for; if 2 supplied, the class name to test - * @param string $requiredExtension used only if 2 arguments supplied + * @param string $classOrExtension Class to check extension for, or the extension name to check + * if the second argument is null. + * @param string $requiredExtension If the first argument is the parent class, this is the extension to check. + * If left null, the first parameter will be treated as the extension. * @param boolean $strict if the extension has to match the required extension and not be a subclass * @return bool Flag if the extension exists */ public static function has_extension($classOrExtension, $requiredExtension = null, $strict = false) { - //BC support - if (func_num_args() > 1) { + if ($requiredExtension) { $class = $classOrExtension; } else { $class = get_called_class(); @@ -500,6 +511,7 @@ trait Extensible if ($this->hasExtension($extension)) { return $this->extension_instances[$extension]; } + return null; } /** diff --git a/src/Core/Extension.php b/src/Core/Extension.php index 0914ab635..aca0ea377 100644 --- a/src/Core/Extension.php +++ b/src/Core/Extension.php @@ -16,7 +16,6 @@ use SilverStripe\ORM\DataObject; */ abstract class Extension { - /** * This is used by extensions designed to be applied to controllers. * It works the same way as {@link Controller::$allowed_actions}. @@ -45,13 +44,6 @@ abstract class Extension */ private $ownerStack = []; - public $class; - - public function __construct() - { - $this->class = get_class($this); - } - /** * Called when this extension is added to a particular class * @@ -67,7 +59,7 @@ abstract class Extension /** * Set the owner of this extension. * - * @param Object $owner The owner object, + * @param object $owner The owner object, * @param string $ownerBaseClass The base class that the extension is applied to; this may be * the class of owner, or it may be a parent. For example, if Versioned was applied to SiteTree, * and then a Page object was instantiated, $owner would be a Page object, but $ownerBaseClass diff --git a/src/Core/Injector/Injectable.php b/src/Core/Injector/Injectable.php index 5c6fcf05b..5a5117ca0 100644 --- a/src/Core/Injector/Injectable.php +++ b/src/Core/Injector/Injectable.php @@ -20,22 +20,13 @@ trait Injectable * $list = DataList::create('SiteTree'); * $list = SiteTree::get(); * - * @param string $classOrArgument The first argument, or class name (if called directly - * on Object). - * @param mixed $argument,... arguments to pass to the constructor + * @param array $args * @return static */ - public static function create($classOrArgument = null, $argument = null) + public static function create(...$args) { - $args = func_get_args(); - - // Class to create should be the calling class if not Object, - // otherwise the first parameter + // Class to create should be the calling class $class = get_called_class(); - if ($class == 'SilverStripe\\Core\\Object') { - $class = array_shift($args); - } - return Injector::inst()->createWithArgs($class, $args); } diff --git a/src/Core/Injector/Injector.php b/src/Core/Injector/Injector.php index 235fa258a..650069e7b 100644 --- a/src/Core/Injector/Injector.php +++ b/src/Core/Injector/Injector.php @@ -3,6 +3,7 @@ namespace SilverStripe\Core\Injector; use Psr\Container\NotFoundExceptionInterface; +use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; use ReflectionProperty; use ArrayObject; @@ -828,7 +829,7 @@ class Injector implements ContainerInterface */ public function getServiceName($name) { - // common case, get it over with first + // common case, get it overwith first if (isset($this->specs[$name])) { return $name; } @@ -902,7 +903,7 @@ class Injector implements ContainerInterface * if this object is to be created from scratch (with $asSingleton = false) * @return mixed Instance of the specified object */ - public function get($name, $asSingleton = true, $constructorArgs = null) + public function get($name, $asSingleton = true, $constructorArgs = []) { $object = $this->getNamedService($name, $asSingleton, $constructorArgs); @@ -921,8 +922,11 @@ class Injector implements ContainerInterface * @param array $constructorArgs * @return mixed|null Instance of the specified object (if it exists) */ - protected function getNamedService($name, $asSingleton = true, $constructorArgs = null) + protected function getNamedService($name, $asSingleton = true, $constructorArgs = []) { + // Normalise service / args + list($name, $constructorArgs) = $this->normaliseArguments($name, $constructorArgs); + // reassign the name as it might actually be a compound name if ($serviceName = $this->getServiceName($name)) { // check to see what the type of bean is. If it's a prototype, @@ -974,6 +978,28 @@ class Injector implements ContainerInterface return $this->instantiate($spec); } + /** + * Detect service references with constructor arguments included. + * These will be split out of the service name reference and appended + * to the $args + * + * @param string $name + * @param array $args + * @return array Two items with name and new args + */ + protected function normaliseArguments($name, $args = []) + { + if (strstr($name, '(')) { + list($name, $extraArgs) = ClassInfo::parse_class_spec($name); + if ($args) { + $args = array_merge($args, $extraArgs); + } else { + $args = $extraArgs; + } + } + return [ $name, $args ]; + } + /** * Magic method to return an item directly * @@ -999,7 +1025,7 @@ class Injector implements ContainerInterface { $constructorArgs = func_get_args(); array_shift($constructorArgs); - return $this->get($name, false, count($constructorArgs) ? $constructorArgs : null); + return $this->createWithArgs($name, $constructorArgs); } /** diff --git a/src/Core/Object.php b/src/Core/Object.php deleted file mode 100755 index dc64e57b9..000000000 --- a/src/Core/Object.php +++ /dev/null @@ -1,324 +0,0 @@ -getStaticProperties(); - } - - if (isset($static_properties[$class][$name])) { - $value = $static_properties[$class][$name]; - - $parent = get_parent_class($class); - if (!$parent) { - return $value; - } - - if (!isset($static_properties[$parent])) { - $reflection = new ReflectionClass($parent); - $static_properties[$parent] = $reflection->getStaticProperties(); - } - - if (!isset($static_properties[$parent][$name]) || $static_properties[$parent][$name] !== $value) { - return $value; - } - } - } - - return $default; - } - - public function __construct() - { - $this->class = get_class($this); - $this->constructExtensions(); - } - - // -------------------------------------------------------------------------------------------------------------- - - /** - * Return true if this object "exists" i.e. has a sensible value - * - * This method should be overriden in subclasses to provide more context about the classes state. For example, a - * {@link DataObject} class could return false when it is deleted from the database - * - * @return bool - */ - public function exists() - { - return true; - } - - /** - * @return string this classes parent class - */ - public function parentClass() - { - return get_parent_class($this); - } - - /** - * Check if this class is an instance of a specific class, or has that class as one of its parents - * - * @param string $class - * @return bool - */ - public function is_a($class) - { - return $this instanceof $class; - } - - /** - * @return string the class name - */ - public function __toString() - { - return $this->class; - } -} diff --git a/src/Dev/BuildTask.php b/src/Dev/BuildTask.php index b00e3ff6a..b8c403e8d 100644 --- a/src/Dev/BuildTask.php +++ b/src/Dev/BuildTask.php @@ -3,7 +3,9 @@ namespace SilverStripe\Dev; use SilverStripe\Control\HTTPRequest; -use SilverStripe\Core\Object; +use SilverStripe\Core\Config\Configurable; +use SilverStripe\Core\Extensible; +use SilverStripe\Core\Injector\Injectable; /** * Interface for a generic build task. Does not support dependencies. This will simply @@ -12,8 +14,16 @@ use SilverStripe\Core\Object; * To disable the task (in the case of potentially destructive updates or deletes), declare * the $Disabled property on the subclass. */ -abstract class BuildTask extends Object +abstract class BuildTask { + use Injectable; + use Configurable; + use Extensible; + + public function __construct() + { + $this->constructExtensions(); + } /** * Set a custom url segment (to follow dev/tasks/) @@ -63,7 +73,7 @@ abstract class BuildTask extends Object */ public function getTitle() { - return ($this->title) ? $this->title : $this->class; + return $this->title ?: static::class; } /** diff --git a/src/Dev/BulkLoader.php b/src/Dev/BulkLoader.php index f1a89cbdb..3658b0a96 100644 --- a/src/Dev/BulkLoader.php +++ b/src/Dev/BulkLoader.php @@ -200,7 +200,8 @@ abstract class BulkLoader extends ViewableData */ public function Title() { - return ($title = $this->stat('title')) ? $title : $this->class; + $title = $this->stat('title'); + return $title ?: static::class; } /** diff --git a/src/Dev/BulkLoader_Result.php b/src/Dev/BulkLoader_Result.php index 32868dcd9..957154105 100644 --- a/src/Dev/BulkLoader_Result.php +++ b/src/Dev/BulkLoader_Result.php @@ -2,7 +2,7 @@ namespace SilverStripe\Dev; -use SilverStripe\Core\Object; +use SilverStripe\Core\Injector\Injectable; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataObject; use SilverStripe\View\ArrayData; @@ -15,8 +15,9 @@ use SilverStripe\View\ArrayData; * * @author Ingo Schommer, Silverstripe Ltd. (@silverstripe.com) */ -class BulkLoader_Result extends Object +class BulkLoader_Result { + use Injectable; /** * Stores a map of ID and ClassNames @@ -139,7 +140,7 @@ class BulkLoader_Result extends Object { $this->created[] = $this->lastChange = array( 'ID' => $obj->ID, - 'ClassName' => $obj->class, + 'ClassName' => get_class($obj), 'Message' => $message ); $this->lastChange['ChangeType'] = 'created'; @@ -153,7 +154,7 @@ class BulkLoader_Result extends Object { $this->updated[] = $this->lastChange = array( 'ID' => $obj->ID, - 'ClassName' => $obj->class, + 'ClassName' => get_class($obj), 'Message' => $message ); $this->lastChange['ChangeType'] = 'updated'; diff --git a/src/Dev/CLI.php b/src/Dev/CLI.php index f6988669d..1e9077178 100644 --- a/src/Dev/CLI.php +++ b/src/Dev/CLI.php @@ -2,13 +2,11 @@ namespace SilverStripe\Dev; -use SilverStripe\Core\Object; - /** * Class to facilitate command-line output. * Support less-trivial output stuff such as colours (on xterm-color) */ -class CLI extends Object +class CLI { /** * Returns true if the current STDOUT supports the use of colour control codes. diff --git a/src/Dev/CSSContentParser.php b/src/Dev/CSSContentParser.php index 5d7295569..56f201617 100644 --- a/src/Dev/CSSContentParser.php +++ b/src/Dev/CSSContentParser.php @@ -2,7 +2,7 @@ namespace SilverStripe\Dev; -use SilverStripe\Core\Object; +use SilverStripe\Core\Injector\Injectable; use SimpleXMLElement; use tidy; use Exception; @@ -22,8 +22,10 @@ use Exception; * Caution: Doesn't fully support HTML elements like
* due to them being declared illegal by the "tidy" preprocessing step. */ -class CSSContentParser extends Object +class CSSContentParser { + use Injectable; + protected $simpleXML = null; public function __construct($content) @@ -59,8 +61,6 @@ class CSSContentParser extends Object throw new Exception('CSSContentParser::__construct(): Could not parse content.' . ' Please check the PHP extension tidy is installed.'); } - - parent::__construct(); } /** diff --git a/src/Dev/CSVParser.php b/src/Dev/CSVParser.php index 4ffa02574..f03ccffde 100644 --- a/src/Dev/CSVParser.php +++ b/src/Dev/CSVParser.php @@ -2,7 +2,7 @@ namespace SilverStripe\Dev; -use SilverStripe\Core\Object; +use SilverStripe\Core\Injector\Injectable; use Iterator; use SilverStripe\Control\Director; @@ -31,8 +31,9 @@ use SilverStripe\Control\Director; * } * */ -class CSVParser extends Object implements Iterator +class CSVParser implements Iterator { + use Injectable; /** * @var string $filename @@ -117,8 +118,6 @@ class CSVParser extends Object implements Iterator $this->filename = $filename; $this->delimiter = $delimiter; $this->enclosure = $enclosure; - - parent::__construct(); } /** diff --git a/src/Dev/CsvBulkLoader.php b/src/Dev/CsvBulkLoader.php index 878fb2fac..fdfa5ef87 100644 --- a/src/Dev/CsvBulkLoader.php +++ b/src/Dev/CsvBulkLoader.php @@ -180,7 +180,7 @@ class CsvBulkLoader extends BulkLoader */ protected function processChunk($filepath, $preview = false) { - $results = new BulkLoader_Result(); + $results = BulkLoader_Result::create(); $csv = new CSVParser( $filepath, diff --git a/src/Dev/DebugView.php b/src/Dev/DebugView.php index 33f341b81..64bf4243a 100644 --- a/src/Dev/DebugView.php +++ b/src/Dev/DebugView.php @@ -5,15 +5,18 @@ namespace SilverStripe\Dev; use SilverStripe\Control\Controller; use SilverStripe\Control\Director; use SilverStripe\Control\HTTPRequest; +use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Convert; -use SilverStripe\Core\Object; +use SilverStripe\Core\Injector\Injectable; /** * A basic HTML wrapper for stylish rendering of a developement info view. * Used to output error messages, and test results. */ -class DebugView extends Object +class DebugView { + use Configurable; + use Injectable; /** * Column size to wrap long strings to diff --git a/src/Dev/FixtureBlueprint.php b/src/Dev/FixtureBlueprint.php index 0977b6866..a511f86fd 100644 --- a/src/Dev/FixtureBlueprint.php +++ b/src/Dev/FixtureBlueprint.php @@ -2,6 +2,7 @@ namespace SilverStripe\Dev; +use SilverStripe\Assets\File; use SilverStripe\ORM\DataModel; use SilverStripe\ORM\DB; use SilverStripe\ORM\DataObject; @@ -85,8 +86,8 @@ class FixtureBlueprint // which they are imported doesnt guarantee valid relations until after the import is complete. // Also disable filesystem manipulations Config::nest(); - Config::inst()->update('SilverStripe\\ORM\\DataObject', 'validation_enabled', false); - Config::inst()->update('SilverStripe\\Assets\\File', 'update_filesystem', false); + Config::modify()->set(DataObject::class, 'validation_enabled', false); + Config::modify()->set(File::class, 'update_filesystem', false); $this->invokeCallbacks('beforeCreate', array($identifier, &$data, &$fixtures)); @@ -132,10 +133,10 @@ class FixtureBlueprint if ($data) { foreach ($data as $fieldName => $fieldVal) { if ($schema->manyManyComponent($class, $fieldName) - || $schema->hasManyComponent($class, $fieldName) - || $schema->hasOneComponent($class, $fieldName) - ) { - continue; + || $schema->hasManyComponent($class, $fieldName) + || $schema->hasOneComponent($class, $fieldName) + ) { + continue; } $this->setValue($obj, $fieldName, $fieldVal, $fixtures); diff --git a/src/Dev/FunctionalTest.php b/src/Dev/FunctionalTest.php index 99ecfba9f..ca50d9d8f 100644 --- a/src/Dev/FunctionalTest.php +++ b/src/Dev/FunctionalTest.php @@ -83,8 +83,8 @@ class FunctionalTest extends SapphireTest protected function setUp() { // Skip calling FunctionalTest directly. - if (get_class($this) == __CLASS__) { - $this->markTestSkipped(sprintf('Skipping %s ', get_class($this))); + if (static::class == __CLASS__) { + $this->markTestSkipped(sprintf('Skipping %s ', static::class)); } parent::setUp(); diff --git a/src/Dev/SapphireTest.php b/src/Dev/SapphireTest.php index 3a3ce1f8f..6485836cd 100644 --- a/src/Dev/SapphireTest.php +++ b/src/Dev/SapphireTest.php @@ -111,13 +111,14 @@ class SapphireTest extends PHPUnit_Framework_TestCase */ protected $requireDefaultRecordsFrom = array(); - /** * A list of extensions that can't be applied during the execution of this run. If they are * applied, they will be temporarily removed and a database migration called. * * The keys of the are the classes that the extensions can't be applied the extensions to, and * the values are an array of illegal extensions on that class. + * + * Set a class to `*` to remove all extensions (unadvised) */ protected static $illegal_extensions = []; @@ -242,8 +243,8 @@ class SapphireTest extends PHPUnit_Framework_TestCase } // We cannot run the tests on this abstract class. - if (get_class($this) == __CLASS__) { - $this->markTestSkipped(sprintf('Skipping %s ', get_class($this))); + if (static::class == __CLASS__) { + $this->markTestSkipped(sprintf('Skipping %s ', static::class)); return; } @@ -357,6 +358,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase if (!class_exists($class)) { continue; } + if ($extensions === '*') { + $extensions = $class::get_extensions(); + } foreach ($extensions as $extension) { if (!class_exists($extension) || !$class::has_extension($extension)) { continue; @@ -559,9 +563,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase */ protected function getCurrentAbsolutePath() { - $filename = self::$test_class_manifest->getItemPath(get_class($this)); + $filename = self::$test_class_manifest->getItemPath(static::class); if (!$filename) { - throw new LogicException("getItemPath returned null for " . get_class($this)); + throw new LogicException("getItemPath returned null for " . static::class); } return dirname($filename); } diff --git a/src/Dev/YamlFixture.php b/src/Dev/YamlFixture.php index 77c54a5b0..1ae95dafc 100644 --- a/src/Dev/YamlFixture.php +++ b/src/Dev/YamlFixture.php @@ -4,7 +4,7 @@ namespace SilverStripe\Dev; use SilverStripe\Control\Director; use SilverStripe\Core\ClassInfo; -use SilverStripe\Core\Object; +use SilverStripe\Core\Injector\Injectable; use Symfony\Component\Yaml\Parser; use InvalidArgumentException; @@ -68,8 +68,9 @@ use InvalidArgumentException; * ErrorCode: 404 * */ -class YamlFixture extends Object +class YamlFixture { + use Injectable; /** * Absolute path to the .yml fixture file @@ -104,8 +105,6 @@ class YamlFixture extends Object $this->fixtureFile = $fixture; } - - parent::__construct(); } /** diff --git a/src/Forms/CompositeField.php b/src/Forms/CompositeField.php index 706ea0e2c..87553c87e 100644 --- a/src/Forms/CompositeField.php +++ b/src/Forms/CompositeField.php @@ -246,9 +246,14 @@ class CompositeField extends FormField if ($name) { $formName = (isset($this->form)) ? $this->form->FormName() : '(unknown form)'; if (isset($list[$name])) { - user_error("collateDataFields() I noticed that a field called '$name' appears twice in" - . " your form: '{$formName}'. One is a '{$field->class}' and the other is a" - . " '{$list[$name]->class}'", E_USER_ERROR); + $fieldClass = get_class($field); + $otherFieldClass = get_class($list[$name]); + user_error( + "collateDataFields() I noticed that a field called '$name' appears twice in" + . " your form: '{$formName}'. One is a '{$fieldClass}' and the other is a" + . " '{$otherFieldClass}'", + E_USER_ERROR + ); } $list[$name] = $field; } @@ -509,7 +514,8 @@ class CompositeField extends FormField public function debug() { - $result = "$this->class ($this->name)