Completed
Push — master ( 14d2bd...06e609 )
by mw
81:37 queued 59:24
created

SMWQuantityValue::initConversionData()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 15
c 1
b 0
f 0
nc 3
nop 0
dl 0
loc 23
ccs 15
cts 15
cp 1
crap 4
rs 8.7972
1
<?php
2
3
use SMW\ApplicationFactory;
4
use SMW\NumberFormatter;
5
use SMW\Message;
6
use SMW\DataValues\UnitConversionFetcher;
7
8
/**
9
 * @ingroup SMWDataValues
10
 */
11
12
/**
13
 * This datavalue implements unit support custom units, for which users have
14
 * provided linear conversion factors within the wiki. Those user settings
15
 * are retrieved from a property page associated with this object.
16
 *
17
 * @author Markus Krötzsch
18
 * @ingroup SMWDataValues
19
 */
20
class SMWQuantityValue extends SMWNumberValue {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
21
22
	/**
23
	 * Array with format (canonical unit ID string) => (conversion factor)
24
	 * @var float[]|bool
25
	 */
26
	protected $m_unitfactors = false;
27
28
	/**
29
	 * Array with format (normalised unit string) => (canonical unit ID string)
30
	 * @var string[]|bool
31
	 */
32
	protected $m_unitids = false;
33
34
	/**
35
	 * Ordered array of (normalized) units that should be displayed in tooltips, etc.
36
	 * @var string[]|bool
37
	 */
38
	protected $m_displayunits = false;
39
40
	/**
41
	 * Main unit in canonical form (recognised by the conversion factor 1)
42
	 * @var string|bool
43
	 */
44
	protected $m_mainunit = false;
45
46 20
	protected function convertToMainUnit( $number, $unit ) {
47 20
		$this->initConversionData();
48
49 20
		if ( array_key_exists( $unit, $this->m_unitids ) ) {
50 19
			$this->m_unitin = $this->m_unitids[$unit];
51 19
			assert( '$this->m_unitfactors[$this->m_unitin] != 0 /* Should be filtered by initConversionData() */' );
52 19
			$this->m_dataitem = new SMWDINumber( $number / $this->m_unitfactors[$this->m_unitin], $this->m_typeid );
0 ignored issues
show
Unused Code introduced by
The call to SMWDINumber::__construct() has too many arguments starting with $this->m_typeid.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
53 19
			return true;
54
		} else { // unsupported unit
55 7
			return false;
56
		}
57
	}
58
59 11
	protected function makeConversionValues() {
60 11
		if ( $this->m_unitvalues !== false ) {
61
			return; // do this only once
62
		}
63
64 11
		$this->m_unitvalues = array();
65
66 11
		if ( !$this->isValid() ) {
67
			return;
68
		}
69
70 11
		$this->initDisplayData();
71
72 11
		if ( count( $this->m_displayunits ) == 0 ) { // no display units, just show all
73 9
			foreach ( $this->m_unitfactors as $unit => $factor ) {
0 ignored issues
show
Bug introduced by
The expression $this->m_unitfactors of type array<integer,double>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
74 9
				if ( $unit !== '' ) { // filter out the empty fallback unit that is always there
75 9
					$this->m_unitvalues[$unit] = $this->m_dataitem->getNumber() * $factor;
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getNumber() does only exist in the following sub-classes of SMWDataItem: SMWDINumber. 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...
76
				}
77
			}
78
		} else {
79 3
			foreach ( $this->m_displayunits as $unit ) {
0 ignored issues
show
Bug introduced by
The expression $this->m_displayunits of type array<integer,string>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
80
				/// NOTE We keep non-ID units unless the input unit is used, so display units can be used to pick
81
				/// the preferred form of a unit. Doing this requires us to recompute the conversion values whenever
82
				/// the m_unitin changes.
83 3
				$unitkey = ( $this->m_unitids[$unit] == $this->m_unitin ) ? $this->m_unitids[$unit] : $unit;
84 3
				$this->m_unitvalues[$unitkey] = $this->m_dataitem->getNumber() * $this->m_unitfactors[$this->m_unitids[$unit]];
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getNumber() does only exist in the following sub-classes of SMWDataItem: SMWDINumber. 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...
85
			}
86
		}
87 11
	}
88
89 6
	protected function makeUserValue() {
90 6
		$printunit = false; // the normalised string of a known unit to use for printouts
91
92
		// Check if a known unit is given as outputformat:
93 6
		if ( ( $this->m_outformat ) && ( $this->m_outformat != '-' ) &&
94 6
		     ( $this->m_outformat != '-n' ) && ( $this->m_outformat != '-u' ) ) { // first try given output unit
95 2
			$wantedunit = $this->normalizeUnit( $this->m_outformat );
96 2
			if ( array_key_exists( $wantedunit, $this->m_unitids ) ) {
97 2
				$printunit = $wantedunit;
98
			}
99
		}
100
101
		// Alternatively, try to use the main display unit as a default:
102 6
		if ( $printunit === false ) {
103 6
			$this->initDisplayData();
104 6
			if ( count( $this->m_displayunits ) > 0 ) {
105 3
				$printunit = reset( $this->m_displayunits );
106
			}
107
		}
108
		// Finally, fall back to main unit:
109 6
		if ( $printunit === false ) {
110 4
			$printunit = $this->getUnit();
111
		}
112
113 6
		$asPrefix = isset( $this->prefixalUnitPreference[$printunit] ) && $this->prefixalUnitPreference[$printunit];
114
115 6
		$this->m_unitin = $this->m_unitids[$printunit];
116 6
		$this->m_unitvalues = false; // this array depends on m_unitin if displayunits were used, better invalidate it here
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type array of property $m_unitvalues.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
117
118 6
		$value = $this->m_dataitem->getNumber() * $this->m_unitfactors[$this->m_unitin];
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getNumber() does only exist in the following sub-classes of SMWDataItem: SMWDINumber. 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...
119
120 6
		$this->m_caption = '';
121
122 6
		if ( $this->m_outformat != '-u' ) { // -u is the format for displaying the unit only
123 6
			$this->m_caption .= ( ( $this->m_outformat != '-' ) && ( $this->m_outformat != '-n' ) ? $this->getLocalizedFormattedNumber( $value ) : $this->getNormalizedFormattedNumber( $value ) );
124
		}
125
126 6
		if ( ( $printunit !== '' ) && ( $this->m_outformat != '-n' ) ) { // -n is the format for displaying the number only
127
128 6
			$sep = '';
129
130 6
			if ( $this->m_outformat != '-u' ) {
131 6
				$sep =  ( $this->m_outformat != '-' ? '&#160;' : ' ' );
132
			}
133
134 6
			$this->m_caption = $asPrefix ? $printunit . $sep . $this->m_caption : $this->m_caption . $sep . $printunit;
135
		}
136 6
	}
137
138
	public function getUnitList() {
139
		$this->initConversionData();
140
		return array_keys( $this->m_unitfactors );
141
	}
142
143 5
	public function getUnit() {
144 5
		$this->initConversionData();
145 5
		return $this->m_mainunit;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->m_mainunit; of type string|boolean adds the type boolean to the return on line 145 which is incompatible with the return type of the parent method SMWNumberValue::getUnit of type string.
Loading history...
146
	}
147
148
/// The remaining functions are relatively "private" but are kept protected since
149
/// subclasses might exploit this to, e.g., "fake" conversion factors instead of
150
/// getting them from the database. A cheap way of making built-in types.
151
152
	/**
153
	 * This method initializes $m_unitfactors, $m_unitids, and $m_mainunit.
154
	 */
155 20
	protected function initConversionData() {
156 20
		if ( $this->m_unitids !== false ) {
157 12
			return; // do the below only once
158
		}
159
160 20
		$unitConversionFetcher = new UnitConversionFetcher( $this );
161 20
		$unitConversionFetcher->fetchCachedConversionData( $this->m_property );
162
163 20
		if ( $unitConversionFetcher->getErrors() !== array() ) {
164 7
			foreach ( $unitConversionFetcher->getErrors() as $error ) {
165 7
				$this->addErrorMsg(
166
					$error,
167 7
					Message::TEXT,
168 7
					Message::USER_LANGUAGE
169
				);
170
			}
171
		}
172
173 20
		$this->m_unitids = $unitConversionFetcher->getUnitIds();
174 20
		$this->m_unitfactors = $unitConversionFetcher->getUnitFactors();
175 20
		$this->m_mainunit = $unitConversionFetcher->getMainUnit();
176 20
		$this->prefixalUnitPreference = $unitConversionFetcher->getPrefixalUnitPreference();
177 20
	}
178
179
	/**
180
	 * This method initializes $m_displayunits.
181
	 */
182 12
	protected function initDisplayData() {
183 12
		if ( $this->m_displayunits !== false ) {
184 4
			return; // do the below only once
185
		}
186 12
		$this->initConversionData(); // needed to normalise unit strings
187 12
		$this->m_displayunits = array();
188
189 12
		if ( is_null( $this->m_property ) || is_null( $this->m_property->getDIWikiPage() ) ) {
190
			return;
191
		}
192
193 12
		$units = $this->getPropertySpecificationLookup()->getDisplayUnitsFor(
194 12
			$this->getProperty()
195
		);
196
197 12
		foreach ( $units as $unit ) {
198 4
			$unit = $this->normalizeUnit( $unit );
199 4
			if ( array_key_exists( $unit, $this->m_unitids ) ) {
200 4
				$this->m_displayunits[] = $unit; // do not avoid duplicates, users can handle this
201
			} // note: we ignore unsuppported units -- no way to display them
202
		}
203 12
	}
204
}
205