Completed
Push — master ( 446f2e...32b2c3 )
by Nelson
11:26
created

IStrictPropertiesContainerTester   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 155
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 3

Importance

Changes 0
Metric Value
dl 0
loc 155
rs 10
c 0
b 0
f 0
wmc 14
lcom 0
cbo 3

12 Methods

Rating   Name   Duplication   Size   Complexity  
readonlyPropertiesProvider() 0 1 ?
writeonlyPropertiesProvider() 0 1 ?
readwritePropertiesProvider() 0 1 ?
unaccesiblePropertiesProvider() 0 1 ?
objectInstanceProvider() 0 1 ?
B testReadonlyPropertiesAreReadables() 0 30 3
A testReadonlyPropertiesAreNotWritables() 0 7 1
A testWriteonlyPropertiesAreWritables() 0 11 2
A testWriteonlyPropertiesAreNotReadables() 0 8 1
B testPropertiesWithFullAccessAreReadablesAndWritables() 0 34 3
A testUnaccessiblePropertiesThrowsCatchableError() 0 17 3
A testIsUnableToCreateDirectAttributesOutsideOfClassDefinition() 0 4 1
1
<?php
2
/**
3
 * PHP: Nelson Martell Library file
4
 *
5
 * Content:
6
 * - Trait definition
7
 *
8
 * Copyright © 2016 Nelson Martell (http://nelson6e65.github.io)
9
 *
10
 * Licensed under The MIT License (MIT)
11
 * For full copyright and license information, please see the LICENSE
12
 * Redistributions of files must retain the above copyright notice.
13
 *
14
 * @copyright 2016 Nelson Martell
15
 * @link      http://nelson6e65.github.io/php_nml/
16
 * @since     v0.6.0
17
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License (MIT)
18
 * */
19
20
namespace NelsonMartell\Test\Helpers;
21
22
use Cake\Utility\Inflector;
23
use NelsonMartell\Extensions\String;
24
use NelsonMartell\IStrictPropertiesContainer;
25
use SebastianBergmann\Exporter\Exporter;
26
27
/**
28
 * Test helper for classes implementing ``NelsonMartell\IStrictPropertiesContainer`` interface and
29
 * ``NelsonMartell\PropertiesHandler`` trait.
30
 *
31
 * You can pass an empty in some providers to skip test. For example, if a class has not 'write-only' properties)
32
 *
33
 * @author Nelson Martell <[email protected]>
34
 * */
