PageRenderTime 43ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/app/protected/extensions/zurmoinc/framework/utils/RedBeanDatabaseBuilderUtil.php

https://bitbucket.org/zurmo/zurmo/
PHP | 459 lines | 376 code | 14 blank | 69 comment | 31 complexity | 4b7f4f6c69c68a86b2239ee71515945f MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, GPL-2.0, LGPL-3.0, LGPL-2.1, BSD-2-Clause
  1. <?php
  2. /*********************************************************************************
  3. * Zurmo is a customer relationship management program developed by
  4. * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
  5. *
  6. * Zurmo is free software; you can redistribute it and/or modify it under
  7. * the terms of the GNU General Public License version 3 as published by the
  8. * Free Software Foundation with the addition of the following permission added
  9. * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  10. * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
  11. * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  12. *
  13. * Zurmo is distributed in the hope that it will be useful, but WITHOUT
  14. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  15. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  16. * details.
  17. *
  18. * You should have received a copy of the GNU General Public License along with
  19. * this program; if not, see http://www.gnu.org/licenses or write to the Free
  20. * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  21. * 02110-1301 USA.
  22. *
  23. * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
  24. * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
  25. ********************************************************************************/
  26. /**
  27. * The purpose of this class is to drill through RedBeanModels,
  28. * populating them with made up data and saving them, in order
  29. * to build the database schema for freezing.
  30. */
  31. class RedBeanDatabaseBuilderUtil
  32. {
  33. protected static $modelClassNamesToSampleModels;
  34. protected static $uniqueStrings = array();
  35. /**
  36. * Auto building of models (and therefore the database) involves...
  37. *
  38. * - Creating each model.
  39. * - Setting its members to made up values that conform to the rules specified for the member.
  40. * - Setting or adding to its relations while avoiding making new objects of the same types
  41. * as have already been made.
  42. * - Saving it so that the tables and columns are created.
  43. * - Deleting it so that it doesn't leave rows behind.
  44. * - (The database is now ready for freezing.)
  45. *
  46. * The aim of the auto build is to populate models in a 'valid enough way' to save them
  47. * such that RedBean creates the tables and columns it needs with the right column types.
  48. * This means it does not necessarily make models that are valid, for example it will set
  49. * a model's parent to itself if model and the parent are of the same type. These kinds of
  50. * inconsistencies do not matter for the purpose of auto building the database, and are
  51. * semantic information that is not available and not needed for this process. The idea is
  52. * to create as few models as possible.
  53. *
  54. * Call this an empty unfrozen database with all the models required for certain tests, or
  55. * all the models required for the production database. Then freeze the database.
  56. *
  57. * If a model references a non-leaf model in the hierarchy an example of a model subclassed
  58. * from that type must be included in the $modelClassNames. eg: 'Opportunity' references
  59. * Permitable via its permissions and an abstract 'Permitable' cannot be created, so 'User'
  60. * needs be created at the same time since it is concrete and can be used to create an
  61. * Opportunity.
  62. * ie: $modelClassNames = array('Opportunity', 'User').
  63. */
  64. public static function autoBuildModels(array $modelClassNames, & $messageLogger)
  65. {
  66. assert('AssertUtil::all($modelClassNames, "is_string")');
  67. assert('$messageLogger instanceof MessageLogger');
  68. self::$modelClassNamesToSampleModels = array();
  69. self::$uniqueStrings = array();
  70. foreach ($modelClassNames as $modelClassName)
  71. {
  72. $messages[] = array('info' => "Auto building $modelClassName.");
  73. self::autoBuildSampleModel($modelClassName, $modelClassNames, $messageLogger);
  74. $messageLogger->addInfoMessage("Auto build of $modelClassName done.");
  75. }
  76. foreach (self::$modelClassNamesToSampleModels as $modelClassName => $model)
  77. {
  78. if (!$model instanceof OwnedModel && !$model instanceof OwnedCustomField)
  79. {
  80. try
  81. {
  82. if (!$model->save())
  83. {
  84. $messageLogger->addErrorMessage("*** Saving the sample $modelClassName failed.");
  85. $errors = $model->getErrors();
  86. if (count($errors) > 0)
  87. {
  88. $messageLogger->addErrorMessage('The attributes that did not validate probably need more rules, or are not deletable types.');
  89. $messageLogger->addErrorMessage(print_r($errors, true));
  90. }
  91. else
  92. {
  93. $messageLogger->addErrorMessage('No attributes failed to validate!');
  94. }
  95. }
  96. $messageLogger->addInfoMessage("Auto built $modelClassName saved.");
  97. }
  98. catch (NotSupportedException $e)
  99. {
  100. $messageLogger->addErrorMessage("*** Saving the sample $modelClassName failed.");
  101. if (is_subclass_of($modelClassName, 'OwnedCustomField') ||
  102. is_subclass_of($modelClassName, 'OwnedModel'))
  103. {
  104. $messageLogger->addErrorMessage('It is OWNED and was probably not saved via its owner, making it not a root model.');
  105. }
  106. else
  107. {
  108. $messageLogger->addErrorMessage('The save failed but there were no validation errors.');
  109. }
  110. }
  111. }
  112. }
  113. foreach (self::$modelClassNamesToSampleModels as $modelClassName => $model)
  114. {
  115. try
  116. {
  117. if (!$model->isDeleted())
  118. {
  119. $messageLogger->addInfoMessage(get_class($model) . " Deleted (Not Owned).");
  120. $model->delete();
  121. }
  122. else
  123. {
  124. $messageLogger->addInfoMessage(get_class($model) . " Deleted Already (Owned).");
  125. }
  126. AuditEvent::deleteAllByModel($model);
  127. unset(self::$modelClassNamesToSampleModels[$modelClassName]);
  128. }
  129. catch (NotSupportedException $e)
  130. {
  131. $messageLogger->addErrorMessage("*** Deleting the sample $modelClassName failed. It is marked not deletable.");
  132. }
  133. }
  134. if (count(self::$modelClassNamesToSampleModels))
  135. {
  136. $messageLogger->addErrorMessage('*** Deleting of the sample(s) ' . join(', ', array_keys(self::$modelClassNamesToSampleModels)) . " didn't happen.");
  137. }
  138. }
  139. public static function autoBuildSampleModel($modelClassName, array $modelClassNames, & $messageLogger)
  140. {
  141. assert('$messageLogger instanceof MessageLogger');
  142. if (!empty(self::$modelClassNamesToSampleModels[$modelClassName]))
  143. {
  144. return self::$modelClassNamesToSampleModels[$modelClassName];
  145. }
  146. $messageLogger->addInfoMessage("$modelClassName Being Created.");
  147. $model = new $modelClassName();
  148. self::$modelClassNamesToSampleModels[$modelClassName] = $model;
  149. $metadata = $model->getMetadata();
  150. foreach ($metadata as $unused => $classMetadata)
  151. {
  152. if (!empty($classMetadata['members']))
  153. {
  154. foreach ($classMetadata['members'] as $memberName)
  155. {
  156. if (!$model->isAttributeReadOnly($memberName))
  157. {
  158. $messageLogger->addInfoMessage("Setting $modelClassName->$memberName.");
  159. self::setMadeUpMemberValue($model, $memberName);
  160. }
  161. }
  162. }
  163. }
  164. foreach ($metadata as $unused => $classMetadata)
  165. {
  166. if (!empty($classMetadata['relations']))
  167. {
  168. foreach ($classMetadata['relations'] as $relationName => $relationTypeModelClassNameAndOwns)
  169. {
  170. //Always use the current user to ensure the model can later be saved and removed.
  171. if ($relationName == 'owner' && $model instanceof OwnedSecurableItem)
  172. {
  173. $model->owner = Yii::app()->user->userModel;
  174. }
  175. elseif (!$model->isAttributeReadOnly($relationName))
  176. {
  177. $messageLogger->addInfoMessage("Setting $modelClassName->$relationName.");
  178. $relationType = $relationTypeModelClassNameAndOwns[0];
  179. $relatedModelClassName = $relationTypeModelClassNameAndOwns[1];
  180. $owned = isset($relationTypeModelClassNameAndOwns[2]) &&
  181. $relationTypeModelClassNameAndOwns[2] == RedBeanModel::OWNED;
  182. $message = $relationType == RedBeanModel::HAS_ONE_BELONGS_TO ? "HAS_ONE_BELONGS_TO" :
  183. ($relationType == RedBeanModel::HAS_MANY_BELONGS_TO ? "HAS_MANY_BELONGS_TO" :
  184. ($relationType == RedBeanModel::HAS_ONE ? "HAS_ONE" :
  185. ($relationType == RedBeanModel::HAS_MANY ? "HAS_MANY" :
  186. ($relationType == RedBeanModel::MANY_MANY ? "MANY_MANY" : '????'))));
  187. $messageLogger->addInfoMessage($message);
  188. if ($relationType == RedBeanModel::HAS_ONE &&
  189. $model->$relationName->id < 0 &&
  190. $relatedModelClassName == $modelClassName &&
  191. !$owned)
  192. {
  193. $messageLogger->addInfoMessage($relatedModelClassName);
  194. $messageLogger->addInfoMessage('(Set self)');
  195. $model->$relationName = $model;
  196. }
  197. else
  198. {
  199. $relatedModel = null;
  200. if ($relatedModelClassName::isTypeDeletable() || $owned)
  201. {
  202. $messageLogger->addInfoMessage($relatedModelClassName);
  203. $relatedModel = self::autoBuildSampleModel($relatedModelClassName, $modelClassNames, $messageLogger);
  204. }
  205. else
  206. {
  207. foreach ($modelClassNames as $otherModelClassName)
  208. {
  209. if (is_subclass_of($otherModelClassName, $relatedModelClassName) &&
  210. $otherModelClassName::isTypeDeletable())
  211. {
  212. $messageLogger->addInfoMessage("$relatedModelClassName (subst)");
  213. $relatedModel = self::autoBuildSampleModel($otherModelClassName, $modelClassNames, $messageLogger);
  214. break;
  215. }
  216. }
  217. }
  218. if (isset($relatedModel))
  219. {
  220. if (in_array($relationType, array(RedBeanModel::HAS_ONE_BELONGS_TO,
  221. RedBeanModel::HAS_MANY_BELONGS_TO,
  222. RedBeanModel::HAS_ONE)))
  223. {
  224. $messageLogger->addInfoMessage('(Set)');
  225. $model->$relationName = $relatedModel;
  226. }
  227. elseif ($model->$relationName->count() == 0)
  228. {
  229. assert('in_array($relationType, array(RedBeanModel::HAS_MANY,
  230. RedBeanModel::MANY_MANY))');
  231. $messageLogger->addInfoMessage('(Added)');
  232. $model->$relationName->add($relatedModel);
  233. }
  234. }
  235. }
  236. }
  237. }
  238. }
  239. }
  240. return $model;
  241. }
  242. protected static function setMadeUpMemberValue($model, $memberName)
  243. {
  244. $memberSet = false;
  245. $minValue = null;
  246. $maxValue = null;
  247. $minLength = 1;
  248. $maxLength = null;
  249. $unique = false;
  250. $ignoreDefaultValue = false;
  251. foreach ($model->getValidators($memberName) as $validator)
  252. {
  253. switch (get_class($validator))
  254. {
  255. case 'CBooleanValidator':
  256. $model->$memberName = 1;
  257. $memberSet = true;
  258. break;
  259. case 'RedBeanModelDateTimeDefaultValueValidator':
  260. break;
  261. case 'CDefaultValueValidator':
  262. case 'RedBeanModelDefaultValueValidator':
  263. if ($validator->value === null || $validator->value === '')
  264. {
  265. throw new NotSupportedException();
  266. }
  267. $model->$memberName = $validator->value;
  268. $memberSet = true;
  269. break;
  270. case 'CEmailValidator':
  271. $model->$memberName = 'someone@somewhere.net';
  272. $memberSet = true;
  273. break;
  274. case 'CInlineValidator':
  275. break;
  276. case 'RedBeanModelNumberValidator':
  277. if ($validator->min !== null)
  278. {
  279. $minValue = $validator->min;
  280. }
  281. if ($validator->max !== null)
  282. {
  283. $maxValue = $validator->max;
  284. }
  285. break;
  286. case 'CStringValidator':
  287. if ($validator->min !== null)
  288. {
  289. $minLength = $validator->min;
  290. }
  291. if ($validator->max !== null)
  292. {
  293. $maxLength = $validator->max;
  294. }
  295. break;
  296. case 'RedBeanModelUniqueValidator':
  297. $unique = true;
  298. break;
  299. case 'CUrlValidator':
  300. $model->$memberName = 'http://www.example.com';
  301. $memberSet = true;
  302. break;
  303. case 'CRegularExpressionValidator':
  304. case 'CRequiredValidator':
  305. case 'CSafeValidator':
  306. case 'CUnsafeValidator':
  307. case 'RedBeanModelCompareDateTimeValidator':
  308. case 'RedBeanModelRequiredValidator':
  309. case 'UsernameLengthValidator':
  310. case 'validateTimeZone':
  311. break;
  312. case 'RedBeanModelTypeValidator':
  313. case 'TypeValidator':
  314. if ($validator->type == 'float' || $validator->type == 'integer' || $validator->type == 'string')
  315. {
  316. //A number or string default value could be set in the rules, but we should ignore this and try
  317. //to make the largest sized number possible for this column.
  318. $ignoreDefaultValue = true;
  319. }
  320. break;
  321. case 'CCaptchaValidator':
  322. case 'CCompareValidator':
  323. case 'CDateValidator':
  324. case 'CExistValidator':
  325. case 'CFileValidator':
  326. case 'CFilterValidator':
  327. case 'CNumberValidator':
  328. case 'CRangeValidator':
  329. case 'CUniqueValidator':
  330. default:
  331. // This just means that supported needs to be
  332. // added for a validator that has been used,
  333. // not that it can't or shouldn't be added.
  334. echo get_class($validator) . "\n";
  335. throw new NotSupportedException();
  336. }
  337. if ($validator instanceof CStringValidator)
  338. {
  339. }
  340. }
  341. if (!$memberSet || $ignoreDefaultValue)
  342. {
  343. foreach ($model->getValidators($memberName) as $validator)
  344. {
  345. if ($validator instanceof TypeValidator)
  346. {
  347. switch ($validator->type)
  348. {
  349. case 'integer':
  350. $i = 2147483647;
  351. if ($minValue !== null)
  352. {
  353. $i = max($i, $minValue);
  354. }
  355. if ($maxValue !== null)
  356. {
  357. $i = min($i, $maxValue);
  358. }
  359. $model->$memberName = $i;
  360. break;
  361. case 'float':
  362. $f = 3.14;
  363. if ($minValue !== null)
  364. {
  365. $f = max($f, $minValue);
  366. }
  367. if ($maxValue !== null)
  368. {
  369. $f = min($f, $maxValue);
  370. }
  371. $model->$memberName = $f;
  372. break;
  373. case 'date':
  374. $model->$memberName = '2000-01-01';
  375. break;
  376. case 'time':
  377. $model->$memberName = '12:00';
  378. break;
  379. case 'datetime':
  380. $model->$memberName = '2000-01-01 12:00:00';
  381. break;
  382. case 'array';
  383. throw new NotSupportedException();
  384. case 'string':
  385. default:
  386. // Makes a string like 'Diald' respecting
  387. // the minimum and maximum length rules
  388. // and if that does not validate guesses
  389. // that it should be all lowercase or all
  390. // uppercase. If it still doesn't validate,
  391. // well, we'll see...
  392. $s = self::getUniqueString($minLength, $maxLength, $unique);
  393. $model->$memberName = $s;
  394. if (!$model->validate(array($memberName)))
  395. {
  396. $model->$memberName = strtolower($model->$memberName);
  397. if (!$model->validate(array($memberName)))
  398. {
  399. $model->$memberName = strtoupper($model->$memberName);
  400. if (!$model->validate(array($memberName)))
  401. {
  402. $model->$memberName = $s;
  403. }
  404. }
  405. }
  406. }
  407. }
  408. }
  409. }
  410. }
  411. protected static function getUniqueString($minLength, $maxLength, $unique)
  412. {
  413. if ($maxLength == null)
  414. {
  415. $maxLength = 1024;
  416. }
  417. do
  418. {
  419. $s = chr(rand(ord('A'), ord('Z')));
  420. $length = max($minLength, $maxLength);
  421. if ($maxLength !== null)
  422. {
  423. $length = min($length, $maxLength);
  424. }
  425. while (strlen($s) < $length)
  426. {
  427. $s .= chr(rand(ord('a'), ord('z')));
  428. }
  429. } while ($unique && in_array($s, self::$uniqueStrings));
  430. $uniqueStrings[] = $s;
  431. return $s;
  432. }
  433. }
  434. ?>