Completed
Push — master ( fc4353...2e8e64 )
by Haralan
01:19
created

Node   C

Complexity

Total Complexity 70

Size/Duplication

Total Lines 735
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 70
lcom 2
cbo 8
dl 0
loc 735
ccs 0
cts 203
cp 0
rs 5
c 0
b 0
f 0

52 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 3
A driver() 0 4 1
A extension() 0 9 2
A parent() 0 4 1
A load_new_id() 0 5 1
A next_wait_time() 0 15 4
A wait() 0 6 1
A dom() 0 4 1
A is_root() 0 4 1
A id() 0 4 1
A html() 0 4 1
A __toString() 0 4 1
A tag_name() 0 4 1
A attribute() 0 4 1
A text() 0 4 1
A is_visible() 0 4 1
A is_selected() 0 4 1
A is_checked() 0 4 1
A value() 0 4 1
A set() 0 5 1
A append() 0 8 1
A click() 0 5 1
A select_option() 0 5 1
A unselect_option() 0 5 1
A hover() 0 5 1
A drop_files() 0 5 1
A click_on() 0 5 1
A click_link() 0 5 1
A click_button() 0 5 1
A fill_in() 0 11 2
A choose() 0 5 1
A check() 0 5 1
A uncheck() 0 5 1
A attach_file() 0 5 1
A select() 0 14 2
A unselect() 0 13 2
A confirm() 0 5 1
A execute() 0 13 2
A screenshot() 0 5 1
A hover_on() 0 5 1
A hover_link() 0 5 1
A hover_field() 0 5 1
A hover_button() 0 5 1
A find_field() 0 4 1
A find_link() 0 4 1
A find_button() 0 4 1
A find() 0 16 2
A not_present() 0 16 2
A end() 0 4 1
A all() 0 6 1
B get_locator() 0 22 5
A __call() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like Node often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Node, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Openbuildings\Spiderling;
4
5
/**
6
 * Node - represents HTML Dom node
7
 *
8
 * @package    Openbuildings\Spiderling
9
 * @author     Ivan Kerin
10
 * @copyright  (c) 2013 OpenBuildings Ltd.
11
 * @license    http://spdx.org/licenses/BSD-3-Clause
12
 */
