統計
| ブランチ: | リビジョン:

pictcode / lib / Cake / Model / BehaviorCollection.php @ 635eef61

履歴 | 表示 | アノテート | ダウンロード (8.673 KB)

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
}