Skip to content
Permalink
Browse files

Start integrating the context classes into FormHelper.

Add several new methods that allow the various context classes to be
used by FormHelper. This also removes some 2.x legacy code that will
inevitably break existing tests & code.

A few TODO's are in the commit as I didn't want to make it even more
enourmous.
  • Loading branch information...
markstory committed Feb 9, 2014
1 parent d8c075f commit dda3040c809472df869ab8e047c766341666534d
Showing with 163 additions and 141 deletions.
  1. +113 −132 src/View/Helper/FormHelper.php
  2. +50 −9 tests/TestCase/View/Helper/FormHelperTest.php
@@ -16,13 +16,20 @@
use Cake\Core\Configure;
use Cake\Error;
use Cake\ORM\Entity;
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
use Cake\Utility\Inflector;
use Cake\Utility\Security;
use Cake\View\Form\ArrayContext;
use Cake\View\Form\EntityContext;
use Cake\View\Form\NullContext;
use Cake\View\Helper;
use Cake\View\StringTemplate;
use Cake\View\View;
use \DateTime;
use Cake\View\Widget\InputRegistry;
use DateTime;
use Traversable;
/**
* Form helper library.
@@ -122,113 +129,71 @@ class FormHelper extends Helper {
protected $_domIdSuffixes = array();
/**
* Copies the validationErrors variable from the View object into this instance
* Registry for input widgets.
*
* @param View $View The View this helper is being attached to.
* @param array $settings Configuration settings for the helper.
* @var Cake\View\Widget\InputRegistry
*/
public function __construct(View $View, $settings = array()) {
parent::__construct($View, $settings);
$this->validationErrors =& $View->validationErrors;
}
protected $_registry;
/**
* Guess the location for a model based on its name and tries to create a new instance
* or get an already created instance of the model
* Template formatter for the helper.
*
* @param string $model
* @return Model model instance
* @var Cake\View\StringTemplate
*/
protected function _getModel($model) {
$object = null;
if (!$model || $model === 'Model') {
return $object;
}
if (array_key_exists($model, $this->_models)) {
return $this->_models[$model];
}
$object = TableRegistry::get($model);
$this->_models[$model] = $object;
if (!$object) {
return null;
}
$this->fieldset[$model] = array('fields' => null, 'key' => $object->primaryKey(), 'validates' => null);
return $object;
}
protected $_templates;
/**
* Inspects the model properties to extract information from them.
* Currently it can extract information from the the fields, the primary key and required fields
* Context for the current form.
*
* The $key parameter accepts the following list of values:
*
* - key: Returns the name of the primary key for the model
* - fields: Returns the model schema
* - validates: returns the list of fields that are required
* - errors: returns the list of validation errors
*
* If the $field parameter is passed if will return the information for that sole field.
* @var Cake\View\Form\Context
*/
protected $_context;
/**
* Context provider methods.
*
* `$this->_introspectModel('Post', 'fields', 'title');` will return the schema information for title column
* @var array
* @see addContextProvider
*/
protected $_contextProviders;
/**
* Copies the validationErrors variable from the View object into this instance
*
* @param string $model name of the model to extract information from
* @param string $key name of the special information key to obtain (key, fields, validates, errors)
* @param string $field name of the model field to get information from
* @return mixed information extracted for the special key and field in a model
* @param View $View The View this helper is being attached to.
* @param array $settings Configuration settings for the helper.
*/
protected function _introspectModel($model, $key, $field = null) {
$object = $this->_getModel($model);
if (!$object) {
return;
public function __construct(View $View, $settings = array()) {
$settings += ['widgets' => [], 'templates' => null, 'registry' => null];
if (is_array($settings['templates'])) {
$settings['templates'] = new StringTemplate($settings['templates']);
}
$this->_templates = $settings['templates'] ?: new StringTemplate();
if ($key === 'key') {
return $this->fieldset[$model]['key'] = $object->primaryKey();
if (empty($settings['registry'])) {
$settings['registry'] = new InputRegistry($this->_templates, $settings['widgets']);
}
$this->_registry = $settings['registry'];
if ($key === 'fields') {
if (!isset($this->fieldset[$model]['fields'])) {
$this->fieldset[$model]['fields'] = $object->schema();
foreach ($object->hasAndBelongsToMany as $alias => $assocData) {
$this->fieldset[$object->alias]['fields'][$alias] = array('type' => 'multiple');
}
}
if ($field === null || $field === false) {
return $this->fieldset[$model]['fields'];
} elseif (isset($this->fieldset[$model]['fields'][$field])) {
return $this->fieldset[$model]['fields'][$field];
$this->addContextProvider('array', function ($request, $data) {
if (is_array($data) && isset($data['schema'])) {
return new ArrayContext($request, $data);
}
return isset($object->hasAndBelongsToMany[$field]) ? array('type' => 'multiple') : null;
}
if ($key === 'errors' && !isset($this->validationErrors[$model])) {
$this->validationErrors[$model] =& $object->validationErrors;
return $this->validationErrors[$model];
} elseif ($key === 'errors' && isset($this->validationErrors[$model])) {
return $this->validationErrors[$model];
}
if ($key === 'validates' && !isset($this->fieldset[$model]['validates'])) {
$validates = array();
foreach ($object->validator() as $validateField => $validateProperties) {
if ($this->_isRequiredField($validateProperties)) {
$validates[$validateField] = true;
}
});
$this->addContextProvider('orm', function ($request, $data) {
if (
$data instanceof Entity ||
$data instanceof Traversable ||
(is_array($data) && !isset($data['schema']))
) {
return new EntityContext($request, $data);
}
$this->fieldset[$model]['validates'] = $validates;
}
});
$this->addContextProvider('_default', function ($request, $data) {
return new NullContext($request, (array)$data);
});
if ($key === 'validates') {
if (empty($field)) {
return $this->fieldset[$model]['validates'];
}
return isset($this->fieldset[$model]['validates'][$field]) ?
$this->fieldset[$model]['validates'] : null;
}
parent::__construct($View, $settings);
}
/**
@@ -268,19 +233,7 @@ public function tagIsInvalid() {
array_splice($entity, 1, 0, $model);
$model = array_shift($entity);
}
$errors = array();
if (!empty($entity) && isset($this->validationErrors[$model])) {
$errors = $this->validationErrors[$model];
}
if (!empty($entity) && empty($errors)) {
$errors = $this->_introspectModel($model, 'errors');
}
if (empty($errors)) {
return false;
}
$errors = Hash::get($errors, implode('.', $entity));
return $errors === null ? false : $errors;
return false;
}
/**
@@ -310,28 +263,12 @@ public function tagIsInvalid() {
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-create
*/
public function create($model = null, $options = array()) {
$created = $id = false;
$created = $id = $key = false;
$append = '';
if (is_array($model) && empty($options)) {
$options = $model;
$model = null;
}
if (empty($model) && $model !== false && !empty($this->request->params['models'])) {
$model = key($this->request->params['models']);
} elseif (empty($model) && empty($this->request->params['models'])) {
$model = false;
}
$this->defaultModel = $model;
$key = null;
if ($model !== false) {
list($plugin, $model) = pluginSplit($model, true);
$key = $this->_introspectModel($plugin . $model, 'key');
$this->setEntity($model, true);
}
$this->_context = $this->_buildContext($model);
// TODO implement 'isNew' on context classes.
if ($model !== false && $key) {
$recordExists = (
isset($this->request->data[$model]) &&
@@ -351,10 +288,8 @@ public function create($model = null, $options = array()) {
'url' => null,
'default' => true,
'encoding' => strtolower(Configure::read('App.encoding')),
'inputDefaults' => array()),
),
$options);
$this->inputDefaults($options['inputDefaults']);
unset($options['inputDefaults']);
if (!isset($options['id'])) {
$domId = isset($options['action']) ? $options['action'] : $this->request['action'];
@@ -439,10 +374,6 @@ public function create($model = null, $options = array()) {
$append = $this->Html->useTag('hiddenblock', $append);
}
if ($model !== false) {
$this->setEntity($model, true);
$this->_introspectModel($model, 'fields');
}
return $this->Html->useTag('form', $action, $htmlAttributes) . $append;
}
@@ -513,11 +444,10 @@ public function end($options = null) {
$out .= $this->secure($this->fields);
$this->fields = array();
}
$this->setEntity(null);
$out .= $this->Html->useTag('formend');
$this->_View->modelScope = false;
$this->requestType = null;
$this->_context = null;
return $out;
}
@@ -2942,6 +2872,55 @@ public function inputDefaults($defaults = null, $merge = false) {
return $this->_inputDefaults;
}
/**
* 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 a object
* when the form context is the correct type.
* @return void
*/
public function addContextProvider($name, callable $check) {
$this->_contextProviders[$name] = $check;
}
/**
* Get the context instance for the current form set.
*
* If there is no active form null will be returned.
*
* @return null|object The context for the form.
*/
public function context() {
return $this->_context;
}
/**
* Find the matching context provider for the data.
*
* If no type can be matched the default provider will be returned.
*
* @param mixed $data The data to get a context provider for.
* @return mixed Context provider.
*/
protected function _buildContext($data) {
foreach ($this->_contextProviders as $key => $check) {
$context = $check($this->request, $data);
if ($context) {
return $context;
}
}
$check = $this->_contextProviders['_default'];
return $check($this->request, $data);
}
/**
* Add a new widget to FormHelper.
*
@@ -2953,6 +2932,7 @@ public function inputDefaults($defaults = null, $merge = false) {
* @return void
*/
public function addWidget($name, $spec) {
$this->_registry->add([$name => $spec]);
}
/**
@@ -2967,8 +2947,9 @@ public function addWidget($name, $spec) {
* @param array $attrs The attributes for rendering the input.
* @return void
*/
public function widget($name, $data) {
public function widget($name, array $data = []) {
$widget = $this->_registry->get($name);
return $widget->render($data);
}
}
Oops, something went wrong.

0 comments on commit dda3040

Please sign in to comment.
You can’t perform that action at this time.