Skip to content

pepellou/PHP-Curiosities

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 

Repository files navigation

PHP curiosities

This project is intended for gathering all those PHP strange behaviours I keep discovering from time to time, and share them with you. They're covered as unit tests to show them in code, but I'll try to keep this README updated to explain in detail every curiosity. I hope some of them will be useful to you.

Incrementing chars/strings

I discovered this weird thing while trying to do a loop like this:

for ($ch = 'a'; $ch <= 'z'; $ch++) {
	// do something with $ch
}

I expected that loop to run 26 times, with $ch taking values from 'a' to 'z', both included. Well, that's not what happens. Instead it runs 676 times, with $ch taking the following values:

a b c d e f g h i j k l m n o p q r s t u v w x y z aa ab ac ad ae af ag ah ai aj ak al am an ao ap aq ar as at au av aw ax ay az ba bb bc bd be bf bg bh bi bj bk bl bm bn bo bp bq br bs bt bu bv bw bx by bz ca cb cc cd ce cf cg ch ci cj ck cl cm cn co cp cq cr cs ct cu cv cw cx cy cz da db dc dd de df dg dh di dj dk dl dm dn do dp dq dr ds dt du dv dw dx dy dz ea eb ec ed ee ef eg eh ei ej ek el em en eo ep eq er es et eu ev ew ex ey ez fa fb fc fd fe ff fg fh fi fj fk fl fm fn fo fp fq fr fs ft fu fv fw fx fy fz ga gb gc gd ge gf gg gh gi gj gk gl gm gn go gp gq gr gs gt gu gv gw gx gy gz ha hb hc hd he hf hg hh hi hj hk hl hm hn ho hp hq hr hs ht hu hv hw hx hy hz ia ib ic id ie if ig ih ii ij ik il im in io ip iq ir is it iu iv iw ix iy iz ja jb jc jd je jf jg jh ji jj jk jl jm jn jo jp jq jr js jt ju jv jw jx jy jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko kp kq kr ks kt ku kv kw kx ky kz la lb lc ld le lf lg lh li lj lk ll lm ln lo lp lq lr ls lt lu lv lw lx ly lz ma mb mc md me mf mg mh mi mj mk ml mm mn mo mp mq mr ms mt mu mv mw mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or os ot ou ov ow ox oy oz pa pb pc pd pe pf pg ph pi pj pk pl pm pn po pp pq pr ps pt pu pv pw px py pz qa qb qc qd qe qf qg qh qi qj qk ql qm qn qo qp qq qr qs qt qu qv qw qx qy qz ra rb rc rd re rf rg rh ri rj rk rl rm rn ro rp rq rr rs rt ru rv rw rx ry rz sa sb sc sd se sf sg sh si sj sk sl sm sn so sp sq sr ss st su sv sw sx sy sz ta tb tc td te tf tg th ti tj tk tl tm tn to tp tq tr ts tt tu tv tw tx ty tz ua ub uc ud ue uf ug uh ui uj uk ul um un uo up uq ur us ut uu uv uw ux uy uz va vb vc vd ve vf vg vh vi vj vk vl vm vn vo vp vq vr vs vt vu vv vw vx vy vz wa wb wc wd we wf wg wh wi wj wk wl wm wn wo wp wq wr ws wt wu wv ww wx wy wz xa xb xc xd xe xf xg xh xi xj xk xl xm xn xo xp xq xr xs xt xu xv xw xx xy xz ya yb yc yd ye yf yg yh yi yj yk yl ym yn yo yp yq yr ys yt yu yv yw yx yy yz

What happens is that incrementing 'z' results in 'aa' and so incrementing a string leads to a somehow convenient chain of ordered char combinations. So by having this knowledge we discover we are failing in our loop condition, since because of how strings are compared it turns out that 'aa' <= 'z' and the loop continues until the value 'za', which in fact is not less or equal than 'z'.

To fix our loop we have to do weird things such as:

for ($ch = 'a'; $ch != 'aa'; $ch++) {
	// do something with $ch
}

or

for ($ch = 'a'; strlen($ch) == 1; $ch++) {
	// do something with $ch
}

We proved that weird behaviour with the following tests:

public function test_onStringIncrements(
) {
	$aChar = "y";

	$aChar++;
	$this->assertEquals("z", $aChar);

	$aChar++;
	$this->assertEquals("aa", $aChar);
}

public function test_onCharsIncrements(
) {
	$aChar = 'y';

	$aChar++;
	$this->assertEquals('z', $aChar);

	$aChar++;
	$this->assertEquals('aa', $aChar);
}

Some chars you write as single chars aren't really single chars

The following code was intended to generate random tokens of length 40 chars:

public static function nextRandomToken(
) {
	$valid_chars = array(
		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
		'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
		'U', 'V', 'W', 'X', 'Y', 'Z',
		'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
		'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
		'u', 'v', 'w', 'x', 'y', 'z',
		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
		'%', '-', '.', ',', '\'', '/', '$', '·', '@', '='
	);
	$result = "";
	for ($p = 0; $p < 40; $p++) {
		$result = $result . $valid_chars[rand(0, count($valid_chars) - 1)];
	}
	return $result;
}

The method is fairly simple but it turns out that it randomly breaks a test that checks generated tokens have length 40. Why? Because the literal '·' hasn't length 1 but 2!

Of course this is an encoding issue we don't realize when writing a literal in our code. The obvious behaviour would be that if your write a literal containing N chars its length was N. But as shown this is not true if you write a literal containing some special chars, like '·'.

We proved this behaviour with the following test:

public function test_someCharsArentReallySingleChars(
) {
	$this->assertEquals(2, strlen('·'));
	$this->assertEquals(2, strlen('¬'));
	$this->assertEquals(2, strlen('ñ'));
	$this->assertEquals(2, strlen('Ñ'));
	$this->assertEquals(2, strlen('ç'));
	$this->assertEquals(2, strlen('º'));
	$this->assertEquals(2, strlen('ª'));
	$this->assertEquals(3, strlen('€'));
}

About

Just to gather some PHP curiosities

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages