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.1.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\ORM\Locator;
16:
17: use Cake\Core\App;
18: use Cake\Datasource\ConnectionManager;
19: use Cake\ORM\AssociationCollection;
20: use Cake\ORM\Table;
21: use Cake\Utility\Inflector;
22: use RuntimeException;
23:
24: /**
25: * Provides a default registry/factory for Table objects.
26: */
27: class TableLocator implements LocatorInterface
28: {
29:
30: /**
31: * Configuration for aliases.
32: *
33: * @var array
34: */
35: protected $_config = [];
36:
37: /**
38: * Instances that belong to the registry.
39: *
40: * @var \Cake\ORM\Table[]
41: */
42: protected $_instances = [];
43:
44: /**
45: * Contains a list of Table objects that were created out of the
46: * built-in Table class. The list is indexed by table alias
47: *
48: * @var \Cake\ORM\Table[]
49: */
50: protected $_fallbacked = [];
51:
52: /**
53: * Contains a list of options that were passed to get() method.
54: *
55: * @var array
56: */
57: protected $_options = [];
58:
59: /**
60: * Stores a list of options to be used when instantiating an object
61: * with a matching alias.
62: *
63: * @param string|array $alias Name of the alias or array to completely overwrite current config.
64: * @param array|null $options list of options for the alias
65: * @return $this
66: * @throws \RuntimeException When you attempt to configure an existing table instance.
67: */
68: public function setConfig($alias, $options = null)
69: {
70: if (!is_string($alias)) {
71: $this->_config = $alias;
72:
73: return $this;
74: }
75:
76: if (isset($this->_instances[$alias])) {
77: throw new RuntimeException(sprintf(
78: 'You cannot configure "%s", it has already been constructed.',
79: $alias
80: ));
81: }
82:
83: $this->_config[$alias] = $options;
84:
85: return $this;
86: }
87:
88: /**
89: * Returns configuration for an alias or the full configuration array for all aliases.
90: *
91: * @param string|null $alias Alias to get config for, null for complete config.
92: * @return array The config data.
93: */
94: public function getConfig($alias = null)
95: {
96: if ($alias === null) {
97: return $this->_config;
98: }
99:
100: return isset($this->_config[$alias]) ? $this->_config[$alias] : [];
101: }
102:
103: /**
104: * Stores a list of options to be used when instantiating an object
105: * with a matching alias.
106: *
107: * The options that can be stored are those that are recognized by `get()`
108: * If second argument is omitted, it will return the current settings
109: * for $alias.
110: *
111: * If no arguments are passed it will return the full configuration array for
112: * all aliases
113: *
114: * @deprecated 3.4.0 Use setConfig()/getConfig() instead.
115: * @param string|array|null $alias Name of the alias
116: * @param array|null $options list of options for the alias
117: * @return array The config data.
118: * @throws \RuntimeException When you attempt to configure an existing table instance.
119: */
120: public function config($alias = null, $options = null)
121: {
122: deprecationWarning(
123: 'TableLocator::config() is deprecated. ' .
124: 'Use getConfig()/setConfig() instead.'
125: );
126: if ($alias !== null) {
127: if (is_string($alias) && $options === null) {
128: return $this->getConfig($alias);
129: }
130:
131: $this->setConfig($alias, $options);
132: }
133:
134: return $this->getConfig($alias);
135: }
136:
137: /**
138: * Get a table instance from the registry.
139: *
140: * Tables are only created once until the registry is flushed.
141: * This means that aliases must be unique across your application.
142: * This is important because table associations are resolved at runtime
143: * and cyclic references need to be handled correctly.
144: *
145: * The options that can be passed are the same as in Cake\ORM\Table::__construct(), but the
146: * `className` key is also recognized.
147: *
148: * ### Options
149: *
150: * - `className` Define the specific class name to use. If undefined, CakePHP will generate the
151: * class name based on the alias. For example 'Users' would result in
152: * `App\Model\Table\UsersTable` being used. If this class does not exist,
153: * then the default `Cake\ORM\Table` class will be used. By setting the `className`
154: * option you can define the specific class to use. The className option supports
155: * plugin short class references {@link Cake\Core\App::shortName()}.
156: * - `table` Define the table name to use. If undefined, this option will default to the underscored
157: * version of the alias name.
158: * - `connection` Inject the specific connection object to use. If this option and `connectionName` are undefined,
159: * The table class' `defaultConnectionName()` method will be invoked to fetch the connection name.
160: * - `connectionName` Define the connection name to use. The named connection will be fetched from
161: * Cake\Datasource\ConnectionManager.
162: *
163: * *Note* If your `$alias` uses plugin syntax only the name part will be used as
164: * key in the registry. This means that if two plugins, or a plugin and app provide
165: * the same alias, the registry will only store the first instance.
166: *
167: * @param string $alias The alias name you want to get.
168: * @param array $options The options you want to build the table with.
169: * If a table has already been loaded the options will be ignored.
170: * @return \Cake\ORM\Table
171: * @throws \RuntimeException When you try to configure an alias that already exists.
172: */
173: public function get($alias, array $options = [])
174: {
175: if (isset($this->_instances[$alias])) {
176: if (!empty($options) && $this->_options[$alias] !== $options) {
177: throw new RuntimeException(sprintf(
178: 'You cannot configure "%s", it already exists in the registry.',
179: $alias
180: ));
181: }
182:
183: return $this->_instances[$alias];
184: }
185:
186: $this->_options[$alias] = $options;
187: list(, $classAlias) = pluginSplit($alias);
188: $options = ['alias' => $classAlias] + $options;
189:
190: if (isset($this->_config[$alias])) {
191: $options += $this->_config[$alias];
192: }
193:
194: $className = $this->_getClassName($alias, $options);
195: if ($className) {
196: $options['className'] = $className;
197: } else {
198: if (empty($options['className'])) {
199: $options['className'] = Inflector::camelize($alias);
200: }
201: if (!isset($options['table']) && strpos($options['className'], '\\') === false) {
202: list(, $table) = pluginSplit($options['className']);
203: $options['table'] = Inflector::underscore($table);
204: }
205: $options['className'] = 'Cake\ORM\Table';
206: }
207:
208: if (empty($options['connection'])) {
209: if (!empty($options['connectionName'])) {
210: $connectionName = $options['connectionName'];
211: } else {
212: /* @var \Cake\ORM\Table $className */
213: $className = $options['className'];
214: $connectionName = $className::defaultConnectionName();
215: }
216: $options['connection'] = ConnectionManager::get($connectionName);
217: }
218: if (empty($options['associations'])) {
219: $associations = new AssociationCollection($this);
220: $options['associations'] = $associations;
221: }
222:
223: $options['registryAlias'] = $alias;
224: $this->_instances[$alias] = $this->_create($options);
225:
226: if ($options['className'] === 'Cake\ORM\Table') {
227: $this->_fallbacked[$alias] = $this->_instances[$alias];
228: }
229:
230: return $this->_instances[$alias];
231: }
232:
233: /**
234: * Gets the table class name.
235: *
236: * @param string $alias The alias name you want to get.
237: * @param array $options Table options array.
238: * @return string|false
239: */
240: protected function _getClassName($alias, array $options = [])
241: {
242: if (empty($options['className'])) {
243: $options['className'] = Inflector::camelize($alias);
244: }
245:
246: return App::className($options['className'], 'Model/Table', 'Table');
247: }
248:
249: /**
250: * Wrapper for creating table instances
251: *
252: * @param array $options The alias to check for.
253: * @return \Cake\ORM\Table
254: */
255: protected function _create(array $options)
256: {
257: return new $options['className']($options);
258: }
259:
260: /**
261: * {@inheritDoc}
262: */
263: public function exists($alias)
264: {
265: return isset($this->_instances[$alias]);
266: }
267:
268: /**
269: * {@inheritDoc}
270: */
271: public function set($alias, Table $object)
272: {
273: return $this->_instances[$alias] = $object;
274: }
275:
276: /**
277: * {@inheritDoc}
278: */
279: public function clear()
280: {
281: $this->_instances = [];
282: $this->_config = [];
283: $this->_fallbacked = [];
284: }
285:
286: /**
287: * Returns the list of tables that were created by this registry that could
288: * not be instantiated from a specific subclass. This method is useful for
289: * debugging common mistakes when setting up associations or created new table
290: * classes.
291: *
292: * @return \Cake\ORM\Table[]
293: */
294: public function genericInstances()
295: {
296: return $this->_fallbacked;
297: }
298:
299: /**
300: * {@inheritDoc}
301: */
302: public function remove($alias)
303: {
304: unset(
305: $this->_instances[$alias],
306: $this->_config[$alias],
307: $this->_fallbacked[$alias]
308: );
309: }
310: }
311: