Completed
Push — master ( 94f348...ce557b )
by Marko
02:44
created

Entity   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 140
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 69.44%

Importance

Changes 0
Metric Value
wmc 12
lcom 1
cbo 1
dl 0
loc 140
ccs 25
cts 36
cp 0.6944
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __call() 0 4 1
A DOMElement() 0 18 3
A __construct() 0 19 1
A position() 0 5 1
A rotation() 0 5 1
A scale() 0 5 1
A component() 0 19 4
1
<?php
2
/** @formatter:off
3
 * ******************************************************************
4
 * Created by   Marko Kungla on Jun 20, 2016 - 9:11:10 PM
5
 * Contact      [email protected]
6
 * @copyright   2016 Marko Kungla - https://github.com/mkungla
7
 * @license     The MIT License (MIT)
8
 * 
9
 * @category       AframeVR
10
 * @package        aframe-php
11
 * 
12
 * Lang         PHP (php version >= 7)
13
 * Encoding     UTF-8
14
 * File         Entity.php
15
 * Code format  PSR-2 and 12
16
 * @link        https://github.com/mkungla/aframe-php
17
 ^ @issues      https://github.com/mkungla/aframe-php/issues
18
 * ********************************************************************
19
 * Contributors:
20
 * @author Marko Kungla <[email protected]>
21
 * ********************************************************************
22
 * Comments:
23
 * @formatter:on */
24
namespace AframeVR\Core;
25
26
use \AframeVR\Core\Exceptions\BadComponentCallException;
27
use \AframeVR\Interfaces\{
28
    ComponentInterface,
29
    EntityInterface
30
};
31
use \DOMElement;
32
33
class Entity implements EntityInterface
34
{
35
36
    protected $components = array();
37
38 8
    public function __construct()
39
    {
40
        /* Components which All entities inherently have */
41 8
        $this->component('Position');
42 8
        $this->component('Rotation');
43
        
44
        /*
45
         * We initialize common entity components here since
46
         * init and defaults are most cases overwritten by extending class
47
         */
48 8
        $this->position();
49 8
        $this->rotation();
50
        
51
        /* Extending entity components and init */
52 8
        $this->init();
0 ignored issues
show
Documentation Bug introduced by
The method init does not exist on object<AframeVR\Core\Entity>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
53
        
54
        /* Extending entity defaults */
55 8
        $this->defaults();
0 ignored issues
show
Documentation Bug introduced by
The method defaults does not exist on object<AframeVR\Core\Entity>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
56 8
    }
57
58
    /**
59
     * Position component
60
     * 
61
     * All entities inherently have the position component.
62
     * 
63
     * @param number $x
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $x a bit more specific; maybe use integer.
Loading history...
64
     * @param number $y
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $y a bit more specific; maybe use integer.
Loading history...
65
     * @param number $z
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $z a bit more specific; maybe use integer.
Loading history...
66
     * @return Entity
67
     */
68 8
    public function position($x = 0, $y = 0, $z = 0): EntityInterface
69
    {
70 8
        $this->component('Position')->update($x, $y, $z);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface AframeVR\Interfaces\ComponentInterface as the method update() does only exist in the following implementations of said interface: AframeVR\Components\Position, AframeVR\Components\Rotation, AframeVR\Components\Scale.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
71 8
        return $this;
72
    }
73
74
    /**
75
     * Rotation component
76
     * 
77
     * All entities inherently have the rotation component.
78
     * 
79
     * @param number $x
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $x a bit more specific; maybe use integer.
Loading history...
80
     * @param number $y
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $y a bit more specific; maybe use integer.
Loading history...
81
     * @param number $z
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $z a bit more specific; maybe use integer.
Loading history...
82
     * @return EntityInterface
0 ignored issues
show
Documentation introduced by
Should the return type not be \AframeVR\Interfaces\EntityInterface?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
83
     */
84 8
    public function rotation($x = 0, $y = 0, $z = 0): EntityInterface
85
    {
86 8
        $this->component('Rotation')->update($x, $y, $z);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface AframeVR\Interfaces\ComponentInterface as the method update() does only exist in the following implementations of said interface: AframeVR\Components\Position, AframeVR\Components\Rotation, AframeVR\Components\Scale.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
87 8
        return $this;
88
    }
89
    
90
    /**
91
     * Scale component
92
     *
93
     * All entities inherently have the scale component.
94
     *
95
     * @param number $x
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $x a bit more specific; maybe use integer.
Loading history...
96
     * @param number $y
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $y a bit more specific; maybe use integer.
Loading history...
97
     * @param number $z
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $z a bit more specific; maybe use integer.
Loading history...
98
     * @return EntityInterface
0 ignored issues
show
Documentation introduced by
Should the return type not be \AframeVR\Interfaces\EntityInterface?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
99
     */
100 1
    public function scale($x = 0, $y = 0, $z = 0): EntityInterface
101
    {
102 1
        $this->component('Scale')->update($x, $y, $z);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface AframeVR\Interfaces\ComponentInterface as the method update() does only exist in the following implementations of said interface: AframeVR\Components\Position, AframeVR\Components\Rotation, AframeVR\Components\Scale.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
103 1
        return $this;
104
    }
105
    
106
    /**
107
     * Load component for this entity
108
     *
109
     * @param string $component_name            
110
     * @throws BadComponentCallException
111
     * @return ComponentInterface
0 ignored issues
show
Documentation introduced by
Should the return type not be \AframeVR\Interfaces\ComponentInterface?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
112
     */
113 8
    public function component(string $component_name): ComponentInterface
114
    {
115 8
        $component_name = strtolower($component_name);
116
        
117 8
        if (! array_key_exists($component_name, $this->components)) {
118
            try {
119 8
                $component = sprintf('\AframeVR\Components\%s', ucfirst($component_name));
120 8
                if (class_exists($component)) {
121 8
                    $this->components[$component_name] = new $component();
122
                } else {
123 8
                    throw new BadComponentCallException($component_name);
124
                }
125
            } catch (BadComponentCallException $e) {
126
                die($e->getMessage());
127
            }
128
        }
129
        
130 8
        return $this->components[$component_name];
131
    }
132
133
    /**
134
     * Handle entity components
135
     *
136
     * Since we might need to customize these to have
137
     * custom components loaded as $this->methosd aswell therefore
138
     * we have these placeholder magic methods here
139
     *
140
     * @param string $component_name            
141
     * @param array $args            
142
     */
143
    public function __call(string $component_name, array $args)
144
    {
145
        return call_user_func_array($this->{$component_name}->bindTo($this), $args);
146
    }
147
148
    /**
149
     * Create and add DOM element of the entity
150
     *
151
     * @param unknown $aframe_dom            
152
     * @return DOMElement
153
     */
154
    public function DOMElement(&$aframe_dom): DOMElement
155
    {
156
        /* Create entity DOMElement */
157
        $a_entity = $aframe_dom->createElement('a-entity', "\n");
158
        foreach ($this->components as $component) {
159
            /**
160
             * Remeve component default properties which are not needed
161
             */
162
            $component->removeDefaultDOMAttributes();
163
            /*
164
             * Check does component has any attributes to add to DOM element.
165
             * default attributes most of cases are ommited so we might not have any attributes to add
166
             */
167
            if ($component->hasDOMAttributes())
168
                $a_entity->setAttributeNode($component->getDOMAttributes());
169
        }
170
        return $a_entity;
171
    }
172
}
173