Completed
Push — master ( a00052...e176fb )
by Tim
06:25
created

Strg::equals()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/**
4
 * \AppserverIo\Lang\Strg
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2015 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/appserver-io/lang
18
 * @link      http://www.appserver.io
19
 */
20
21
namespace AppserverIo\Lang;
22
23
/**
24
 * This class implements functionality to handle
25
 * a string value as object.
26
 *
27
 * @author    Tim Wagner <[email protected]>
28
 * @copyright 2015 TechDivision GmbH <[email protected]>
29
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
30
 * @link      https://github.com/appserver-io/lang
31
 * @link      http://www.appserver.io
32
 */
33
class Strg extends Object implements \Serializable
34
{
35
36
    /**
37
     * Holds the chars the String contains of.
38
     *
39
     * @var array
40
     */
41
    protected $value;
42
43
    /**
44
     * The length of the String.
45
     *
46
     * @var integer
47
     */
48
    protected $length;
49
50
    /**
51
     * The cached hash of the String itself.
52
     *
53
     * @var integer
54
     */
55
    protected $hash;
56
57
    /**
58
     * Initializes a newly created <code>String</code> object so that it
59
     * represents the same sequence of characters as the argument; in other
60
     * words, the newly created string is a copy of the argument string.
61
     * Unless
62
     * an explicit copy of <code>value</code> is needed, use of this
63
     * constructor is unnecessary since Strings are immutable.
64
     *
65
     * @param mixed $value Holds the value to initialize the String instance with
66
     */
67
    public function __construct($value = null)
68
    {
69
        // initialize property default values here, as declarative default values may break thread safety,
70
        // when utilizing static and non-static access on class methods within same thread context!
71
        $this->value = '';
0 ignored issues
show
Documentation Bug introduced by
It seems like '' of type string is incompatible with the declared type array of property $value.

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...
72
        $this->length = 0;
73
        $this->hash = 0;
74
75
        $this->init($value);
76
    }
77
78
    /**
79
     * Initializes the string and returns the
80
     * instance.
81
     *
82
     * @param mixed $value The value to initialize the instance with
83
     *
84
     * @return \AppserverIo\Lang\Strg The initialized instance
85
     */
86
    protected function init($value)
87
    {
88
        // check if a value was passed
89
        if (! is_null($value)) {
90
            // if yes, cast and set it
91
            $this->value = (string) $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like (string) $value of type string is incompatible with the declared type array of property $value.

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...
92
            $this->length = strlen($this->value);
93
        }
94
        // return the instance
95
        return $this;
96
    }
97
98
    /**
99
     * Initializes a new String instance with the passed value
100
     * and returns it.
101
     *
102
     * @param mixed $value The value to initialize the String with
103
     *
104
     * @return \AppserverIo\Lang\Strg The initialized String instance
105
     */
106
    public static function valueOf($value)
107
    {
108
        return new Strg($value);
109
    }
110
111
    /**
112
     * This method returns the class name as
113
     * a string.
114
     *
115
     * @return string
116
     */
117
    public static function __getClass()
118
    {
119
        return __CLASS__;
120
    }
121
122
    /**
123
     * A copy of this object is returned.
124
     *
125
     * @return \AppserverIo\Lang\Strg A copy of the String itself.
126
     */
127
    public function toString()
128
    {
129
        return new Strg($this->stringValue());
130
    }
131
132
    /**
133
     * This returns the string value of
134
     * the String itself.
135
     *
136
     * @return string Returns the string value of itself
137
     */
138
    public function __toString()
139
    {
140
        return $this->stringValue();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->stringValue(); (array) is incompatible with the return type of the parent method AppserverIo\Lang\Object::__toString of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
141
    }
142
143
    /**
144
     * Returns a new String, containing the concatenated value
145
     * of the this string with the passed one.
146
     *
147
     * @param \AppserverIo\Lang\Strg $string The String to concatenate
148
     *
149
     * @return \AppserverIo\Lang\Strg The concatenated String
150
     */
151
    public function concat(Strg $string)
152
    {
153
        return new Strg($this->stringValue() . $string->stringValue());
154
    }
155
156
    /**
157
     * Returns the length of this string.
158
     * The length is equal to the number of 16-bit
159
     * Unicode characters in the string.
160
     *
161
     * @return integer The length of the sequence of characters represented by this object.
162
     */
163
    public function length()
164
    {
165
        return $this->length;
166
    }
167
168
    /**
169
     * Returns the value as string.
170
     *
171
     * @return string The string value represented by this object
172
     */
173
    public function stringValue()
174
    {
175
        return $this->value;
176
    }
177
178
    /**
179
     * Returns a new string resulting from replacing all occurrences of
180
     * <code>oldChar</code> in this string with <code>newChar</code>.
181
     * <p>
182
     * If the character <code>oldChar</code> does not occur in the
183
     * character sequence represented by this <code>String</code> object,
184
     * then a reference to this <code>String</code> object is returned.
185
     * Otherwise, a new <code>String</code> object is created that
186
     * represents a character sequence identical to the character sequence
187
     * represented by this <code>String</code> object, except that every
188
     * occurrence of <code>oldChar</code> is replaced by an occurrence
189
     * of <code>newChar</code>.
190
     * <p>
191
     * Examples:
192
     * <blockquote><pre>
193
     * "mesquite in your cellar".replace('e', 'o')
194
     * returns "mosquito in your collar"
195
     * "the war of baronets".replace('r', 'y')
196
     * returns "the way of bayonets"
197
     * "sparring with a purple porpoise".replace('p', 't')
198
     * returns "starring with a turtle tortoise"
199
     * "JonL".replace('q', 'x') returns "JonL" (no change)
200
     * </pre></blockquote>
201
     *
202
     * @param string $oldChar The old character
203
     * @param string $newChar The new character
204
     *
205
     * @return \AppserverIo\Lang\Strg A string derived from this string by replacing every occurrence of <code>oldChar</code> with <code>newChar</code>
206
     */
207
    public function replace($oldChar, $newChar)
208
    {
209
        return new Strg(str_replace($oldChar, $newChar, $this->stringValue()));
210
    }
211
212
    /**
213
     * Returns true if the passed value is equal.
214
     *
215
     * @param \AppserverIo\Lang\Object $val The value to check
216
     *
217
     * @return boolean
218
     */
219
    public function equals(Object $val)
220
    {
221
        return $this->stringValue() == $val->stringValue();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class AppserverIo\Lang\Object as the method stringValue() does only exist in the following sub-classes of AppserverIo\Lang\Object: AppserverIo\Lang\Strg. 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...
222
    }
223
224
    /**
225
     * Returns a new String that is a substring of this String.
226
     * The
227
     * substring begins at the specified <code>beginIndex</code> and
228
     * extends to the character at index <code>endIndex - 1</code>.
229
     * Thus the length of the substring is <code>endIndex-beginIndex</code>.
230
     * <p>
231
     * Examples:
232
     * <blockquote><pre>
233
     * $hamburger = new String("hamburger");
234
     * $hamburger->substring(4, 8) returns "urge"
235
     * </pre></blockquote>
236
     *
237
     * @param integer $beginIndex The beginning index, inclusive
238
     * @param integer $endIndex   The ending index, exclusive
239
     *
240
     * @return \AppserverIo\Lang\Strg The specified substring
241
     * @exception \AppserverIo\Lang\StrgIndexOutOfBoundsException if the <code>beginIndex</code> is negative, or <code>endIndex</code> is larger than the length of this <code>String</code> object, or <code>beginIndex</code> is larger than <code>endIndex</code>.
242
     */
243
    public function substring($beginIndex, $endIndex)
244
    {
245
        if ($beginIndex < 0) {
246
            StrgIndexOutOfBoundsException::forIndex($beginIndex);
247
        }
248
        if ($endIndex > $this->length()) {
249
            StrgIndexOutOfBoundsException::forIndex($endIndex);
250
        }
251
        if ($beginIndex > $endIndex) {
252
            StrgIndexOutOfBoundsException::forIndex($endIndex - $beginIndex);
253
        }
254
        if (($beginIndex == 0) && ($endIndex == $this->length())) {
255
            return $this;
256
        }
257
        $value = substr($this->stringValue(), $beginIndex, $endIndex);
258
        return new Strg($value);
259
    }
260
261
    /**
262
     * Returns a hash code for this string.
263
     * The hash code for a
264
     * <code>String</code> object is computed as
265
     * <blockquote><pre>
266
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
267
     * </pre></blockquote>
268
     * using <code>int</code> arithmetic, where <code>s[i]</code> is the
269
     * <i>i</i>th character of the string, <code>n</code> is the length of
270
     * the string, and <code>^</code> indicates exponentiation.
271
     * (The hash value of the empty string is zero.)
272
     *
273
     * @return string A hash code value for this object.
274
     */
275
    public function hashCode()
276
    {
277
        $h = $this->hash;
278
        if ($h == 0) {
279
            $off = 0;
280
            $len = $this->length();
281
            for ($i = 0; $i < $len; $i ++) {
282
                $h = 31 * $h + $off ++;
283
            }
284
            $this->hash = $h;
285
        }
286
        return $h;
287
    }
288
289
    /**
290
     * Tells whether or not this string matches the
291
     * given regular expression.
292
     *
293
     * @param string $regex The regular expression to which this string is to be matched
294
     *
295
     * @return boolean TRUE if, and only if, this string matches the given regular expression
296
     */
297
    public function matches($regex)
298
    {
299
        $isExisting = false;
300
        if (ereg($regex, $this->stringValue()) != false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing ereg($regex, $this->stringValue()) of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
301
            $isExisting = true;
302
        }
303
        return $isExisting;
304
    }
305
306
    /**
307
     * This method has to be called to serialize the String.
308
     *
309
     * @return string Returns a serialized version of the String
310
     * @see \Serializable::serialize()
311
     */
312
    public function serialize()
313
    {
314
        return serialize(get_object_vars($this));
315
    }
316
317
    /**
318
     * This method unserializes the passed string and initializes the String
319
     * itself with the data.
320
     *
321
     * @param string $data Holds the data of the instance as serialized string
322
     *
323
     * @return void
324
     * @see \Serializable::unserialize($data)
325
     */
326
    public function unserialize($data)
327
    {
328
        foreach (unserialize($data) as $propertyName => $propertyValue) {
329
            $this->$propertyName = $propertyValue;
330
        }
331
    }
332
333
    /**
334
     * Splits this string around matches of the given regular expression.
335
     *
336
     * The array returned by this method contains each substring of this
337
     * string that is terminated by another substring that matches the
338
     * given expression or is terminated by the end of the string. The
339
     * substrings in the array are in the order in which they occur in
340
     * this string. If the expression does not match any part of the
341
     * input then the resulting array has just one element, namely this
342
     * string.
343
     *
344
     * The limit parameter controls the number of times the pattern is
345
     * applied and therefore affects the length of the resulting array.
346
     * If the limit n is greater than zero then the pattern will be applied
347
     * at most n - 1 times, the array's length will be no greater than n,
348
     * and the array's last entry will contain all input beyond the last
349
     * matched delimiter. If n is non-positive then the pattern will be
350
     * applied as many times as possible and the array can have any length.
351
     * If n is zero then the pattern will be applied as many times as
352
     * possible, the array can have any length, and trailing empty strings
353
     * will be discarded.
354
     *
355
     * The string "boo:and:foo", for example, yields the following results
356
     * with these parameters:
357
     *
358
     * Regex Limit Result
359
     * : 2 { "boo", "and:foo" }
360
     * : 5 { "boo", "and", "foo" }
361
     * : -2 { "boo", "and", "foo" }
362
     * o 5 { "b", "", ":and:f", "", "" }
363
     * o -2 { "b", "", ":and:f", "", "" }
364
     * o 0 { "b", "", ":and:f" }
365
     *
366
     * An invocation of this method of the form str.split(regex, n) yields the
367
     * same result as the expression.
368
     *
369
     * @param string  $regex The delimiting regular expression
370
     * @param integer $limit The result threshold, as described above
371
     *
372
     * @return array The array of strings computed by splitting this string around matches of the given regular expression
373
     */
374
    public function split($regex, $limit = -1)
375
    {
376
        // split the internal value into it's parts
377
        return preg_split($regex, $this->stringValue(), $limit, PREG_SPLIT_NO_EMPTY);
378
    }
379
380
    /**
381
     * Returns a copy of the string, with leading and trailing whitespace
382
     * omitted.
383
     * <p>
384
     * If this <code>String</code> object represents an empty character
385
     * sequence, or the first and last characters of character sequence
386
     * represented by this <code>String</code> object both have codes
387
     * greater than <code>'&#92;u0020'</code> (the space character), then a
388
     * reference to this <code>String</code> object is returned.
389
     * <p>
390
     * Otherwise, if there is no character with a code greater than
391
     * <code>'&#92;u0020'</code> in the string, then a new
392
     * <code>String</code> object representing an empty string is created
393
     * and returned.
394
     * <p>
395
     * Otherwise, let <i>k</i> be the index of the first character in the
396
     * string whose code is greater than <code>'&#92;u0020'</code>, and let
397
     * <i>m</i> be the index of the last character in the string whose code
398
     * is greater than <code>'&#92;u0020'</code>. A new <code>String</code>
399
     * object is created, representing the substring of this string that
400
     * begins with the character at index <i>k</i> and ends with the
401
     * character at index <i>m</i>-that is, the result of
402
     * <code>this.substring(<i>k</i>,&nbsp;<i>m</i>+1)</code>.
403
     * <p>
404
     * This method may be used to trim
405
     * {@link Character#isSpace(char) whitespace} from the beginning and end
406
     * of a string; in fact, it trims all ASCII control characters as well.
407
     *
408
     * @return \AppserverIo\Lang\Strg A reference of this string with leading and trailing white space removed, or this string if it has no leading or trailing white space.
409
     */
410
    public function trim()
411
    {
412
        return $this->init(trim($this->stringValue()));
413
    }
414
415
    /**
416
     * md5 encryptes the string and returns the
417
     * instance.
418
     *
419
     * @return \AppserverIo\Lang\Strg The instance md5 encrypted
420
     */
421
    public function md5()
422
    {
423
        return $this->init(md5($this->stringValue()));
424
    }
425
426
    /**
427
     * Converts the string value to upper case
428
     * and returns the instance.
429
     *
430
     * @return \AppserverIo\Lang\Strg The instance
431
     */
432
    public function toUpperCase()
433
    {
434
        return $this->init(strtoupper($this->stringValue()));
435
    }
436
437
    /**
438
     * Converts the string value to lower case
439
     * and returns the instance.
440
     *
441
     * @return \AppserverIo\Lang\Strg The instance
442
     */
443
    public function toLowerCase()
444
    {
445
        return $this->init(strtolower($this->stringValue()));
446
    }
447
}
448