sfModelGenerator.class.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. * Model generator.
  11. *
  12. * @package symfony
  13. * @subpackage generator
  14. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  15. * @version SVN: $Id: sfModelGenerator.class.php 15622 2009-02-19 10:27:58Z fabien $
  16. */
  17. abstract class sfModelGenerator extends sfGenerator
  18. {
  19. protected
  20. $configuration = null,
  21. $primaryKey = array(),
  22. $modelClass = '',
  23. $params = array(),
  24. $config = array(),
  25. $formObject = null;
  26. /**
  27. * Generates classes and templates in cache.
  28. *
  29. * @param array $params The parameters
  30. *
  31. * @return string The data to put in configuration cache
  32. */
  33. public function generate($params = array())
  34. {
  35. $this->validateParameters($params);
  36. $this->modelClass = $this->params['model_class'];
  37. // generated module name
  38. $this->setModuleName($this->params['moduleName']);
  39. $this->setGeneratedModuleName('auto'.ucfirst($this->params['moduleName']));
  40. // theme exists?
  41. $theme = isset($this->params['theme']) ? $this->params['theme'] : 'default';
  42. $this->setTheme($theme);
  43. $themeDir = $this->generatorManager->getConfiguration()->getGeneratorTemplate($this->getGeneratorClass(), $theme, '');
  44. if (!is_dir($themeDir))
  45. {
  46. throw new sfConfigurationException(sprintf('The theme "%s" does not exist.', $theme));
  47. }
  48. // configure the model
  49. $this->configure();
  50. $this->configuration = $this->loadConfiguration();
  51. // generate files
  52. $this->generatePhpFiles($this->generatedModuleName, sfFinder::type('file')->relative()->in($themeDir));
  53. // move helper file
  54. if (file_exists($file = $this->generatorManager->getBasePath().'/'.$this->getGeneratedModuleName().'/lib/helper.php'))
  55. {
  56. @rename($file, $this->generatorManager->getBasePath().'/'.$this->getGeneratedModuleName().'/lib/Base'.ucfirst($this->moduleName).'GeneratorHelper.class.php');
  57. }
  58. return "require_once(sfConfig::get('sf_module_cache_dir').'/".$this->generatedModuleName."/actions/actions.class.php');";
  59. }
  60. /**
  61. * Gets the class name for current model.
  62. *
  63. * @return string
  64. */
  65. public function getModelClass()
  66. {
  67. return $this->modelClass;
  68. }
  69. /**
  70. * Gets the primary key name.
  71. *
  72. * @param Boolean $firstOne Whether to return the first PK or not
  73. *
  74. * @return array An array of primary keys
  75. */
  76. public function getPrimaryKeys($firstOne = false)
  77. {
  78. return $firstOne ? $this->primaryKey[0] : $this->primaryKey;
  79. }
  80. /**
  81. * Gets the singular name for current model.
  82. *
  83. * @return string
  84. */
  85. public function getSingularName()
  86. {
  87. return isset($this->params['singular']) ? $this->params['singular'] : sfInflector::underscore($this->getModelClass());
  88. }
  89. /**
  90. * Gets the plural name for current model.
  91. *
  92. * @return string
  93. */
  94. public function getPluralName()
  95. {
  96. return isset($this->params['plural']) ? $this->params['plural'] : $this->getSingularName().'_list';
  97. }
  98. /**
  99. * Gets the i18n catalogue to use for user strings.
  100. *
  101. * @return string The i18n catalogue
  102. */
  103. public function getI18nCatalogue()
  104. {
  105. return isset($this->params['i18n_catalogue']) ? $this->params['i18n_catalogue'] : 'messages';
  106. }
  107. /**
  108. * Returns PHP code for primary keys parameters.
  109. *
  110. * @param integer $indent The indentation value
  111. * @param string $callee The function to call
  112. *
  113. * @return string The PHP code
  114. */
  115. public function getRetrieveByPkParamsForAction($indent)
  116. {
  117. $params = array();
  118. foreach ($this->getPrimaryKeys() as $pk)
  119. {
  120. $params[] = sprintf("\$request->getParameter('%s')", sfInflector::underscore($pk));
  121. }
  122. return implode(",\n".str_repeat(' ', max(0, $indent - strlen($this->getSingularName().$this->modelClass))), $params);
  123. }
  124. /**
  125. * Returns PHP code to add to a URL for primary keys.
  126. *
  127. * @param string $prefix The prefix value
  128. *
  129. * @return string PHP code
  130. */
  131. public function getPrimaryKeyUrlParams($prefix = '', $full = false)
  132. {
  133. $params = array();
  134. foreach ($this->getPrimaryKeys() as $pk)
  135. {
  136. $fieldName = sfInflector::underscore($pk);
  137. if ($full)
  138. {
  139. $params[] = sprintf("%s='.%s->%s()", $fieldName, $prefix, $this->getColumnGetter($fieldName, false));
  140. }
  141. else
  142. {
  143. $params[] = sprintf("%s='.%s", $fieldName, $this->getColumnGetter($fieldName, true, $prefix));
  144. }
  145. }
  146. return implode(".'&", $params);
  147. }
  148. /**
  149. * Configures this generator.
  150. */
  151. abstract protected function configure();
  152. abstract public function getType($column);
  153. abstract public function getAllFieldNames();
  154. /**
  155. * Returns the getter either non-developped: 'getFoo' or developped: '$class->getFoo()'.
  156. *
  157. * This method is ORM dependant.
  158. *
  159. * @param string $column The column name
  160. * @param boolean $developed true if you want developped method names, false otherwise
  161. * @param string $prefix The prefix value
  162. *
  163. * @return string PHP code
  164. */
  165. abstract public function getColumnGetter($column, $developed = false , $prefix = '');
  166. abstract public function getManyToManyTables();
  167. /**
  168. * Returns HTML code for an action link.
  169. *
  170. * @param string $actionName The action name
  171. * @param array $params The parameters
  172. * @param boolean $pk_link Whether to add a primary key link or not
  173. *
  174. * @return string HTML code
  175. */
  176. public function getLinkToAction($actionName, $params, $pk_link = false)
  177. {
  178. $action = isset($params['action']) ? $params['action'] : 'List'.sfInflector::camelize($actionName);
  179. $url_params = $pk_link ? '?'.$this->getPrimaryKeyUrlParams() : '\'';
  180. return '[?php echo link_to(__(\''.$params['label'].'\'), \''.$this->getModuleName().'/'.$action.$url_params.', '.$this->asPhp($params['params']).', \''.$this->getI18nCatalogue().'\') ?]';
  181. }
  182. /**
  183. * Wraps content with a credential condition.
  184. *
  185. * @param string $content The content
  186. * @param array $params The parameters
  187. *
  188. * @return string HTML code
  189. */
  190. public function addCredentialCondition($content, $params = array())
  191. {
  192. if (isset($params['credentials']))
  193. {
  194. $credentials = $this->asPhp($params['credentials']);
  195. return <<<EOF
  196. [?php if (\$sf_user->hasCredential($credentials)): ?]
  197. $content
  198. [?php endif; ?]
  199. EOF;
  200. }
  201. else
  202. {
  203. return $content;
  204. }
  205. }
  206. /**
  207. * Returns HTML code for a field.
  208. *
  209. * @param sfModelGeneratorConfigurationField $field The field
  210. *
  211. * @return string HTML code
  212. */
  213. public function renderField($field)
  214. {
  215. $html = $this->getColumnGetter($field->getName(), true);
  216. if ($renderer = $field->getRenderer())
  217. {
  218. $html = sprintf("$html ? call_user_func_array(%s, array_merge(array(%s), %s)) : '&nbsp;'", $this->asPhp($renderer), $html, $this->asPhp($field->getRendererArguments()));
  219. }
  220. else if ($field->isComponent())
  221. {
  222. return sprintf("get_component('%s', '%s', array('type' => 'list', '%s' => \$%s))", $this->getModuleName(), $field->getName(), $this->getSingularName(), $this->getSingularName());
  223. }
  224. else if ($field->isPartial())
  225. {
  226. return sprintf("get_partial('%s', array('type' => 'list', '%s' => \$%s))", $field->getName(), $this->getSingularName(), $this->getSingularName());
  227. }
  228. else if ('Date' == $field->getType())
  229. {
  230. $html = sprintf("$html ? format_date(%s, \"%s\") : '&nbsp;'", $html, $field->getConfig('date_format', 'f'));
  231. }
  232. else if ('Boolean' == $field->getType())
  233. {
  234. $html = sprintf("get_partial('%s/list_field_boolean', array('value' => %s))", $this->getModuleName(), $html);
  235. }
  236. if ($field->isLink())
  237. {
  238. $html = sprintf("link_to(%s, '%s', \$%s)", $html, $this->getUrlForAction('edit'), $this->getSingularName());
  239. }
  240. return $html;
  241. }
  242. /**
  243. * Wraps a content for I18N.
  244. *
  245. * @param string $key The configuration key name
  246. *
  247. * @return string HTML code
  248. */
  249. public function getI18NString($key)
  250. {
  251. $value = $this->configuration->getValue($key, '', true);
  252. $parts = explode('.', $key);
  253. $context = $parts[0];
  254. // find %%xx%% strings
  255. preg_match_all('/%%([^%]+)%%/', $value, $matches, PREG_PATTERN_ORDER);
  256. $fields = array();
  257. foreach ($matches[1] as $name)
  258. {
  259. $fields[] = $name;
  260. }
  261. $vars = array();
  262. foreach ($this->configuration->getContextConfiguration($context, $fields) as $field)
  263. {
  264. $vars[] = '\'%%'.$field->getName().'%%\' => '.$this->renderField($field);
  265. }
  266. return sprintf("__('%s', array(%s), '%s')", $value, implode(', ', $vars), $this->getI18nCatalogue());
  267. }
  268. /**
  269. * Gets the form object
  270. *
  271. * @return sfForm
  272. */
  273. public function getFormObject()
  274. {
  275. if (is_null($this->formObject))
  276. {
  277. $class = is_null($this->configuration) ? $this->getModelClass().'Form' : $this->configuration->getFormClass();
  278. $this->formObject = new $class();
  279. }
  280. return $this->formObject;
  281. }
  282. /**
  283. * Gets the HTML to add to the form tag if the form is multipart.
  284. *
  285. * @return string
  286. */
  287. public function getFormMultipartHtml()
  288. {
  289. if (isset($this->params['non_verbose_templates']) && $this->params['non_verbose_templates'])
  290. {
  291. return '[?php $form->isMultipart() and print \' enctype="multipart/form-data"\' ?]';
  292. }
  293. else
  294. {
  295. return $this->getFormObject()->isMultipart() ? ' enctype="multipart/form-data"' : '';
  296. }
  297. }
  298. /**
  299. * Validates the basic structure of the parameters.
  300. *
  301. * @param array $params An array of parameters
  302. */
  303. protected function validateParameters($params)
  304. {
  305. foreach (array('model_class', 'moduleName') as $key)
  306. {
  307. if (!isset($params[$key]))
  308. {
  309. throw new sfParseException(sprintf('sfModelGenerator must have a "%s" parameter.', $key));
  310. }
  311. }
  312. if (!class_exists($params['model_class']))
  313. {
  314. throw new sfInitializationException(sprintf('Unable to generate a module for non-existent model "%s".', $params['model_class']));
  315. }
  316. $this->config = isset($params['config']) ? $params['config'] : array();
  317. unset($params['config']);
  318. $this->params = $params;
  319. }
  320. /**
  321. * Loads the configuration for this generated module.
  322. */
  323. protected function loadConfiguration()
  324. {
  325. try
  326. {
  327. $this->generatorManager->getConfiguration()->getGeneratorTemplate($this->getGeneratorClass(), $this->getTheme(), '../parts/configuration.php');
  328. }
  329. catch (sfException $e)
  330. {
  331. return null;
  332. }
  333. $config = $this->getGeneratorManager()->getConfiguration();
  334. if (!$config instanceof sfApplicationConfiguration)
  335. {
  336. throw new LogicException('The sfModelGenerator can only operates with an application configuration.');
  337. }
  338. $basePath = $this->getGeneratedModuleName().'/lib/Base'.ucfirst($this->getModuleName()).'GeneratorConfiguration.class.php';
  339. $this->getGeneratorManager()->save($basePath, $this->evalTemplate('../parts/configuration.php'));
  340. require_once $this->getGeneratorManager()->getBasePath().'/'.$basePath;
  341. $class = 'Base'.ucfirst($this->getModuleName()).'GeneratorConfiguration';
  342. foreach ($config->getLibDirs($this->getModuleName()) as $dir)
  343. {
  344. if (!is_file($configuration = $dir.'/'.$this->getModuleName().'GeneratorConfiguration.class.php'))
  345. {
  346. continue;
  347. }
  348. require_once $configuration;
  349. $class = $this->getModuleName().'GeneratorConfiguration';
  350. break;
  351. }
  352. // validate configuration
  353. foreach ($this->config as $context => $value)
  354. {
  355. if (!$value)
  356. {
  357. continue;
  358. }
  359. throw new InvalidArgumentException(sprintf('Your generator configuration contains some errors for the "%s" context. The following configuration cannot be parsed: %s.', $context, $this->asPhp($value)));
  360. }
  361. return new $class();
  362. }
  363. /**
  364. * Returns the URL for a given action.
  365. *
  366. * @return string The URL related to a given action
  367. */
  368. public function getUrlForAction($action)
  369. {
  370. if (isset($this->params['route_prefix']))
  371. {
  372. return 'list' == $action ? $this->params['route_prefix'] : $this->params['route_prefix'].'_'.$action;
  373. }
  374. else
  375. {
  376. return $this->getModuleName().'/'.$action;
  377. }
  378. }
  379. public function asPhp($variable)
  380. {
  381. return str_replace(array("\n", 'array ('), array('', 'array('), var_export($variable, true));
  382. }
  383. public function escapeString($string)
  384. {
  385. return str_replace("'", "\\'", $string);
  386. }
  387. }