35
trait IStrictPropertiesContainerTester
36
{
37
    public abstract function readonlyPropertiesProvider();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
38
    public abstract function writeonlyPropertiesProvider();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
39
    public abstract function readwritePropertiesProvider();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
40
    public abstract function unaccesiblePropertiesProvider();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
41
    public abstract function objectInstanceProvider();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
42
43
    /**
44
     * Optional if provider returns an empty array.
45
     *
46
     * @dataProvider readonlyPropertiesProvider
47
     */
48
    public function testReadonlyPropertiesAreReadables(
49
        IStrictPropertiesContainer $obj = null,
50
        $property = null,
51
        $expected = null
52
    ) {
53
        if ($obj === null) {
54
            $this->markTestSkipped('Target class has not read-only properties.');
0 ignored issues
show
Bug introduced by
It seems like markTestSkipped() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
55
        }
56
57
        $exporter = new Exporter();
58
59
        $var = get_class($obj);
60
        $var = Inflector::variable(substr(
61
            $var,
62
            strrpos($var, '\\') === false ? 0 : strrpos($var, '\\') + 1
63
        ));
64
65
        $actual = $obj->$property;
66
67
        $message = String::format(
68
            '$actual = ${var}->{property}; // {actual}',
69
            [
70
                'var'      => $var,
71
                'property' => $property,
72
                'actual'   => $exporter->shortenedExport($actual)
73
            ]
74
        );
75
76
        $this->assertEquals($expected, $actual, $message);
0 ignored issues
show
Bug introduced by
It seems like assertEquals() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
77
    }
78
79
    /**
80
     * @depends testReadonlyPropertiesAreReadables
81
     * @dataProvider readonlyPropertiesProvider
82
     * @expectedException \BadMethodCallException
83
     */
84
    public function testReadonlyPropertiesAreNotWritables(
85
        IStrictPropertiesContainer $obj = null,
86
        $property = null,
87
        $value = null
88
    ) {
89
        $obj->$property = $value;
90
    }
91
92
    /**
93
     * @dataProvider writeonlyPropertiesProvider
94
     */
95
    public function testWriteonlyPropertiesAreWritables(
96
        IStrictPropertiesContainer $obj = null,
97
        $property = null,
98
        $value = null
99
    ) {
100
        if ($obj === null) {
101
            $this->markTestSkipped('Target class has not write-only properties to test.');
0 ignored issues
show
Bug introduced by
It seems like markTestSkipped() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
102
        }
103
104
        $obj->$property = $value;
105
    }
106
107
    /**
108
     * @depends testWriteonlyPropertiesAreWritables
109
     * @dataProvider writeonlyPropertiesProvider
110
     * @expectedException \BadMethodCallException
111
     */
112
    public function testWriteonlyPropertiesAreNotReadables(
113
        IStrictPropertiesContainer $obj = null,
114
        $property = null,
115
        $value = null
116
    ) {
117
        $obj->$property = $value;
118
        $actual = $obj->$property;
0 ignored issues
show
Unused Code introduced by
$actual is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
119
    }
120
121
    /**
122
     * @dataProvider readwritePropertiesProvider
123
     */
124
    public function testPropertiesWithFullAccessAreReadablesAndWritables(
125
        IStrictPropertiesContainer $obj = null,
126
        $property = null,
127
        $value = null,
128
        $expected = null
129
    ) {
130
        if ($obj === null) {
131
            $this->markTestSkipped('Target class has not read-write properties to test.');
0 ignored issues
show
Bug introduced by
It seems like markTestSkipped() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
132
        }
133
134
        $exporter = new Exporter();
135
136
        $var = get_class($obj);
137
        $var = Inflector::variable(substr(
138
            $var,
139
            strrpos($var, '\\') === false ? 0 : strrpos($var, '\\') + 1
140
        ));
141
142
        $obj->$property = $value;
143
144
        $actual = $obj->$property;
145
146
        $message = String::format(
147
            '${var}->{property} = {value}; $actual = ${var}->{property}; // {actual}',
148
            [
149
                'var'      => $var,
150
                'property' => $property,
151
                'actual'   => $exporter->shortenedExport($actual),
152
                'value'    => $exporter->shortenedExport($value),
153
            ]
154
        );
155
156
        $this->assertEquals($expected, $actual, $message);
0 ignored issues
show
Bug introduced by
It seems like assertEquals() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
157
    }
158
159
    /**
160
     * @dataProvider unaccesiblePropertiesProvider
161
     * @expectedException \BadMethodCallException
162
     */
163
    public function testUnaccessiblePropertiesThrowsCatchableError(
164
        IStrictPropertiesContainer $obj = null,
165
        $property = null,
166
        $value = null
167
    ) {
168
        if ($obj === null) {
169
            $this->markTestSkipped('Target class has not unaccesible properties to test.');
0 ignored issues
show
Bug introduced by
It seems like markTestSkipped() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
170
        }
171
172
        if ($value === null) {
173
            // Getter exception
174
            $actual = $obj->$property;
0 ignored issues
show
Unused Code introduced by
$actual is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
175
        } else {
176
            // Setter exception
177
            $obj->$property = $value;
178
        }
179
    }
180
181
    /**
182
     * @dataProvider objectInstanceProvider
183
     * @expectedException \BadMethodCallException
184
     */
185
    public function testIsUnableToCreateDirectAttributesOutsideOfClassDefinition(IStrictPropertiesContainer $obj)
186
    {
187
        $obj->thisPropertyNameIsMaybeImposibleThatExistsInClassToBeUsedAsNameOfPropertyOfAnyClassGiven = 'No way';
0 ignored issues
show
Bug introduced by
Accessing thisPropertyNameIsMaybeI...PropertyOfAnyClassGiven on the interface NelsonMartell\IStrictPropertiesContainer suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
188
    }
189
}
0 ignored issues
show
Coding Style introduced by
As per coding style, files should not end with a newline character.

This check marks files that end in a newline character, i.e. an empy line.

Loading history...
190