13
class Node {
14
15
	const DEFAULT_WAIT_TIME = 2000;
16
17
	/**
18
	 * The driver for traversing this node and its children
19
	 * @var Driver
20
	 */
21
	protected $_driver;
22
23
	/**
24
	 * The parent node, NULL if root
25
	 * @var Node
26
	 */
27
	protected $_parent;
28
29
	/**
30
	 * The id for the current node, NULL if root
31
	 * @var string
32
	 */
33
	protected $_id = NULL;
34
35
	/**
36
	 * The time find operation will wait for the node to appear, in milliseconds
37
	 * @var integer
38
	 */
39
	protected $_next_wait_time;
40
41
	/**
42
	 * All methods not for this node will be proxied through this
43
	 * @var object
44
	 */
45
	protected $_extension;
46
47
	function __construct(Driver $driver = NULL, Node $parent = NULL, $id = NULL)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
48
	{
49
		$this->_driver = $driver;
50
		$this->_parent = $parent;
51
		$this->_id = $id;
52
53
		if ($parent AND $parent->_extension)
54
		{
55
			$this->_extension = $parent->_extension;
56
		}
57
	}
58
59
	/**
60
	 * Getter, get the current driver object
61
	 * @return Driver
62
	 */
63
	public function driver()
64
	{
65
		return $this->_driver;
66
	}
67
68
	/**
69
	 * Getter / Setter for the extension object
70
	 * @param  mixed $extension
71
	 * @return mixed|$this
72
	 */
73
	public function extension($extension = NULL)
74
	{
75
		if ($extension !== NULL)
76
		{
77
			$this->_extension = $extension;
78
			return $this;
79
		}
80
		return $this->_extension;
81
	}
82
83
	/**
84
	 * Getter - get the parent node
85
	 * @return Node
86
	 */
87
	public function parent()
88
	{
89
		return $this->_parent;
90
	}
91
92
	/**
93
	 * Setter this method is used to populate a node with a new id
94
	 * @param  string $id
95
	 * @return Node     $this
96
	 */
97
	public function load_new_id($id)
98
	{
99
		$this->_id = $id;
100
		return $this;
101
	}
102
103
	/**
104
	 * Setter / Getter of the next wait time
105
	 * @param  integer $next_wait_time milliseconds
106
	 * @return integer|Node
107
	 */
108
	public function next_wait_time($next_wait_time = NULL)
109
	{
110
		if ($next_wait_time !== NULL)
111
		{
112
			$this->_next_wait_time = $next_wait_time;
113
			return $this;
114
		}
115
116
		if ($this->_next_wait_time === NULL AND $this->_driver)
117
		{
118
			$this->_next_wait_time = $this->_driver->default_wait_time;
119
		}
120
121
		return $this->_next_wait_time;
122
	}
123
124
	/**
125
	 * Wait milliseconds
126
	 * @param  integer $milliseconds
127
	 * @return Node                $this
128
	 */
129
	public function wait($milliseconds = 1000)
130
	{
131
		usleep($milliseconds * 1000);
132
133
		return $this;
134
	}
135
136
	/**
137
	 * The DOMDocument or DOMElement representation of the current tag
138
	 * @return DOMDocument|DOMElement
139
	 */
140
	public function dom()
141
	{
142
		return $this->driver()->dom($this->id());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Openbuildings\Spiderling\Driver as the method dom() does only exist in the following sub-classes of Openbuildings\Spiderling\Driver: Openbuildings\Spiderling\Driver_Kohana, Openbuildings\Spiderling\Driver_Simple, Openbuildings\Spiderling\Driver_SimpleXML. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
143
	}
144
145
	/**
146
	 * GETTERS
147
	 * ===========================================
148
	 */
149
150
	/**
151
	 * is this the main html page?
152
	 * @return boolean
153
	 */
154
	public function is_root()
155
	{
156
		return ! (bool) $this->_id;
157
	}
158
159
	/**
160
	 * The current internal ID, unique to this page
161
	 * @return mixed
162
	 */
163
	public function id()
164
	{
165
		return $this->_id;
166
	}
167
168
	/**
169
	 * The html source of the current tag
170
	 * @return string
171
	 */
172
	public function html()
173
	{
174
		return $this->driver()->html($this->id());
175
	}
176
177
	/**
178
	 * The html source of the current tag
179
	 * @return string
180
	 */
181
	public function __toString()
182
	{
183
		return (string) $this->html();
184
	}
185
186
	/**
187
	 * The tag name of the current tag (body, div, input)
188
	 * @return string
189
	 */
190
	public function tag_name()
191
	{
192
		return $this->driver()->tag_name($this->id());
193
	}
194
195
	/**
196
	 * Attribute of the current tag
197
	 * @param  string $name the name of the attribute
198
	 * @return string
199
	 */
200
	public function attribute($name)
201
	{
202
		return $this->driver()->attribute($this->id(), $name);
203
	}
204
205
	/**
206
	 * The text content of the current tag (similar to javascript's innerText)
207
	 * @return string
208
	 */
209
	public function text()
210
	{
211
		return $this->driver()->text($this->id());
212
	}
213
214
	/**
215
	 * Is this element visible?
216
	 * @return boolean
217
	 */
218
	public function is_visible()
219
	{
220
		return $this->driver()->is_visible($this->id());
221
	}
222
223
	/**
224
	 * Is this option element selected?
225
	 * @return boolean
226
	 */
227
	public function is_selected()
228
	{
229
		return $this->driver()->is_selected($this->id());
230
	}
231
232
	/**
233
	 * Is this checkbox checked?
234
	 * @return boolean
235
	 */
236
	public function is_checked()
237
	{
238
		return $this->driver()->is_checked($this->id());
239
	}
240
241
	/**
242
	 * Get the value of the current form field
243
	 * @return string
244
	 */
245
	public function value()
246
	{
247
		return $this->driver()->value($this->id());
248
	}
249
250
251
	/**
252
	 * SETTERS
253
	 * ===========================================
254
	 */
255
256
	/**
257
	 * Set the value for the current form field
258
	 * @param mixed $value
259
	 * @return Node $this
260
	 */
261
	public function set($value)
262
	{
263
		$this->driver()->set($this->id(), $value);
264
		return $this;
265
	}
266
267
	/**
268
	 * Append to the current value - useful for textarea / input fields
269
	 * @param  string $value
270
	 * @return Node $this
271
	 */
272
	public function append($value)
273
	{
274
		$current_value = $this->driver()->value($this->id());
275
276
		$this->driver()->set($this->id(), $current_value.$value);
277
278
		return $this;
279
	}
280
281
	/**
282
	 * Click on the current html tag, either a button or a link
283
	 * @return Node $this
284
	 */
285
	public function click()
286
	{
287
		$this->driver()->click($this->id());
288
		return $this;
289
	}
290
291
	/**
292
	 * Select an option for the current select tag
293
	 * @return Node $this
294
	 */
295
	public function select_option()
296
	{
297
		$this->driver()->select_option($this->id(), TRUE);
298
		return $this;
299
	}
300
301
	/**
302
	 * Unselect an option for the current select tag
303
	 * @return Node $this
304
	 */
305
	public function unselect_option()
306
	{
307
		$this->driver()->select_option($this->id(), FALSE);
308
		return $this;
309
	}
310
311
	/**
312
	 * Hover over the current tag with the mouse
313
	 * @param  integer       $x offset inside the tag
314
	 * @param  integer       $y offset inside the tag
315
	 * @return Node $this
316
	 */
317
	public function hover($x = NULL, $y = NULL)
318
	{
319
		$this->driver()->move_to($this->id(), $x, $y);
320
		return $this;
321
	}
322
323
	/**
324
	 * Simulate drop file events on the current element
325
	 * @param  array|string $files local file filename or an array of filenames
326
	 * @return Node        $this
327
	 */
328
	public function drop_files($files)
329
	{
330
		$this->driver()->drop_files($this->id(), $files);
0 ignored issues
show
Bug introduced by
The method drop_files() does not seem to exist on object<Openbuildings\Spiderling\Driver>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
331
		return $this;
332
	}
333
334
335
	/**
336
	 * ACTIONS
337
	 * =======================================
338
	 */
339
340
	/**
341
	 * Click on a specifc tag child of the current tag
342
	 * @param  string|array $selector
343
	 * @param  array         $filters
344
	 * @return Node $this
345
	 */
346
	public function click_on($selector, array $filters = array())
347
	{
348
		$this->find($selector, $filters)->click();
349
		return $this;
350
	}
351
352
	/**
353
	 * Click on a specifc link child of the current tag
354
	 * @param  string|array  $selector
355
	 * @param  array         $filters
356
	 * @return Node $this
357
	 */
358
	public function click_link($selector, array $filters = array())
359
	{
360
		$this->find_link($selector, $filters)->click();
361
		return $this;
362
	}
363
364
	/**
365
	 * Click on a specifc button child of the current tag
366
	 * @param  string|array  $selector
367
	 * @param  array         $filters
368
	 * @return Node $this
369
	 */
370
	public function click_button($selector, array $filters = array())
371
	{
372
		$this->find_button($selector, $filters)->click();
373
		return $this;
374
	}
375
376
	/**
377
	 * Set the value of the specific form field inside the current tag
378
	 * @param  string|array  $selector
379
	 * @param  mixed         $with     the value to be set
380
	 * @param  array         $filters
381
	 * @return Node this
382
	 */
383
	public function fill_in($selector, $with, array $filters = array())
384
	{
385
		$field = $this->find_field($selector, $filters);
386
387
		if ( ! in_array($field->tag_name(), array('input', 'textarea')))
388
			throw new Exception('Element of type ":type" cannot be filled in! Only input and textarea elements can.');
389
390
		$field->set($with);
391
392
		return $this;
393
	}
394
395
	/**
396
	 * Choose a spesific radio tag inside the current tag
397
	 * @param  string|array   $selector
398
	 * @param  array          $filters
399
	 * @return Node  $this
400
	 */
401
	public function choose($selector, array $filters = array())
402
	{
403
		$this->find_field($selector, $filters)->set(TRUE);
404
		return $this;
405
	}
406
407
	/**
408
	 * Check a spesific checkbox input tag inside the current tag
409
	 * @param  string|array   $selector
410
	 * @param  array          $filters
411
	 * @return Node  $this
412
	 */
413
	public function check($selector, array $filters = array())
414
	{
415
		$this->find_field($selector, $filters)->set(TRUE);
416
		return $this;
417
	}
418
419
	/**
420
	 * Uncheck a spesific checkbox input tag inside the current tag
421
	 * @param  string|array   $selector
422
	 * @param  array          $filters
423
	 * @return Node  $this
424
	 */
425
	public function uncheck($selector, array $filters = array())
426
	{
427
		$this->find_field($selector, $filters)->set(FALSE);
428
		return $this;
429
	}
430
431
	/**
432
	 * Attach a file to a spesific file input tag inside the current tag
433
	 * @param  string|array   $selector
434
	 * @param  string         $file      the filename for the file
435
	 * @param  array          $filters
436
	 * @return Node  $this
437
	 */
438
	public function attach_file($selector, $file, array $filters = array())
439
	{
440
		$this->find_field($selector, $filters)->set($file);
441
		return $this;
442
	}
443
444
	/**
445
	 * Select an option of a spesific select tag inside the current tag
446
	 *
447
	 * To select the option the second parameter can be either a string of the option text
448
	 * or a filter to be applied on the options e.g. array('value' => 10)
449
	 *
450
	 * @param  string|array   $selector
451
	 * @param  array          $filters
452
	 * @param  array|string   $option_filters
453
	 * @return Node  $this
454
	 */
455
	public function select($selector, $option_filters, array $filters = array())
456
	{
457
		if ( ! is_array($option_filters))
458
		{
459
			$option_filters = array('text' => $option_filters);
460
		}
461
462
		$this
463
			->find_field($selector, $filters)
464
				->find('option', $option_filters)
465
					->select_option();
466
467
		return $this;
468
	}
469
470
	/**
471
	 * Unselect an option of a spesific select tag inside the current tag
472
	 *
473
	 * To select the option the second parameter can be either a string of the option text
474
	 * or a filter to be applied on the options e.g. array('value' => 10)
475
	 *
476
	 * @param  string|array   $selector
477
	 * @param  array          $filters
478
	 * @param  array|string   $option_filters
479
	 * @return Node  $this
480
	 */
481
	public function unselect($selector, $option_filters, array $filters = array())
0 ignored issues
show
Unused Code introduced by
The parameter $filters is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
482
	{
483
		if ( ! is_array($option_filters))
484
		{
485
			$option_filters = array('value' => $option_filters);
486
		}
487
488
		$this
489
			->find_field($selector)
490
				->find('option', $option_filters)
491
					->unselect_option();
492
		return $this;
493
	}
494
495
	/**
496
	 * Confirm a javascript alert/confirm dialog box
497
	 *
498
	 * @param  boolean|string $confirm alert/confirm - use boolean for inputs use string
499
	 * @return Node  $this
500
	 */
501
	public function confirm($confirm)
502
	{
503
		$this->driver()->confirm($confirm);
0 ignored issues
show
Bug introduced by
It seems like $confirm defined by parameter $confirm on line 501 can also be of type string; however, Openbuildings\Spiderling\Driver::confirm() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
504
		return $this;
505
	}
506
507
	/**
508
	 * Execute arbitrary javascript on the page and get the result
509
	 *
510
	 * @param  string $script
511
	 * @return mixed
512
	 */
513
	public function execute($script, $callback = NULL)
514
	{
515
		$result = $this->driver()->execute($this->id(), $script);
516
		if ($callback)
517
		{
518
			call_user_func($callback, $result, $this);
519
			return $this;
520
		}
521
		else
522
		{
523
			return $result;
524
		}
525
	}
526
527
	/**
528
	 * Perform a screenshot of the current into the given file
529
	 * @param  string $file
530
	 * @return Node       $this
531
	 */
532
	public function screenshot($file)
533
	{
534
		$this->driver()->screenshot($file);
535
		return $this;
536
	}
537
538
	/**
539
	 * Hover the mouse over a specific tag child of the current tag
540
	 * @param  string|array  $selector
541
	 * @param  array         $filters
542
	 * @return Node $this
543
	 */
544
	public function hover_on($selector, array $filters = array())
545
	{
546
		$this->find($selector, $filters)->hover();
547
		return $this;
548
	}
549
550
	/**
551
	 * Hover the mouse over a specific link child of the current tag
552
	 * @param  string|array  $selector
553
	 * @param  array         $filters
554
	 * @return Node $this
555
	 */
556
	public function hover_link($selector, array $filters = array())
557
	{
558
		$this->find_link($selector, $filters)->hover();
559
		return $this;
560
	}
561
562
	/**
563
	 * Hover the mouse over a specific field child of the current tag
564
	 * @param  string|array  $selector
565
	 * @param  array         $filters
566
	 * @return Node $this
567
	 */
568
	public function hover_field($selector, array $filters = array())
569
	{
570
		$this->find_field($selector, $filters)->hover();
571
		return $this;
572
	}
573
574
	/**
575
	 * Hover the mouse over a specific button child of the current tag
576
	 * @param  string|array  $selector
577
	 * @param  array         $filters
578
	 * @return Node $this
579
	 */
580
	public function hover_button($selector, array $filters = array())
581
	{
582
		$this->find_button($selector, $filters)->hover();
583
		return $this;
584
	}
585
586
	/**
587
	 * FINDERS
588
	 * =====================================================
589
	 */
590
591
	/**
592
	 * Find an html form field child of the current tag
593
	 * @param  string|array   $selector
594
	 * @param  array          $filters
595
	 * @return Node  $this
596
	 */
597
	public function find_field($selector, array $filters = array())
598
	{
599
		return $this->find(array('field', $selector, $filters));
600
	}
601
602
	/**
603
	 * Find an html form field child of the current tag
604
	 * @param  string|array   $selector
605
	 * @param  array          $filters
606
	 * @return Node  $this
607
	 */
608
	public function find_link($selector, array $filters = array())
609
	{
610
		return $this->find(array('link', $selector, $filters));
611
	}
612
613
	/**
614
	 * Find an html button tag child of the current tag
615
	 * @param  string|array   $selector
616
	 * @param  array          $filters
617
	 * @return Node  $this
618
	 */
619
	public function find_button($selector, array $filters = array())
620
	{
621
		return $this->find(array('button', $selector, $filters));
622
	}
623
624
	/**
625
	 * Find an html tag child of the current tag
626
	 * This is the basic find method that is used by all the other finders.
627
	 * To work with ajax requests it waits a bit (defualt 2 seconds) for the content to appear on the page
628
	 * before throwing an Functest_Exception_Notfound exception
629
	 *
630
	 * @param  string|array   $selector
631
	 * @param  array          $filters
632
	 * @throws Functest_Exception_Notfound If element not found
633
	 * @return Node  $this
634
	 */
635
	public function find($selector, array $filters = array())
636
	{
637
		$locator = self::get_locator($selector, $filters);
638
		$self = $this;
639
640
		$node = Attempt::make(function() use ($self, $locator){
641
			return $self->all($locator)->first();
642
		}, $this->next_wait_time());
0 ignored issues
show
Bug introduced by
It seems like $this->next_wait_time() targeting Openbuildings\Spiderling\Node::next_wait_time() can also be of type this<Openbuildings\Spiderling\Node>; however, Openbuildings\Spiderling\Attempt::make() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
643
644
		$this->_next_wait_time = NULL;
645
646
		if ($node == NULL)
647
			throw new Exception_Notfound($locator, $this->driver());
648
649
		return $node;
650
	}
651
652
	/**
653
	 * Oposite to the find method()
654
	 *
655
	 * @param  string|array  $selector
656
	 * @param  array         $filters
657
	 * @throws Functest_Exception_Found If element is found on the page
658
	 * @return Node $this
659
	 */
660
	public function not_present($selector, array $filters = array())
661
	{
662
		$locator = self::get_locator($selector, $filters);
663
		$self = $this;
664
665
		$not_found = Attempt::make(function() use ($self, $locator){
666
			return ! $self->all($locator)->first();
667
		}, $this->next_wait_time());
0 ignored issues
show
Bug introduced by
It seems like $this->next_wait_time() targeting Openbuildings\Spiderling\Node::next_wait_time() can also be of type this<Openbuildings\Spiderling\Node>; however, Openbuildings\Spiderling\Attempt::make() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
668
669
		$this->_next_wait_time = NULL;
670
671
		if ( ! $not_found)
672
			throw new Exception_Found($locator, $this->driver());
673
674
		return TRUE;
675
	}
676
677
	/**
678
	 * Returns the parent element
679
	 *
680
	 * @return Node parent
681
	 */
682
	public function end()
683
	{
684
		return $this->_parent;
685
	}
686
687
	/**
688
	 * Find a list of elements represented by the selector / filter
689
	 *
690
	 * @param  string|array $selector
691
	 * @param  array        $filters
692
	 * @return Nodelist
693
	 */
694
	public function all($selector, array $filters = array())
695
	{
696
		$locator = self::get_locator($selector, $filters);
697
698
		return new Nodelist($this->driver(), $locator, $this);
699
	}
700
701
	/**
702
	 * Shortcuts for creating locators (from arrays or nested arrays)
703
	 * @param  string|Locator|array $selector
704
	 * @param  array  $filters
705
	 * @return Locator
706
	 */
707
	public static function get_locator($selector, array $filters = array())
708
	{
709
		if ($selector instanceof Locator)
710
			return $selector;
711
712
		$type = NULL;
713
714
		if (is_array($selector))
715
		{
716
			// Manage nested selectors
717
			if (is_array($selector[1]))
718
			{
719
				$selector = $selector[1];
720
			}
721
722
			$type = $selector[0];
723
			$filters = isset($selector[2]) ? $selector[2] : array();
724
			$selector = $selector[1];
725
		}
726
727
		return new Locator($type, $selector, $filters);
728
	}
729
730
	/**
731
	 * Pass all other methods to the extension if it is set. That way you can add additional methods
732
	 * @param  string $method
733
	 * @param  array $arguments
734
	 * @return Node $this
735
	 */
736
	public function __call($method, $arguments)
737
	{
738
		if ( ! $this->extension() OR ! method_exists($this->extension(), $method))
739
			throw new Exception_Methodmissing('Method :method does not exist on this node or node extension', array(':method' => $method));
740
741
		array_unshift($arguments, $this);
742
743
		call_user_func_array(array($this->extension(), $method), $arguments);
744
745
		return $this;
746
	}
747
}
748