Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
16591 anikendra 1
<?php
2
/**
3
 * BehaviorCollection
4
 *
5
 * Provides management and interface for interacting with collections of behaviors.
6
 *
7
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
9
 *
10
 * Licensed under The MIT License
11
 * For full copyright and license information, please see the LICENSE.txt
12
 * Redistributions of files must retain the above copyright notice.
13
 *
14
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
15
 * @link          http://cakephp.org CakePHP(tm) Project
16
 * @package       Cake.Model
17
 * @since         CakePHP(tm) v 1.2.0.0
18
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
19
 */
20
 
21
App::uses('ObjectCollection', 'Utility');
22
App::uses('CakeEventListener', 'Event');
23
 
24
/**
25
 * Model behavior collection class.
26
 *
27
 * Defines the Behavior interface, and contains common model interaction functionality.
28
 *
29
 * @package       Cake.Model
30
 */
31
class BehaviorCollection extends ObjectCollection implements CakeEventListener {
32
 
33
/**
34
 * Stores a reference to the attached name
35
 *
36
 * @var string
37
 */
38
	public $modelName = null;
39
 
40
/**
41
 * Keeps a list of all methods of attached behaviors
42
 *
43
 * @var array
44
 */
45
	protected $_methods = array();
46
 
47
/**
48
 * Keeps a list of all methods which have been mapped with regular expressions
49
 *
50
 * @var array
51
 */
52
	protected $_mappedMethods = array();
53
 
54
/**
55
 * Attaches a model object and loads a list of behaviors
56
 *
57
 * @param string $modelName Model name.
58
 * @param array $behaviors Behaviors list.
59
 * @return void
60
 */
61
	public function init($modelName, $behaviors = array()) {
62
		$this->modelName = $modelName;
63
 
64
		if (!empty($behaviors)) {
65
			foreach (BehaviorCollection::normalizeObjectArray($behaviors) as $config) {
66
				$this->load($config['class'], $config['settings']);
67
			}
68
		}
69
	}
70
 
71
/**
72
 * Backwards compatible alias for load()
73
 *
74
 * @param string $behavior Behavior name.
75
 * @param array $config Configuration options.
76
 * @return void
77
 * @deprecated 3.0.0 Will be removed in 3.0. Replaced with load().
78
 */
79
	public function attach($behavior, $config = array()) {
80
		return $this->load($behavior, $config);
81
	}
82
 
83
/**
84
 * Loads a behavior into the collection. You can use use `$config['enabled'] = false`
85
 * to load a behavior with callbacks disabled. By default callbacks are enabled. Disable behaviors
86
 * can still be used as normal.
87
 *
88
 * You can alias your behavior as an existing behavior by setting the 'className' key, i.e.,
89
 * ```
90
 * public $actsAs = array(
91
 *   'Tree' => array(
92
 *     'className' => 'AliasedTree'
93
 *   );
94
 * );
95
 * ```
96
 * All calls to the `Tree` behavior would use `AliasedTree` instead.
97
 *
98
 * @param string $behavior CamelCased name of the behavior to load
99
 * @param array $config Behavior configuration parameters
100
 * @return bool True on success, false on failure
101
 * @throws MissingBehaviorException when a behavior could not be found.
102
 */
103
	public function load($behavior, $config = array()) {
104
		if (isset($config['className'])) {
105
			$alias = $behavior;
106
			$behavior = $config['className'];
107
		}
108
		$configDisabled = isset($config['enabled']) && $config['enabled'] === false;
109
		$priority = isset($config['priority']) ? $config['priority'] : $this->defaultPriority;
110
		unset($config['enabled'], $config['className'], $config['priority']);
111
 
112
		list($plugin, $name) = pluginSplit($behavior, true);
113
		if (!isset($alias)) {
114
			$alias = $name;
115
		}
116
 
117
		$class = $name . 'Behavior';
118
 
119
		App::uses($class, $plugin . 'Model/Behavior');
120
		if (!class_exists($class)) {
121
			throw new MissingBehaviorException(array(
122
				'class' => $class,
123
				'plugin' => substr($plugin, 0, -1)
124
			));
125
		}
126
 
127
		if (!isset($this->{$alias})) {
128
			if (ClassRegistry::isKeySet($class)) {
129
				$this->_loaded[$alias] = ClassRegistry::getObject($class);
130
			} else {
131
				$this->_loaded[$alias] = new $class();
132
				ClassRegistry::addObject($class, $this->_loaded[$alias]);
133
			}
134
		} elseif (isset($this->_loaded[$alias]->settings) && isset($this->_loaded[$alias]->settings[$this->modelName])) {
135
			if ($config !== null && $config !== false) {
136
				$config = array_merge($this->_loaded[$alias]->settings[$this->modelName], $config);
137
			} else {
138
				$config = array();
139
			}
140
		}
141
		if (empty($config)) {
142
			$config = array();
143
		}
144
		$this->_loaded[$alias]->settings['priority'] = $priority;
145
		$this->_loaded[$alias]->setup(ClassRegistry::getObject($this->modelName), $config);
146
 
147
		foreach ($this->_loaded[$alias]->mapMethods as $method => $methodAlias) {
148
			$this->_mappedMethods[$method] = array($alias, $methodAlias);
149
		}
150
		$methods = get_class_methods($this->_loaded[$alias]);
151
		$parentMethods = array_flip(get_class_methods('ModelBehavior'));
152
		$callbacks = array(
153
			'setup', 'cleanup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave',
154
			'beforeDelete', 'afterDelete', 'onError'
155
		);
156
 
157
		foreach ($methods as $m) {
158
			if (!isset($parentMethods[$m])) {
159
				$methodAllowed = (
160
					$m[0] !== '_' && !array_key_exists($m, $this->_methods) &&
161
					!in_array($m, $callbacks)
162
				);
163
				if ($methodAllowed) {
164
					$this->_methods[$m] = array($alias, $m);
165
				}
166
			}
167
		}
168
 
169
		if ($configDisabled) {
170
			$this->disable($alias);
171
		} elseif (!$this->enabled($alias)) {
172
			$this->enable($alias);
173
		} else {
174
			$this->setPriority($alias, $priority);
175
		}
176
 
177
		return true;
178
	}
179
 
180
/**
181
 * Detaches a behavior from a model
182
 *
183
 * @param string $name CamelCased name of the behavior to unload
184
 * @return void
185
 */
186
	public function unload($name) {
187
		list(, $name) = pluginSplit($name);
188
		if (isset($this->_loaded[$name])) {
189
			$this->_loaded[$name]->cleanup(ClassRegistry::getObject($this->modelName));
190
			parent::unload($name);
191
		}
192
		foreach ($this->_methods as $m => $callback) {
193
			if (is_array($callback) && $callback[0] === $name) {
194
				unset($this->_methods[$m]);
195
			}
196
		}
197
	}
198
 
199
/**
200
 * Backwards compatible alias for unload()
201
 *
202
 * @param string $name Name of behavior
203
 * @return void
204
 * @deprecated 3.0.0 Will be removed in 3.0. Use unload instead.
205
 */
206
	public function detach($name) {
207
		return $this->unload($name);
208
	}
209
 
210
/**
211
 * Dispatches a behavior method. Will call either normal methods or mapped methods.
212
 *
213
 * If a method is not handled by the BehaviorCollection, and $strict is false, a
214
 * special return of `array('unhandled')` will be returned to signal the method was not found.
215
 *
216
 * @param Model $model The model the method was originally called on.
217
 * @param string $method The method called.
218
 * @param array $params Parameters for the called method.
219
 * @param bool $strict If methods are not found, trigger an error.
220
 * @return array All methods for all behaviors attached to this object
221
 */
222
	public function dispatchMethod($model, $method, $params = array(), $strict = false) {
223
		$method = $this->hasMethod($method, true);
224
 
225
		if ($strict && empty($method)) {
226
			trigger_error(__d('cake_dev', '%s - Method %s not found in any attached behavior', 'BehaviorCollection::dispatchMethod()', $method), E_USER_WARNING);
227
			return null;
228
		}
229
		if (empty($method)) {
230
			return array('unhandled');
231
		}
232
		if (count($method) === 3) {
233
			array_unshift($params, $method[2]);
234
			unset($method[2]);
235
		}
236
		return call_user_func_array(
237
			array($this->_loaded[$method[0]], $method[1]),
238
			array_merge(array(&$model), $params)
239
		);
240
	}
241
 
242
/**
243
 * Gets the method list for attached behaviors, i.e. all public, non-callback methods.
244
 * This does not include mappedMethods.
245
 *
246
 * @return array All public methods for all behaviors attached to this collection
247
 */
248
	public function methods() {
249
		return $this->_methods;
250
	}
251
 
252
/**
253
 * Check to see if a behavior in this collection implements the provided method. Will
254
 * also check mappedMethods.
255
 *
256
 * @param string $method The method to find.
257
 * @param bool $callback Return the callback for the method.
258
 * @return mixed If $callback is false, a boolean will be returned, if its true, an array
259
 *   containing callback information will be returned. For mapped methods the array will have 3 elements.
260
 */
261
	public function hasMethod($method, $callback = false) {
262
		if (isset($this->_methods[$method])) {
263
			return $callback ? $this->_methods[$method] : true;
264
		}
265
		foreach ($this->_mappedMethods as $pattern => $target) {
266
			if (preg_match($pattern . 'i', $method)) {
267
				if ($callback) {
268
					$target[] = $method;
269
					return $target;
270
				}
271
				return true;
272
			}
273
		}
274
		return false;
275
	}
276
 
277
/**
278
 * Returns the implemented events that will get routed to the trigger function
279
 * in order to dispatch them separately on each behavior
280
 *
281
 * @return array
282
 */
283
	public function implementedEvents() {
284
		return array(
285
			'Model.beforeFind' => 'trigger',
286
			'Model.afterFind' => 'trigger',
287
			'Model.beforeValidate' => 'trigger',
288
			'Model.afterValidate' => 'trigger',
289
			'Model.beforeSave' => 'trigger',
290
			'Model.afterSave' => 'trigger',
291
			'Model.beforeDelete' => 'trigger',
292
			'Model.afterDelete' => 'trigger'
293
		);
294
	}
295
 
296
}