/cubi/modules/common/lib/Pivot.php

http://openbiz-cubi.googlecode.com/ · PHP · 531 lines · 460 code · 37 blank · 34 comment · 65 complexity · 3d2563aa2b85deec11ec6752844cfdbd MD5 · raw file

  1. <?php
  2. /*
  3. * By Gonzalo Ayuso
  4. * Apache License 2.0
  5. * http://code.google.com/p/gam-pivot/
  6. */
  7. class Pivot
  8. {
  9. /**
  10. * @param struct $recorset
  11. * @return Pivot
  12. */
  13. static function factory($recorset)
  14. {
  15. return new self($recorset);
  16. }
  17. protected function __construct($recorset)
  18. {
  19. $this->_recordset = $recorset;
  20. }
  21. private $_typeMark = false;
  22. /**
  23. * @param boolean $type
  24. * @return Pivot
  25. */
  26. public function typeMark($type = true)
  27. {
  28. $this->_typeMark = $type;
  29. return $this;
  30. }
  31. private $_pivotOn = null;
  32. /**
  33. * @param array $conf
  34. * @return Pivot
  35. */
  36. public function pivotOn($conf)
  37. {
  38. $this->_pivotOn = $conf;
  39. return $this;
  40. }
  41. private $_pivotTotal = false;
  42. /**
  43. * @param boolean $bool
  44. * @return Pivot
  45. */
  46. public function pivotTotal($bool=true)
  47. {
  48. $this->_pivotTotal = $bool;
  49. return $this;
  50. }
  51. private $_lineTotal = false;
  52. /**
  53. * @param boolean $bool
  54. * @return Pivot
  55. */
  56. public function lineTotal($bool=true)
  57. {
  58. $this->_lineTotal = $bool;
  59. return $this;
  60. }
  61. private $_fullTotal = false;
  62. /**
  63. * @param boolean $bool
  64. * @return Pivot
  65. */
  66. public function fullTotal($bool=true)
  67. {
  68. $this->_fullTotal = $bool;
  69. return $this;
  70. }
  71. private $_column = null;
  72. private $_columnValues = null;
  73. /**
  74. * @param array $column
  75. * @param array $columnValues
  76. * @return Pivot
  77. */
  78. public function addColumn($column, $columnValues)
  79. {
  80. $this->_column = $column;
  81. $this->_columnValues = $columnValues;
  82. return $this;
  83. }
  84. const TOTAL = "TOT";
  85. private $_splits = array();
  86. public function fetch($fetchType=null)
  87. {
  88. $tmp = $tmpCount = array();
  89. $split = @$this->_column[0];
  90. $column = @$this->_column[1];
  91. foreach ($this->_recordset as $reg) {
  92. switch (count($this->_pivotOn)) {
  93. case 1:
  94. $k0 = $this->_getColumnItem($reg, 0);
  95. foreach ($this->_columnValues as $item) {
  96. if ($item instanceof Pivot_Callback) {
  97. break;
  98. } elseif ($item instanceof Pivot_Count) {
  99. @$tmpCount[$k0][$reg[$split]][$reg[$column]][$item->getKey()]++;
  100. }
  101. @$tmp[$k0][$reg[$split]][$reg[$column]][$item] += $reg[$item];
  102. @$this->_splits[$reg[$split]][$reg[$column]][$item] = $item;
  103. }
  104. break;
  105. case 2:
  106. $k0 = $this->_getColumnItem($reg, 0);
  107. $k1 = $this->_getColumnItem($reg, 1);
  108. foreach ($this->_columnValues as $item) {
  109. if ($item instanceof Pivot_Callback) {
  110. break;
  111. } elseif ($item instanceof Pivot_Count) {
  112. @$tmpCount[$k0][$k1][$reg[$split]][$reg[$column]][$item->getKey()] ++;
  113. }
  114. @$tmp[$k0][$k1][$reg[$split]][$reg[$column]][$item] += $reg[$item];
  115. @$this->_splits[$reg[$split]][$reg[$column]][$item] = $item;
  116. }
  117. break;
  118. case 3:
  119. $k0 = $this->_getColumnItem($reg, 0);
  120. $k1 = $this->_getColumnItem($reg, 1);
  121. $k2 = $this->_getColumnItem($reg, 2);
  122. foreach ($this->_columnValues as $item) {
  123. if ($item instanceof Pivot_Callback) {
  124. break;
  125. } elseif ($item instanceof Pivot_Count) {
  126. $tmpCount[$k0][$k1][$k2][$reg[$split]][$reg[$column]][$item->getKey()] ++;
  127. }
  128. @$tmp[$k0][$k1][$k2][$reg[$split]][$reg[$column]][$item] += $reg[$item];
  129. $this->_splits[$reg[$split]][$reg[$column]][$item] = $item;
  130. }
  131. break;
  132. }
  133. }
  134. return $this->_buildOutput($tmp, $fetchType, $tmpCount);
  135. }
  136. const TYPE_LINE = 0;
  137. const TYPE_PIVOT_TOTAL_LEVEL1 = 1;
  138. const TYPE_PIVOT_TOTAL_LEVEL2 = 2;
  139. const TYPE_FULL_TOTAL = 3;
  140. const _ID = '_id';
  141. private function _buildOutput($tmp, $fetchType, $tmpCount)
  142. {
  143. $out = array();
  144. $cont = 0;
  145. $fullTotal = array();
  146. switch (count($this->_pivotOn)) {
  147. case 1:
  148. foreach ($tmp as $p0 => $p0Values) {
  149. @$i++;
  150. $_out = $_lineTotal = array();
  151. $_out[self::_ID] = ++$cont;
  152. if ($this->_typeMark) {
  153. $_out['type'] = self::TYPE_LINE;
  154. }
  155. $_out[$this->_pivotOn[0]] = $p0;
  156. foreach (array_keys($this->_splits) as $split) {
  157. $cols = @$p0Values[$split];
  158. foreach (array_keys($this->_splits[$split]) as $col) {
  159. $colValues = @$cols[$col];
  160. foreach ($this->_columnValues as $_k) {
  161. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  162. if ($_k instanceof Pivot_Count) {
  163. $value = @$tmpCount[$p0][$split][$col][$k];
  164. } elseif ($_k instanceof Pivot_Callback) {
  165. $value = $_k->cbk($colValues);
  166. } else {
  167. $value = $colValues[$k];
  168. }
  169. $_out["{$split}_{$col}_{$k}"] = $value;
  170. if ($this->_lineTotal) {
  171. @$_lineTotal[$k] += $value;
  172. }
  173. if ($this->_fullTotal) {
  174. @$fullTotal[$split][$col][$k] += $value;
  175. }
  176. }
  177. }
  178. }
  179. if ($this->_lineTotal) {
  180. foreach ($this->_columnValues as $_k) {
  181. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  182. $value = ($_k instanceof Pivot_Callback) ?
  183. $_k->cbk($_lineTotal) : $_lineTotal[$k];
  184. $_out[self::TOTAL . "_{$k}"] = $value;
  185. }
  186. }
  187. $out[] = $_out;
  188. }
  189. break;
  190. case 2:
  191. foreach ($tmp as $p0 => $p0Values) {
  192. $p0Total = array();
  193. foreach ($p0Values as $p1 => $p1Values) {
  194. $_out = $_lineTotal = array();
  195. $_out[self::_ID] = ++$cont;
  196. if ($this->_typeMark) {
  197. $_out['type'] = self::TYPE_LINE;
  198. }
  199. $_out[$this->_pivotOn[0]] = $p0;
  200. $_out[$this->_pivotOn[1]] = $p1;
  201. foreach (array_keys($this->_splits) as $split) {
  202. $cols = $p1Values[$split];
  203. foreach (array_keys($this->_splits[$split]) as $col) {
  204. @$colValues = $cols[$col];
  205. foreach ($this->_columnValues as $_k) {
  206. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  207. if ($_k instanceof Pivot_Count) {
  208. $value = @$tmpCount[$p0][$p1][$split][$col][$k];
  209. } elseif ($_k instanceof Pivot_Callback) {
  210. $value = $_k->cbk($colValues);
  211. } else {
  212. $value = $colValues[$k];
  213. }
  214. $_out["{$split}_{$col}_{$k}"] = $value;
  215. if ($this->_lineTotal) {
  216. @$_lineTotal[$k] += $value;
  217. }
  218. if ($this->_pivotTotal) {
  219. @$p0Total[$split][$col][$k] += $value;
  220. }
  221. if ($this->_fullTotal) {
  222. @$fullTotal[$split][$col][$k] += $value;
  223. }
  224. }
  225. }
  226. }
  227. if ($this->_lineTotal) {
  228. foreach ($this->_columnValues as $_k) {
  229. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  230. $value = ($_k instanceof Pivot_Callback) ?
  231. $_k->cbk($_lineTotal) : $_lineTotal[$k];
  232. $_out[self::TOTAL . "_{$k}"] = $value;
  233. }
  234. }
  235. $out[] = $_out;
  236. }
  237. if ($this->_pivotTotal) {
  238. $_out = $_lineTotal = array();
  239. $_out[self::_ID] = ++$cont;
  240. if ($this->_typeMark) {
  241. $_out['type'] = self::TYPE_PIVOT_TOTAL_LEVEL1;
  242. }
  243. $i = 0;
  244. foreach ($this->_pivotOn as $pivotOn) {
  245. if ($i == 0) {
  246. $_out[$pivotOn] = self::TOTAL . "({$this->_pivotOn[0]})";
  247. } else {
  248. $_out[$pivotOn] = null;
  249. }
  250. $i++;
  251. }
  252. foreach ($p0Total as $split => $values) {
  253. foreach ($values as $col => $colValues) {
  254. foreach ($this->_columnValues as $_k) {
  255. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  256. $value = ($_k instanceof Pivot_Callback) ?
  257. $_k->cbk($p0Total[$split][$col]) : $p0Total[$split][$col][$k];
  258. $_out["{$split}_{$col}_{$k}"] = $value;
  259. @$_lineTotal[$k] += $value;
  260. }
  261. }
  262. }
  263. if ($this->_lineTotal) {
  264. foreach ($this->_columnValues as $_k) {
  265. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  266. $value = ($_k instanceof Pivot_Callback) ?
  267. $_k->cbk($_lineTotal) : $_lineTotal[$k];
  268. $_out[self::TOTAL . "_{$k}"] = $_lineTotal[$k];
  269. }
  270. }
  271. $out[] = $_out;
  272. }
  273. }
  274. break;
  275. case 3:
  276. foreach ($tmp as $p0 => $p0Values) {
  277. $p0Total = array();
  278. foreach ($p0Values as $p1 => $p1Values) {
  279. foreach ($p1Values as $p2 => $p2Values) {
  280. $_out = $_lineTotal = array();
  281. $_out[self::_ID] = ++$cont;
  282. if ($this->_typeMark) {
  283. $_out['type'] = self::TYPE_LINE;
  284. }
  285. $_out[$this->_pivotOn[0]] = $p0;
  286. $_out[$this->_pivotOn[1]] = $p1;
  287. $_out[$this->_pivotOn[2]] = $p2;
  288. foreach (array_keys($this->_splits) as $split) {
  289. $cols = $p2Values[$split];
  290. foreach (array_keys($this->_splits[$split]) as $col) {
  291. $colValues = $cols[$col];
  292. foreach ($this->_columnValues as $_k) {
  293. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  294. if ($_k instanceof Pivot_Count) {
  295. $value = $tmpCount[$p0][$p1][$p2][$split][$col][$k];
  296. } elseif ($_k instanceof Pivot_Callback) {
  297. $value = $_k->cbk($colValues);
  298. } else {
  299. $value = $colValues[$k];
  300. }
  301. $_out["{$split}_{$col}_{$k}"] = $value;
  302. if ($this->_lineTotal) {
  303. $_lineTotal[$k] += $value;
  304. }
  305. if ($this->_pivotTotal) {
  306. $p0Total[$split][$col][$k] += $value;
  307. $p1Total[$split][$col][$k] += $value;
  308. }
  309. if ($this->_fullTotal) {
  310. $fullTotal[$split][$col][$k] += $value;
  311. }
  312. }
  313. }
  314. }
  315. if ($this->_lineTotal) {
  316. foreach ($this->_columnValues as $_k) {
  317. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  318. $value = ($_k instanceof Pivot_Callback) ?
  319. $_k->cbk($_lineTotal) : $_lineTotal[$k];
  320. $_out[self::TOTAL . "_{$k}"] = $value;
  321. }
  322. }
  323. $out[] = $_out;
  324. }
  325. }
  326. if ($this->_pivotTotal) {
  327. $_out = $_lineTotal = array();
  328. $_out[self::_ID] = ++$cont;
  329. if ($this->_typeMark) {
  330. $_out['type'] = self::TYPE_PIVOT_TOTAL_LEVEL2;
  331. }
  332. $i = 0;
  333. foreach ($this->_pivotOn as $pivotOn) {
  334. if ($i == 0) {
  335. $_out[$pivotOn] = self::TOTAL . "({$this->_pivotOn[0]})";
  336. } else {
  337. $_out[$pivotOn] = null;
  338. }
  339. $i++;
  340. }
  341. foreach ($p0Total as $split => $values) {
  342. foreach ($values as $col => $colValues) {
  343. foreach ($this->_columnValues as $_k) {
  344. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  345. $value = ($_k instanceof Pivot_Callback) ?
  346. $_k->cbk($p0Total[$split][$col]) : $p0Total[$split][$col][$k];
  347. $_out["{$split}_{$col}_{$k}"] = $value;
  348. $_lineTotal[$k] += $value;
  349. }
  350. }
  351. }
  352. if ($this->_lineTotal) {
  353. foreach ($this->_columnValues as $_k) {
  354. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  355. $value = ($_k instanceof Pivot_Callback) ?
  356. $_k->cbk($_lineTotal) : $_lineTotal[$k];
  357. $_out[self::TOTAL . "_{$k}"] = $value;
  358. }
  359. }
  360. $out[] = $_out;
  361. }
  362. if ($this->_pivotTotal) {
  363. $_out = $_lineTotal = array();
  364. $_out[self::_ID] = ++$cont;
  365. if ($this->_typeMark) {
  366. $_out['type'] = self::TYPE_PIVOT_TOTAL_LEVEL1;
  367. }
  368. $i = 0;
  369. foreach ($this->_pivotOn as $pivotOn) {
  370. if ($i == 0) {
  371. $_out[$pivotOn] = self::TOTAL . "({$this->_pivotOn[0]}, {$this->_pivotOn[1]})";
  372. } else {
  373. $_out[$pivotOn] = null;
  374. }
  375. $i++;
  376. }
  377. foreach ($p1Total as $split => $values) {
  378. foreach ($values as $col => $colValues) {
  379. foreach ($this->_columnValues as $_k) {
  380. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  381. $value = ($_k instanceof Pivot_Callback) ?
  382. $_k->cbk($p1Total[$split][$col]) : $p1Total[$split][$col][$k];
  383. $_out["{$split}_{$col}_{$k}"] = $value;
  384. $_lineTotal[$k] += $value;
  385. }
  386. }
  387. }
  388. if ($this->_lineTotal) {
  389. foreach ($this->_columnValues as $_k) {
  390. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  391. $value = ($_k instanceof Pivot_Callback) ?
  392. $_k->cbk($_lineTotal) : $_lineTotal[$k];
  393. $_out[self::TOTAL . "_{$k}"] = $value;
  394. }
  395. }
  396. $out[] = $_out;
  397. }
  398. }
  399. break;
  400. }
  401. if ($this->_fullTotal) {
  402. $_out = $_lineTotal = array();
  403. $_out[self::_ID] = ++$cont;
  404. if ($this->_typeMark) {
  405. $_out['type'] = self::TYPE_FULL_TOTAL;
  406. }
  407. $i = 0;
  408. foreach ($this->_pivotOn as $pivotOn) {
  409. if ($i == 0) {
  410. $_out[$pivotOn] = self::TOTAL;
  411. } else {
  412. $_out[$pivotOn] = null;
  413. }
  414. $i++;
  415. }
  416. foreach ($fullTotal as $split => $values) {
  417. foreach ($values as $col => $colValues) {
  418. foreach ($this->_columnValues as $_k) {
  419. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  420. $value = ($_k instanceof Pivot_Callback) ?
  421. $_k->cbk($fullTotal[$split][$col]) : $fullTotal[$split][$col][$k];
  422. $_out["{$split}_{$col}_{$k}"] = $value;
  423. @$_lineTotal[$k] += $value;
  424. }
  425. }
  426. }
  427. if ($this->_lineTotal) {
  428. foreach ($this->_columnValues as $_k) {
  429. $k = ($_k instanceof Pivot_Callback || $_k instanceof Pivot_Count) ? $_k->getKey() : $_k;
  430. $value = ($_k instanceof Pivot_Callback) ? $_k->cbk($_lineTotal) : $_lineTotal[$k];
  431. $_out[self::TOTAL . "_{$k}"] = $value;
  432. }
  433. }
  434. $out[] = $_out;
  435. }
  436. $return = array();
  437. if (count($out) > 0) {
  438. switch ($fetchType) {
  439. case self::FETCH_STRUCT:
  440. $return = array(
  441. 'splits' => $this->_splits,
  442. 'data' => array_map('array_values', $out),
  443. );
  444. break;
  445. default:
  446. $return = $out;
  447. }
  448. }
  449. return $return;
  450. }
  451. const FETCH_STRUCT = 1;
  452. private function _getColumnItem($reg, $key)
  453. {
  454. return $reg[$this->_pivotOn[$key]];
  455. }
  456. static function callback($key, $cbk)
  457. {
  458. return new Pivot_Callback($key, $cbk);
  459. }
  460. static function count($key)
  461. {
  462. return new Pivot_Count($key);
  463. }
  464. }
  465. class Pivot_Count
  466. {
  467. private $_key = null;
  468. function __construct($key)
  469. {
  470. $this->_key = $key;
  471. }
  472. public function getKey()
  473. {
  474. return $this->_key;
  475. }
  476. }
  477. class Pivot_Callback
  478. {
  479. private $_cbk = null;
  480. private $_key = null;
  481. function __construct($key, $cbk)
  482. {
  483. $this->_cbk = $cbk;
  484. $this->_key = $key;
  485. }
  486. public function getKey()
  487. {
  488. return $this->_key;
  489. }
  490. public function cbk()
  491. {
  492. return call_user_func_array($this->_cbk, func_get_args());
  493. }
  494. }