Browse Source

Add ContextFactory class.

This offloads the code for generating context instance based
on data passed to ForHelper::create() into separate class.
ADmad 8 years ago
parent
commit
5625bc4454
2 changed files with 161 additions and 60 deletions
  1. 143 0
      src/View/Form/ContextFactory.php
  2. 18 60
      src/View/Helper/FormHelper.php

+ 143 - 0
src/View/Form/ContextFactory.php

@@ -0,0 +1,143 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link          https://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\View\Form;
+
+use Cake\Collection\Collection;
+use Cake\Datasource\EntityInterface;
+use Cake\Form\Form;
+use Cake\Http\ServerRequest;
+use RuntimeException;
+use Traversable;
+
+/**
+ * Factory for getting form context instance based on provided data.
+ */
+class ContextFactory
+{
+    /**
+     * Context provider methods.
+     *
+     * @var array
+     */
+    protected $contextProviders = [];
+
+    /**
+     * Constructor.
+     *
+     * @param array $providers Array of provider callables. Each element should
+     *   be of form `['type' => 'a-string', 'callable' => ..]`
+     * @param bool $addDefaults Whether default providers should be added.
+     */
+    public function __construct(array $providers = [], $addDefaults = true)
+    {
+        if ($addDefaults) {
+            $this->addDefaultProviders();
+        }
+
+        foreach ($providers as $provider) {
+            $this->addProvider($provider['type'], $provider);
+        }
+    }
+
+    /**
+     * Add a new context type.
+     *
+     * Form context types allow FormHelper to interact with
+     * data providers that come from outside CakePHP. For example
+     * if you wanted to use an alternative ORM like Doctrine you could
+     * create and connect a new context class to allow FormHelper to
+     * read metadata from doctrine.
+     *
+     * @param string $type The type of context. This key
+     *   can be used to overwrite existing providers.
+     * @param callable $check A callable that returns an object
+     *   when the form context is the correct type.
+     * @return void
+     */
+    public function addProvider($type, callable $check)
+    {
+        $this->contextProviders = [$type => ['type' => $type, 'callable' => $check]]
+            + $this->contextProviders;
+    }
+
+    /**
+     * Find the matching context for the data.
+     *
+     * If no type can be matched a NullContext will be returned.
+     *
+     * @param \Cake\Http\ServerRequest $request Request instance.
+     * @param array $data The data to get a context provider for.
+     * @return \Cake\View\Form\ContextInterface Context provider.
+     * @throws \RuntimeException when the context class does not implement the
+     *   ContextInterface.
+     */
+    public function get(ServerRequest $request, array $data = [])
+    {
+        $data += ['entity' => null];
+
+        foreach ($this->contextProviders as $provider) {
+            $check = $provider['callable'];
+            $context = $check($request, $data);
+            if ($context) {
+                break;
+            }
+        }
+        if (!isset($context)) {
+            $context = new NullContext($request, $data);
+        }
+        if (!($context instanceof ContextInterface)) {
+            throw new RuntimeException(
+                'Context objects must implement Cake\View\Form\ContextInterface'
+            );
+        }
+
+        return $context;
+    }
+
+    /**
+     * Add the default suite of context providers.
+     *
+     * @return void
+     */
+    protected function addDefaultProviders()
+    {
+        $this->addProvider('orm', function ($request, $data) {
+            if (is_array($data['entity']) || $data['entity'] instanceof Traversable) {
+                $pass = (new Collection($data['entity']))->first() !== null;
+                if ($pass) {
+                    return new EntityContext($request, $data);
+                }
+            }
+            if ($data['entity'] instanceof EntityInterface) {
+                return new EntityContext($request, $data);
+            }
+            if (is_array($data['entity']) && empty($data['entity']['schema'])) {
+                return new EntityContext($request, $data);
+            }
+        });
+
+        $this->addProvider('form', function ($request, $data) {
+            if ($data['entity'] instanceof Form) {
+                return new FormContext($request, $data);
+            }
+        });
+
+        $this->addProvider('array', function ($request, $data) {
+            if (is_array($data['entity']) && isset($data['entity']['schema'])) {
+                return new ArrayContext($request, $data['entity']);
+            }
+        });
+    }
+}

+ 18 - 60
src/View/Helper/FormHelper.php

@@ -14,19 +14,14 @@
  */
 namespace Cake\View\Helper;
 
