Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
I think having separate form context for multiple and single top level models will make both code paths simpler as they have different requirements. At least in my head they are different. Implement EntityContext val() and some tests.
- Loading branch information
Showing
3 changed files
with
281 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
<?php | ||
/** | ||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org) | ||
* Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org) | ||
* @link http://cakephp.org CakePHP(tm) Project | ||
* @since CakePHP(tm) v 3.0 | ||
* @license http://www.opensource.org/licenses/mit-license.php MIT License | ||
*/ | ||
namespace Cake\View\Form; | ||
|
||
use Cake\Network\Request; | ||
use Cake\ORM\Entity; | ||
use Cake\Validation\Validator; | ||
use Traversable; | ||
|
||
/** | ||
* Provides a form context around a single entity and its relations. | ||
* | ||
* This class lets FormHelper interface with entities or collections | ||
* of entities. | ||
* | ||
* Important Keys: | ||
* | ||
* - `entity` The entity this context is operating on. | ||
* - `table` Either the ORM\Table instance to fetch schema/validators | ||
* from, an array of table instances in the case of an form spanning | ||
* multiple entities, or the name(s) of the table. | ||
* If this is null the table name(s) will be determined using conventions. | ||
* This table object will be used to fetch the schema and | ||
* validation information. | ||
* - `validator` Either the Validation\Validator to use, or the name of the | ||
* validation method to call on the table object. For example 'default'. | ||
* Defaults to 'default'. | ||
*/ | ||
class EntityContext { | ||
|
||
/** | ||
* The request object. | ||
* | ||
* @var Cake\Network\Request | ||
*/ | ||
protected $_request; | ||
|
||
/** | ||
* Context data for this object. | ||
* | ||
* @var array | ||
*/ | ||
protected $_context; | ||
|
||
protected $_pluralName; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param Cake\Network\Request | ||
* @param array | ||
*/ | ||
public function __construct(Request $request, array $context) { | ||
$this->_request = $request; | ||
$context += [ | ||
'entity' => null, | ||
'schema' => null, | ||
'table' => null, | ||
'validator' => null | ||
]; | ||
$this->_context = $context; | ||
$this->_prepare(); | ||
} | ||
|
||
/** | ||
* Prepare some additional data from the context. | ||
* | ||
* @return void | ||
*/ | ||
protected function _prepare() { | ||
// TODO handle the other cases (string, array, instance) | ||
if (is_string($this->_context['table'])) { | ||
$plural = $this->_context['table']; | ||
} | ||
$this->_pluralName = $plural; | ||
} | ||
|
||
/** | ||
* Get the value for a given path. | ||
* | ||
* Traverses the entity data and finds the value for $path. | ||
* | ||
* @param string $field The dot separated path to the value. | ||
* @return mixed The value of the field or null on a miss. | ||
*/ | ||
public function val($field) { | ||
if (empty($this->_context['entity'])) { | ||
return null; | ||
} | ||
$parts = explode('.', $field); | ||
|
||
// Remove the model name if present. | ||
if (count($parts) > 1 && $parts[0] === $this->_pluralName) { | ||
array_shift($parts); | ||
} | ||
|
||
$val = $this->_context['entity']; | ||
foreach ($parts as $prop) { | ||
$val = $this->_getProp($val, $prop); | ||
if ( | ||
!is_array($val) && | ||
!($val instanceof Traversable) && | ||
!($val instanceof Entity) | ||
) { | ||
return $val; | ||
} | ||
} | ||
return $val; | ||
} | ||
|
||
/** | ||
* Read property values or traverse arrays/iterators. | ||
* | ||
* @param mixed $target The entity/array/collection to fetch $field from. | ||
* @param string $field The next field to fetch. | ||
* @return mixed. | ||
*/ | ||
protected function _getProp($target, $field) { | ||
if (is_array($target) || $target instanceof Traversable) { | ||
foreach ($target as $i => $val) { | ||
if ($i == $field) { | ||
return $val; | ||
} | ||
} | ||
} | ||
return $target->get($field); | ||
} | ||
|
||
public function isRequired($field) { | ||
} | ||
|
||
public function type($field) { | ||
} | ||
|
||
public function attributes($field) { | ||
} | ||
|
||
public function hasError($field) { | ||
} | ||
|
||
public function error($field) { | ||
} | ||
|
||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
<?php | ||
/** | ||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org) | ||
* Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org) | ||
* @link http://cakephp.org CakePHP(tm) Project | ||
* @since CakePHP(tm) v 3.0 | ||
* @license http://www.opensource.org/licenses/mit-license.php MIT License | ||
*/ | ||
namespace Cake\Test\TestCase\View\Form; | ||
|
||
use Cake\ORM\Entity; | ||
use Cake\ORM\Table; | ||
use Cake\Network\Request; | ||
use Cake\TestSuite\TestCase; | ||
use Cake\View\Form\EntityContext; | ||
|
||
/** | ||
* Entity context test case. | ||
*/ | ||
class EntityContextTest extends TestCase { | ||
|
||
/** | ||
* Fixtures to use. | ||
* | ||
* @var array | ||
*/ | ||
public $fixtures = ['core.article', 'core.comment']; | ||
|
||
/** | ||
* setup method. | ||
* | ||
* @return void | ||
*/ | ||
public function setUp() { | ||
parent::setUp(); | ||
$this->request = new Request(); | ||
} | ||
|
||
/** | ||
* Test reading data. | ||
* | ||
* @return void | ||
*/ | ||
public function testValBasic() { | ||
$row = new Entity([ | ||
'title' => 'Test entity', | ||
'body' => 'Something new' | ||
]); | ||
$context = new EntityContext($this->request, [ | ||
'entity' => $row, | ||
'table' => 'Articles', | ||
]); | ||
$result = $context->val('Articles.title'); | ||
$this->assertEquals($row->title, $result); | ||
|
||
$result = $context->val('title'); | ||
$this->assertEquals($row->title, $result); | ||
|
||
$result = $context->val('Articles.body'); | ||
$this->assertEquals($row->body, $result); | ||
|
||
$result = $context->val('body'); | ||
$this->assertEquals($row->body, $result); | ||
|
||
$result = $context->val('Articles.nope'); | ||
$this->assertNull($result); | ||
|
||
$result = $context->val('nope'); | ||
$this->assertNull($result); | ||
} | ||
|
||
/** | ||
* Test reading values from associated entities. | ||
* | ||
* @return void | ||
*/ | ||
public function testValAssociated() { | ||
$row = new Entity([ | ||
'title' => 'Test entity', | ||
'user' => new Entity([ | ||
'username' => 'mark', | ||
'fname' => 'Mark' | ||
]), | ||
'comments' => [ | ||
new Entity(['comment' => 'Test comment']), | ||
new Entity(['comment' => 'Second comment']), | ||
] | ||
]); | ||
$context = new EntityContext($this->request, [ | ||
'entity' => $row, | ||
'table' => 'Articles', | ||
]); | ||
|
||
$result = $context->val('Articles.user.fname'); | ||
$this->assertEquals($row->user->fname, $result); | ||
|
||
$result = $context->val('user.fname'); | ||
$this->assertEquals($row->user->fname, $result); | ||
|
||
$result = $context->val('Articles.comments.0.comment'); | ||
$this->assertEquals($row->comments[0]->comment, $result); | ||
|
||
$result = $context->val('comments.0.comment'); | ||
$this->assertEquals($row->comments[0]->comment, $result); | ||
|
||
$result = $context->val('Articles.comments.1.comment'); | ||
$this->assertEquals($row->comments[1]->comment, $result); | ||
|
||
$result = $context->val('comments.1.comment'); | ||
$this->assertEquals($row->comments[1]->comment, $result); | ||
|
||
$result = $context->val('Articles.comments.0.nope'); | ||
$this->assertNull($result); | ||
|
||
$result = $context->val('Articles.comments.0.nope.no_way'); | ||
$this->assertNull($result); | ||
} | ||
|
||
} |