Skip to content

Commit

Permalink
Add ContextFactory class.
Browse files Browse the repository at this point in the history
This offloads the code for generating context instance based
on data passed to ForHelper::create() into separate class.
  • Loading branch information
ADmad committed Aug 6, 2017
1 parent 4ab9aa9 commit 5625bc4
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 60 deletions.
143 changes: 143 additions & 0 deletions 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']);
}
});
}
}
78 changes: 18 additions & 60 deletions src/View/Helper/FormHelper.php
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -253,7 +247,6 @@ public function __construct(View $View, array $config = [])
parent::__construct($View, $config);

$this->widgetRegistry($registry, $widgets);
$this->_addDefaultContextProviders();
$this->_idPrefix = $this->getConfig('idPrefix');
}

Expand All @@ -279,38 +272,24 @@ public function widgetRegistry(WidgetRegistry $instance = null, $widgets = [])
}

/**
* 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;
}

/**
Expand Down Expand Up @@ -2683,12 +2662,7 @@ protected function _secureFieldName($name)
*/
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);
}

/**
Expand Down Expand Up @@ -2725,23 +2699,7 @@ protected function _getContext($data = [])
}
$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);
}

/**
Expand Down

0 comments on commit 5625bc4

Please sign in to comment.