/protected/extensions/bootstrap/components/JSONStorage.php

https://bitbucket.org/mlsitsystems/yii.job.mlsit.ru · PHP · 386 lines · 200 code · 34 blank · 152 comment · 40 complexity · f0463b82b33f9c6a304605f7c2807242 MD5 · raw file

  1. <?php
  2. /**
  3. * JSONStorage.php
  4. *
  5. * Provides a very simple way to persistent JSON storage. Acts as a registry key saver.
  6. *
  7. * Example of use:
  8. *
  9. * $j = new JSONStorage();
  10. *
  11. * $j->addRegistry('custom');
  12. * echo $j->registryExists('custom');
  13. * $j->setData('mydata','super','custom');
  14. * $j->save();
  15. * $j->load();
  16. *
  17. * echo $j->getData('super','custom');
  18. *
  19. * @author: antonio ramirez <antonio@clevertech.biz>
  20. * Date: 8/2/12
  21. * Time: 6:48 PM
  22. */
  23. class JSONStorage extends CComponent
  24. {
  25. /**
  26. * const string the key to keep value information
  27. */
  28. const META = 'meta';
  29. /**
  30. * const string the key of the registry
  31. */
  32. const REGISTRY = 'registry';
  33. /**
  34. * @var string the filename to save the values to
  35. */
  36. protected $filename = 'registry.json';
  37. /**
  38. * @var string the full path to the directory with read/write access to save the registry to. If none set, the
  39. * component will used the application's runtime folder
  40. */
  41. protected $path;
  42. /**
  43. * @var bool whether the registry has changed or not
  44. */
  45. protected $dirty = false;
  46. /**
  47. * @var null|string the name of the default registry
  48. */
  49. protected $default = "default";
  50. /**
  51. * @var array the data of the registry
  52. */
  53. protected $data = array(
  54. self::META => array(
  55. "updated" => "",
  56. "hash" => ""
  57. ),
  58. self::REGISTRY => array(
  59. /* collection name */
  60. "default" => array(
  61. "foo" => "bar" /* attributes by example */
  62. )
  63. )
  64. );
  65. /**
  66. * class constructor
  67. * @param null $registry
  68. */
  69. public function __construct($registry = null)
  70. {
  71. if (null === $this->path)
  72. {
  73. $this->setPath(Yii::app()->getRuntimePath()); // JSON storage will be at the app runtime path
  74. }
  75. $this->setFile($this->filename);
  76. $this->load();
  77. // setup domain
  78. if ($registry)
  79. {
  80. if ($this->registryExists($registry) == false) $this->addRegistry($registry);
  81. $this->default = $registry;
  82. }
  83. }
  84. /**
  85. * class destructor
  86. */
  87. public function __destruct()
  88. {
  89. // flush data
  90. $this->flush();
  91. }
  92. /**
  93. * Fires before registry has been saved
  94. * @param $event
  95. */
  96. public function onBeforeSave($event)
  97. {
  98. $this->raiseEvent('onBeforeSave', $event);
  99. }
  100. /**
  101. * Fires after the registry has been saved
  102. * @param $event
  103. */
  104. public function onAfterSave($event)
  105. {
  106. $this->raiseEvent('onAfterSave', $event);
  107. }
  108. /**
  109. * Property set path
  110. * @param $path the full path of the directory with read/write access to save the registry file to
  111. * @return bool
  112. * @throws Exception
  113. */
  114. public function setPath($path)
  115. {
  116. if (is_dir($path) && is_writable($path))
  117. {
  118. $this->path = substr($path, -1) == DIRECTORY_SEPARATOR ? $path : $path . DIRECTORY_SEPARATOR;
  119. return true;
  120. }
  121. throw new Exception('"Path" must be a writable directory.');
  122. }
  123. /**
  124. * Property get path
  125. * @return string
  126. */
  127. public function getPath()
  128. {
  129. return $this->path;
  130. }
  131. /**
  132. * Property set file
  133. * @param $file the filename to save the registry to
  134. */
  135. public function setFile($file)
  136. {
  137. $this->filename = $this->path . $file;
  138. }
  139. /**
  140. * Property get file
  141. * @return string
  142. */
  143. public function getFile()
  144. {
  145. return $this->filename;
  146. }
  147. /**
  148. * Verifies data integrity
  149. * @return bool
  150. */
  151. public function verify()
  152. {
  153. $registry = function_exists('json_encode') ? json_encode($this->data[self::REGISTRY]) : CJSON::encode($this->data[self::REGISTRY]);
  154. return $this->data[self::META]["hash"] == md5($registry);
  155. }
  156. /**
  157. * Loads registry data into memory
  158. * @throws Exception
  159. */
  160. public function load()
  161. {
  162. // load data
  163. if (file_exists($this->getFile()))
  164. {
  165. $json = file_get_contents($this->getFile());
  166. if (strlen($json) == 0)
  167. {
  168. return;
  169. }
  170. $this->data = $this->decode($json);
  171. if ($this->data === null)
  172. {
  173. throw new Exception("Error while trying to decode \"$this->file\".");
  174. }
  175. if (!$this->verify())
  176. {
  177. throw new Exception($this->getFile() . ' failed checksum validation.');
  178. }
  179. }
  180. }
  181. /**
  182. * Saves registry data to the file
  183. */
  184. public function save()
  185. {
  186. if ($this->hasEventHandler('onBeforeSave'))
  187. $this->onBeforeSave(new CEvent($this));
  188. $this->flush();
  189. if ($this->hasEventHandler('onAfterSave'))
  190. $this->onAfterSave(new CEvent($this));
  191. }
  192. /**
  193. * Saves data to the registry
  194. * @param $key the name of the key that will hold the data
  195. * @param $data the data to save
  196. * @param null $registry the name of the registry
  197. * @return bool
  198. */
  199. public function setData($key, $data, $registry = null)
  200. {
  201. if ($registry == null) $registry = $this->default;
  202. if (is_string($key . $registry) && $this->registryExists($registry))
  203. {
  204. $this->data[self::REGISTRY][$registry][$key] = $data;
  205. $this->dirty = true;
  206. return true;
  207. }
  208. return false;
  209. }
  210. /**
  211. * Retrieves a data value from the registry
  212. * @param $key the name of the key that holds the data
  213. * @param null $registry the registry name
  214. * @return mixed the data in the key value, null otherwise
  215. */
  216. public function getData($key, $registry = null)
  217. {
  218. if ($registry == null)
  219. {
  220. $registry = $this->default;
  221. }
  222. if (is_string($key . $registry) && $this->registryExists($registry))
  223. {
  224. if (array_key_exists($key, $this->data[self::REGISTRY][$registry]))
  225. {
  226. return $this->data[self::REGISTRY][$registry][$key];
  227. }
  228. }
  229. return null;
  230. }
  231. /**
  232. * Removes data from a key in the registry
  233. * @param $key the key name that holds the data to remove
  234. * @param null $registry the registry name
  235. * @return bool true if successful, false otherwise
  236. */
  237. public function removeData($key, $registry = null)
  238. {
  239. if ($registry == null)
  240. {
  241. $registry = $this->default;
  242. }
  243. if (is_string($key . $registry) && $this->registryExists($registry))
  244. {
  245. if (array_key_exists($key, $this->data[self::REGISTRY][$registry]))
  246. {
  247. unset($this->data[self::REGISTRY][$registry][$key]);
  248. $this->dirty = true;
  249. return true;
  250. }
  251. }
  252. return false;
  253. }
  254. /**
  255. * Retrieves the number of keys in registry
  256. * @param null $registry the registry name
  257. * @return int the data length
  258. */
  259. public function getLength($registry = null)
  260. {
  261. if($registry == null)
  262. {
  263. $registry = $this->default;
  264. }
  265. if(is_string($registry) && $this->registryExists($registry))
  266. {
  267. return count($this->data[self::REGISTRY][$registry]);
  268. }
  269. return 0;
  270. }
  271. /**
  272. * Retrieves a registry collection based on its name
  273. * @param $registry the name of the registry to retrieve
  274. * @return mixed|null the registry, null if none found
  275. */
  276. public function getRegistry($registry)
  277. {
  278. return $this->registryExists($registry) ? $this->data[self::REGISTRY][$registry] : null;
  279. }
  280. /**
  281. * Checkes whether a collection exists (registry)
  282. * @param $registry the name of the registry to check existence
  283. * @return bool
  284. */
  285. public function registryExists($registry)
  286. {
  287. return array_key_exists($registry, $this->data[self::REGISTRY]);
  288. }
  289. /**
  290. * Add new collection name
  291. * @param $registry the name of the registry (collection) to create
  292. * @return bool
  293. */
  294. public function addRegistry($registry)
  295. {
  296. if ($this->registryExists($registry)) return false;
  297. $this->data[self::REGISTRY][$registry] = array();
  298. $this->dirty = true;
  299. }
  300. /**
  301. * Remove an existing collection and all associated data
  302. * @param $registry the name of the registry to remove
  303. */
  304. public function removeRegistry($registry)
  305. {
  306. if ($this->registryExists($registry))
  307. {
  308. unset($this->data[self::REGISTRY][$registry]);
  309. $this->dirty = true;
  310. return true;
  311. }
  312. return false;
  313. }
  314. /**
  315. * Saves the global registry to the file
  316. * @return bool
  317. * @throws Exception
  318. */
  319. private function flush()
  320. {
  321. // check if writeback is needed
  322. if ($this->dirty == false) return true;
  323. // prepare to writeback to file
  324. $data = $this->data;
  325. $registry = $this->encode($this->data[self::REGISTRY]);
  326. $data[self::META]["updated"] = date("c");
  327. $data[self::META]["hash"] = md5($registry);
  328. // overwrite existing data
  329. if (file_put_contents($this->getFile(), $this->encode($data)))
  330. {
  331. return true;
  332. } else throw new Exception(strtr('Unable to write back to {FILE}. Data will be lost!', array('{FILE}' => $this->getFile())));
  333. }
  334. /**
  335. * JSON encodes the data
  336. * @param $data
  337. * @return string
  338. */
  339. private function encode($data)
  340. {
  341. return function_exists('json_encode') ? json_encode($data) : CJSON::encode($data);
  342. }
  343. /**
  344. * JSON decodes the data
  345. * @param $data
  346. * @return mixed
  347. */
  348. private function decode($data)
  349. {
  350. return function_exists('json_decode') ? json_decode($data, true) : CJSON::decode($data, true);
  351. }
  352. }