-use Cake\Collection\Collection;
 use Cake\Core\Configure;
 use Cake\Core\Exception\Exception;
-use Cake\Datasource\EntityInterface;
 use Cake\Form\Form;
 use Cake\Routing\Router;
 use Cake\Utility\Hash;
 use Cake\Utility\Inflector;
-use Cake\View\Form\ArrayContext;
+use Cake\View\Form\ContextFactory;
 use Cake\View\Form\ContextInterface;
-use Cake\View\Form\EntityContext;
-use Cake\View\Form\FormContext;
-use Cake\View\Form\NullContext;
 use Cake\View\Helper;
 use Cake\View\StringTemplateTrait;
 use Cake\View\View;
@@ -206,12 +201,11 @@ class FormHelper extends Helper
     protected $_context;
 
     /**
-     * Context provider methods.
+     * Context factory.
      *
-     * @var array
-     * @see \Cake\View\Helper\FormHelper::addContextProvider()
+     * @var \Cake\View\FormContextFactory
      */
-    protected $_contextProviders = [];
+    protected $_contextFactory;
 
     /**
      * The action attribute value of the last created form.
@@ -253,7 +247,6 @@ class FormHelper extends Helper
         parent::__construct($View, $config);
 
         $this->widgetRegistry($registry, $widgets);
-        $this->_addDefaultContextProviders();
         $this->_idPrefix = $this->getConfig('idPrefix');
     }
 
@@ -279,38 +272,24 @@ class FormHelper extends Helper
     }
 
     /**
-     * Add the default suite of context providers provided by CakePHP.
+     * Set the context factory the helper will use.
      *
-     * @return void
+     * @param \Cake\View\Form\ContextFactory|null $instance The context factory instance to set.
+     * @param array $contexts An array of context providers.
+     * @return \Cake\View\Form\ContextFactory
      */
-    protected function _addDefaultContextProviders()
+    public function contextFactory(ContextFactory $instance = null, array $contexts = [])
     {
-        $this->addContextProvider('orm', function ($request, $data) {
-            if (is_array($data['entity']) || $data['entity'] instanceof Traversable) {
-                $pass = (new Collection($data['entity']))->first() !== null;
-                if ($pass) {
-                    return new EntityContext($request, $data);
-                }
-            }
-            if ($data['entity'] instanceof EntityInterface) {
-                return new EntityContext($request, $data);
-            }
-            if (is_array($data['entity']) && empty($data['entity']['schema'])) {
-                return new EntityContext($request, $data);
+        if ($instance === null) {
+            if ($this->_contextFactory === null) {
+                $this->_contextFactory = new ContextFactory($contexts);
             }
-        });
 
-        $this->addContextProvider('form', function ($request, $data) {
-            if ($data['entity'] instanceof Form) {
-                return new FormContext($request, $data);
-            }
-        });
+            return $this->_contextFactory;
+        }
+        $this->_contextFactory = $instance;
 
-        $this->addContextProvider('array', function ($request, $data) {
-            if (is_array($data['entity']) && isset($data['entity']['schema'])) {
-                return new ArrayContext($request, $data['entity']);
-            }
-        });
+        return $this->_contextFactory;
     }
 
     /**
@@ -2683,12 +2662,7 @@ class FormHelper extends Helper
      */
     public function addContextProvider($type, callable $check)
     {
-        foreach ($this->_contextProviders as $i => $provider) {
-            if ($provider['type'] === $type) {
-                unset($this->_contextProviders[$i]);
-            }
-        }
-        array_unshift($this->_contextProviders, ['type' => $type, 'callable' => $check]);
+        $this->contextFactory()->addProvider($type, $check);
     }
 
     /**
@@ -2725,23 +2699,7 @@ class FormHelper extends Helper
         }
         $data += ['entity' => null];
 
-        foreach ($this->_contextProviders as $provider) {
-            $check = $provider['callable'];
-            $context = $check($this->request, $data);
-            if ($context) {
-                break;
-            }
-        }
-        if (!isset($context)) {
-            $context = new NullContext($this->request, $data);
-        }
-        if (!($context instanceof ContextInterface)) {
-            throw new RuntimeException(
-                'Context objects must implement Cake\View\Form\ContextInterface'
-            );
-        }
-
-        return $this->_context = $context;
+        return $this->_context = $this->contextFactory()->get($this->request, $data);
     }
 
     /**