1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 3.5.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\View\Form;
16:
17: use Cake\Collection\Collection;
18: use Cake\Datasource\EntityInterface;
19: use Cake\Form\Form;
20: use Cake\Http\ServerRequest;
21: use RuntimeException;
22: use Traversable;
23:
24: /**
25: * Factory for getting form context instance based on provided data.
26: */
27: class ContextFactory
28: {
29: /**
30: * Context providers.
31: *
32: * @var array
33: */
34: protected $providers = [];
35:
36: /**
37: * Constructor.
38: *
39: * @param array $providers Array of provider callables. Each element should
40: * be of form `['type' => 'a-string', 'callable' => ..]`
41: */
42: public function __construct(array $providers = [])
43: {
44: foreach ($providers as $provider) {
45: $this->addProvider($provider['type'], $provider['callable']);
46: }
47: }
48:
49: /**
50: * Create factory instance with providers "array", "form" and "orm".
51: *
52: * @param array $providers Array of provider callables. Each element should
53: * be of form `['type' => 'a-string', 'callable' => ..]`
54: * @return \Cake\View\Form\ContextFactory
55: */
56: public static function createWithDefaults(array $providers = [])
57: {
58: $providers = [
59: [
60: 'type' => 'orm',
61: 'callable' => function ($request, $data) {
62: if (is_array($data['entity']) || $data['entity'] instanceof Traversable) {
63: $pass = (new Collection($data['entity']))->first() !== null;
64: if ($pass) {
65: return new EntityContext($request, $data);
66: }
67: }
68: if ($data['entity'] instanceof EntityInterface) {
69: return new EntityContext($request, $data);
70: }
71: if (is_array($data['entity']) && empty($data['entity']['schema'])) {
72: return new EntityContext($request, $data);
73: }
74: }
75: ],
76: [
77: 'type' => 'array',
78: 'callable' => function ($request, $data) {
79: if (is_array($data['entity']) && isset($data['entity']['schema'])) {
80: return new ArrayContext($request, $data['entity']);
81: }
82: }
83: ],
84: [
85: 'type' => 'form',
86: 'callable' => function ($request, $data) {
87: if ($data['entity'] instanceof Form) {
88: return new FormContext($request, $data);
89: }
90: }
91: ],
92: ] + $providers;
93:
94: return new static($providers);
95: }
96:
97: /**
98: * Add a new context type.
99: *
100: * Form context types allow FormHelper to interact with
101: * data providers that come from outside CakePHP. For example
102: * if you wanted to use an alternative ORM like Doctrine you could
103: * create and connect a new context class to allow FormHelper to
104: * read metadata from doctrine.
105: *
106: * @param string $type The type of context. This key
107: * can be used to overwrite existing providers.
108: * @param callable $check A callable that returns an object
109: * when the form context is the correct type.
110: * @return $this
111: */
112: public function addProvider($type, callable $check)
113: {
114: $this->providers = [$type => ['type' => $type, 'callable' => $check]]
115: + $this->providers;
116:
117: return $this;
118: }
119:
120: /**
121: * Find the matching context for the data.
122: *
123: * If no type can be matched a NullContext will be returned.
124: *
125: * @param \Cake\Http\ServerRequest $request Request instance.
126: * @param array $data The data to get a context provider for.
127: * @return \Cake\View\Form\ContextInterface Context provider.
128: * @throws \RuntimeException when the context class does not implement the
129: * ContextInterface.
130: */
131: public function get(ServerRequest $request, array $data = [])
132: {
133: $data += ['entity' => null];
134:
135: foreach ($this->providers as $provider) {
136: $check = $provider['callable'];
137: $context = $check($request, $data);
138: if ($context) {
139: break;
140: }
141: }
142: if (!isset($context)) {
143: $context = new NullContext($request, $data);
144: }
145: if (!($context instanceof ContextInterface)) {
146: throw new RuntimeException(sprintf(
147: 'Context providers must return object implementing %s. Got "%s" instead.',
148: ContextInterface::class,
149: getTypeName($context)
150: ));
151: }
152:
153: return $context;
154: }
155: }
156: