PageRenderTime 38ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/en/tutorials-and-examples/blog/part-two.rst

https://gitlab.com/albertkeba/docs
ReStructuredText | 726 lines | 579 code | 147 blank | 0 comment | 0 complexity | a7d1bb5008e84a483f552e7222e08f05 MD5 | raw file
  1. Blog Tutorial - Adding a layer
  2. ******************************
  3. Create a Post Model
  4. ===================
  5. The Model class is the bread and butter of CakePHP applications. By
  6. creating a CakePHP model that will interact with our database,
  7. we'll have the foundation in place needed to do our view, add,
  8. edit, and delete operations later.
  9. CakePHP's model class files go in ``/app/Model``, and the file
  10. we'll be creating will be saved to ``/app/Model/Post.php``. The
  11. completed file should look like this::
  12. class Post extends AppModel {
  13. }
  14. Naming conventions are very important in CakePHP. By naming our model
  15. Post, CakePHP can automatically infer that this model will be used
  16. in the PostsController, and will be tied to a database table called
  17. ``posts``.
  18. .. note::
  19. CakePHP will dynamically create a model object for you if it
  20. cannot find a corresponding file in /app/Model. This also means
  21. that if you accidentally name your file wrong (for example, post.php
  22. or posts.php instead of Post.php), CakePHP will not recognize
  23. any of your settings and will use the defaults instead.
  24. For more on models, such as table prefixes, callbacks, and
  25. validation, check out the :doc:`/models` chapter of the
  26. Manual.
  27. Create a Posts Controller
  28. =========================
  29. Next, we'll create a controller for our posts. The controller is
  30. where all the business logic for post interaction will happen. In a
  31. nutshell, it's the place where you play with the models and get
  32. post-related work done. We'll place this new controller in a file
  33. called ``PostsController.php`` inside the ``/app/Controller``
  34. directory. Here's what the basic controller should look like::
  35. class PostsController extends AppController {
  36. public $helpers = array('Html', 'Form');
  37. }
  38. Now, let's add an action to our controller. Actions often represent
  39. a single function or interface in an application. For example, when
  40. users request www.example.com/posts/index (which is the same
  41. as www.example.com/posts/), they might expect to see a listing of
  42. posts. The code for that action would look something like this::
  43. class PostsController extends AppController {
  44. public $helpers = array('Html', 'Form');
  45. public function index() {
  46. $this->set('posts', $this->Post->find('all'));
  47. }
  48. }
  49. By defining function ``index()``
  50. in our PostsController, users can access the logic there by
  51. requesting www.example.com/posts/index. Similarly, if we were to
  52. define a function called ``foobar()``, users would be able to
  53. access that at www.example.com/posts/foobar.
  54. .. warning::
  55. You may be tempted to name your controllers and actions a certain
  56. way to obtain a certain URL. Resist that temptation. Follow CakePHP
  57. conventions (capitalization, plural names, etc.) and create readable,
  58. understandable action names. You can map URLs to your code using
  59. "routes" covered later on.
  60. The single instruction in the action uses ``set()`` to pass data
  61. from the controller to the view (which we'll create next). The line
  62. sets the view variable called 'posts' equal to the return value of
  63. the ``find('all')`` method of the Post model. Our Post model is
  64. automatically available at ``$this->Post`` because we've followed
  65. CakePHP's naming conventions.
  66. To learn more about CakePHP's controllers, check out the
  67. :doc:`/controllers` chapter.
  68. Creating Post Views
  69. ===================
  70. Now that we have our data flowing to our model, and our application
  71. logic and flow defined by our controller, let's create a view for
  72. the index action we created above.
  73. CakePHP views are just presentation-flavored fragments that fit inside
  74. an application's layout. For most applications, they're HTML mixed
  75. with PHP, but they may end up as XML, CSV, or even binary data.
  76. A layout is presentation code that is wrapped around a view.
  77. Multiple layouts can be defined, and you can switch between
  78. them, but for now, let's just use the default.
  79. Remember how in the last section we assigned the 'posts' variable
  80. to the view using the ``set()`` method? That would pass data
  81. to the view that would look something like this::
  82. // print_r($posts) output:
  83. Array
  84. (
  85. [0] => Array
  86. (
  87. [Post] => Array
  88. (
  89. [id] => 1
  90. [title] => The title
  91. [body] => This is the post body.
  92. [created] => 2008-02-13 18:34:55
  93. [modified] =>
  94. )
  95. )
  96. [1] => Array
  97. (
  98. [Post] => Array
  99. (
  100. [id] => 2
  101. [title] => A title once again
  102. [body] => And the post body follows.
  103. [created] => 2008-02-13 18:34:56
  104. [modified] =>
  105. )
  106. )
  107. [2] => Array
  108. (
  109. [Post] => Array
  110. (
  111. [id] => 3
  112. [title] => Title strikes back
  113. [body] => This is really exciting! Not.
  114. [created] => 2008-02-13 18:34:57
  115. [modified] =>
  116. )
  117. )
  118. )
  119. CakePHP's view files are stored in ``/app/View`` inside a folder
  120. named after the controller to which they correspond. (We'll have to create
  121. a folder named 'Posts' in this case.) To format this post data into a
  122. nice table, our view code might look something like this
  123. .. code-block:: php
  124. <!-- File: /app/View/Posts/index.ctp -->
  125. <h1>Blog posts</h1>
  126. <table>
  127. <tr>
  128. <th>Id</th>
  129. <th>Title</th>
  130. <th>Created</th>
  131. </tr>
  132. <!-- Here is where we loop through our $posts array, printing out post info -->
  133. <?php foreach ($posts as $post): ?>
  134. <tr>
  135. <td><?php echo $post['Post']['id']; ?></td>
  136. <td>
  137. <?php echo $this->Html->link($post['Post']['title'],
  138. array('controller' => 'posts', 'action' => 'view', $post['Post']['id'])); ?>
  139. </td>
  140. <td><?php echo $post['Post']['created']; ?></td>
  141. </tr>
  142. <?php endforeach; ?>
  143. <?php unset($post); ?>
  144. </table>
  145. You might have noticed the use of an object called ``$this->Html``.
  146. This is an instance of the CakePHP :php:class:`HtmlHelper` class. CakePHP
  147. comes with a set of view helpers that make things like linking,
  148. form output, JavaScript and AJAX a snap. You can learn more about
  149. how to use them in :doc:`/views/helpers`, but
  150. what's important to note here is that the ``link()`` method will
  151. generate an HTML link with the given title (the first parameter)
  152. and URL (the second parameter).
  153. When specifying URLs in CakePHP, it is recommended that you use the
  154. array format. This is explained in more detail in the section on
  155. Routes. Using the array format for URLs allows you to take
  156. advantage of CakePHP's reverse routing capabilities. You can also
  157. specify URLs relative to the base of the application in the form of
  158. /controller/action/param1/param2.
  159. At this point, you should be able to point your browser to
  160. http://www.example.com/posts/index. You should see your view,
  161. correctly formatted with the title and table listing of the posts.
  162. If you happened to have clicked on one of the links we created in
  163. this view (which link a post's title to a URL /posts/view/some\_id),
  164. you were probably informed by CakePHP that the action hadn't yet
  165. been defined. If you were not so informed, either something has
  166. gone wrong, or you actually did define it already, in which case
  167. you are very sneaky. Otherwise, we'll create it in the
  168. PostsController now::
  169. // File: /app/Controller/PostsController.php
  170. class PostsController extends AppController {
  171. public $helpers = array('Html', 'Form');
  172. public function index() {
  173. $this->set('posts', $this->Post->find('all'));
  174. }
  175. public function view($id = null) {
  176. if (!$id) {
  177. throw new NotFoundException(__('Invalid post'));
  178. }
  179. $post = $this->Post->findById($id);
  180. if (!$post) {
  181. throw new NotFoundException(__('Invalid post'));
  182. }
  183. $this->set('post', $post);
  184. }
  185. }
  186. The ``set()`` call should look familiar. Notice we're using
  187. ``findById()`` rather than ``find('all')`` because we only want
  188. a single post's information.
  189. Notice that our view action takes a parameter: the ID of the post
  190. we'd like to see. This parameter is handed to the action through
  191. the requested URL. If a user requests ``/posts/view/3``, then the value
  192. '3' is passed as ``$id``.
  193. We also do a bit of error checking to ensure that a user is actually
  194. accessing a record. If a user requests ``/posts/view``, we will throw a
  195. ``NotFoundException`` and let the CakePHP ErrorHandler take over. We
  196. also perform a similar check to make sure the user has accessed a
  197. record that exists.
  198. Now let's create the view for our new 'view' action and place it in
  199. ``/app/View/Posts/view.ctp``
  200. .. code-block:: php
  201. <!-- File: /app/View/Posts/view.ctp -->
  202. <h1><?php echo h($post['Post']['title']); ?></h1>
  203. <p><small>Created: <?php echo $post['Post']['created']; ?></small></p>
  204. <p><?php echo h($post['Post']['body']); ?></p>
  205. Verify that this is working by trying the links at ``/posts/index`` or
  206. manually requesting a post by accessing ``/posts/view/1``.
  207. Adding Posts
  208. ============
  209. Reading from the database and showing us the posts is a great
  210. start, but let's allow for adding new posts.
  211. First, start by creating an ``add()`` action in the
  212. PostsController::
  213. class PostsController extends AppController {
  214. public $helpers = array('Html', 'Form', 'Session');
  215. public $components = array('Session');
  216. public function index() {
  217. $this->set('posts', $this->Post->find('all'));
  218. }
  219. public function view($id) {
  220. if (!$id) {
  221. throw new NotFoundException(__('Invalid post'));
  222. }
  223. $post = $this->Post->findById($id);
  224. if (!$post) {
  225. throw new NotFoundException(__('Invalid post'));
  226. }
  227. $this->set('post', $post);
  228. }
  229. public function add() {
  230. if ($this->request->is('post')) {
  231. $this->Post->create();
  232. if ($this->Post->save($this->request->data)) {
  233. $this->Session->setFlash(__('Your post has been saved.'));
  234. return $this->redirect(array('action' => 'index'));
  235. }
  236. $this->Session->setFlash(__('Unable to add your post.'));
  237. }
  238. }
  239. }
  240. .. note::
  241. ``$this->request->is()`` takes a single argument, which can be the
  242. request METHOD (``get``, ``put``, ``post``, ``delete``) or some request
  243. identifier (``ajax``). It is **not** a way to check for specific posted
  244. data. For instance, ``$this->request->is('book')`` will not return true
  245. if book data was posted.
  246. .. note::
  247. You need to include the SessionComponent - and SessionHelper - in
  248. any controller where you will use it. If necessary, include it in
  249. your AppController.
  250. Here's what the ``add()`` action does: if the HTTP method of the
  251. request was POST, it tries to save the data using the Post model. If for some
  252. reason it doesn't save, it just renders the view. This gives us a
  253. chance to show the user validation errors or other warnings.
  254. Every CakePHP request includes a ``CakeRequest`` object which is accessible using
  255. ``$this->request``. The request object contains useful information regarding the
  256. request that was just received, and can be used to control the flow of your application.
  257. In this case, we use the :php:meth:`CakeRequest::is()` method to check that the request is a HTTP POST request.
  258. When a user uses a form to POST data to your application, that
  259. information is available in ``$this->request->data``. You can use the
  260. :php:func:`pr()` or :php:func:`debug()` functions to print it out if you want to see
  261. what it looks like.
  262. We use the SessionComponent's :php:meth:`SessionComponent::setFlash()`
  263. method to set a message to a session variable to be displayed on the page after
  264. redirection. In the layout we have
  265. :php:func:`SessionHelper::flash` which displays the
  266. message and clears the corresponding session variable. The
  267. controller's :php:meth:`Controller::redirect` function
  268. redirects to another URL. The param ``array('action' => 'index')``
  269. translates to URL /posts (that is, the index action of the posts controller).
  270. You can refer to :php:func:`Router::url()` function on the
  271. `API <http://api.cakephp.org>`_ to see the formats in which you can specify a
  272. URL for various CakePHP functions.
  273. Calling the ``save()`` method will check for validation errors and
  274. abort the save if any occur. We'll discuss how those errors are
  275. handled in the following sections.
  276. We call the ``create()`` method first in order to reset the model
  277. state for saving new information. It does not actually create a record in the
  278. database, but clears Model::$id and sets Model::$data based on your database
  279. field defaults.
  280. Data Validation
  281. ===============
  282. CakePHP goes a long way toward taking the monotony out of form input
  283. validation. Everyone hates coding up endless forms and their
  284. validation routines. CakePHP makes it easier and faster.
  285. To take advantage of the validation features, you'll need to use
  286. CakePHP's FormHelper in your views. The :php:class:`FormHelper` is available by
  287. default to all views at ``$this->Form``.
  288. Here's our add view:
  289. .. code-block:: php
  290. <!-- File: /app/View/Posts/add.ctp -->
  291. <h1>Add Post</h1>
  292. <?php
  293. echo $this->Form->create('Post');
  294. echo $this->Form->input('title');
  295. echo $this->Form->input('body', array('rows' => '3'));
  296. echo $this->Form->end('Save Post');
  297. ?>
  298. We use the FormHelper to generate the opening tag for an HTML
  299. form. Here's the HTML that ``$this->Form->create()`` generates:
  300. .. code-block:: html
  301. <form id="PostAddForm" method="post" action="/posts/add">
  302. If ``create()`` is called with no parameters supplied, it assumes
  303. you are building a form that submits via POST to the current controller's
  304. ``add()`` action (or ``edit()`` action when ``id`` is included in
  305. the form data).
  306. The ``$this->Form->input()`` method is used to create form elements
  307. of the same name. The first parameter tells CakePHP which field
  308. they correspond to, and the second parameter allows you to specify
  309. a wide array of options - in this case, the number of rows for the
  310. textarea. There's a bit of introspection and automagic here:
  311. ``input()`` will output different form elements based on the model
  312. field specified.
  313. The ``$this->Form->end()`` call generates a submit button and ends
  314. the form. If a string is supplied as the first parameter to
  315. ``end()``, the FormHelper outputs a submit button named accordingly
  316. along with the closing form tag. Again, refer to
  317. :doc:`/views/helpers` for more on helpers.
  318. Now let's go back and update our ``/app/View/Posts/index.ctp``
  319. view to include a new "Add Post" link. Before the ``<table>``, add
  320. the following line::
  321. <?php echo $this->Html->link(
  322. 'Add Post',
  323. array('controller' => 'posts', 'action' => 'add')
  324. ); ?>
  325. You may be wondering: how do I tell CakePHP about my validation
  326. requirements? Validation rules are defined in the model. Let's look
  327. back at our Post model and make a few adjustments::
  328. class Post extends AppModel {
  329. public $validate = array(
  330. 'title' => array(
  331. 'rule' => 'notEmpty'
  332. ),
  333. 'body' => array(
  334. 'rule' => 'notEmpty'
  335. )
  336. );
  337. }
  338. The ``$validate`` array tells CakePHP how to validate your data
  339. when the ``save()`` method is called. Here, I've specified that
  340. both the body and title fields must not be empty. CakePHP's
  341. validation engine is strong, with a number of pre-built rules
  342. (credit card numbers, email addresses, etc.) and flexibility for
  343. adding your own validation rules. For more information,
  344. check the :doc:`/models/data-validation`.
  345. Now that you have your validation rules in place, use the app to
  346. try to add a post with an empty title or body to see how it works.
  347. Since we've used the :php:meth:`FormHelper::input()` method of the
  348. FormHelper to create our form elements, our validation error
  349. messages will be shown automatically.
  350. Editing Posts
  351. =============
  352. Post editing: here we go. You're a CakePHP pro by now, so you
  353. should have picked up a pattern. Make the action, then the view.
  354. Here's what the ``edit()`` action of the PostsController would look
  355. like::
  356. public function edit($id = null) {
  357. if (!$id) {
  358. throw new NotFoundException(__('Invalid post'));
  359. }
  360. $post = $this->Post->findById($id);
  361. if (!$post) {
  362. throw new NotFoundException(__('Invalid post'));
  363. }
  364. if ($this->request->is(array('post', 'put'))) {
  365. $this->Post->id = $id;
  366. if ($this->Post->save($this->request->data)) {
  367. $this->Session->setFlash(__('Your post has been updated.'));
  368. return $this->redirect(array('action' => 'index'));
  369. }
  370. $this->Session->setFlash(__('Unable to update your post.'));
  371. }
  372. if (!$this->request->data) {
  373. $this->request->data = $post;
  374. }
  375. }
  376. This action first ensures that the user has tried to access an existing record.
  377. If they haven't passed in an ``$id`` parameter, or the post does not
  378. exist, we throw a ``NotFoundException`` for the CakePHP ErrorHandler to take care of.
  379. Next the action checks whether the request is either a POST or a PUT request. If it is, then we
  380. use the POST data to update our Post record, or kick back and show the user
  381. validation errors.
  382. If there is no data set to ``$this->request->data``, we simply set it to the
  383. previously retrieved post.
  384. The edit view might look something like this:
  385. .. code-block:: php
  386. <!-- File: /app/View/Posts/edit.ctp -->
  387. <h1>Edit Post</h1>
  388. <?php
  389. echo $this->Form->create('Post');
  390. echo $this->Form->input('title');
  391. echo $this->Form->input('body', array('rows' => '3'));
  392. echo $this->Form->input('id', array('type' => 'hidden'));
  393. echo $this->Form->end('Save Post');
  394. ?>
  395. This view outputs the edit form (with the values populated), along
  396. with any necessary validation error messages.
  397. One thing to note here: CakePHP will assume that you are editing a
  398. model if the 'id' field is present in the data array. If no 'id' is
  399. present (look back at our add view), CakePHP will assume that you are
  400. inserting a new model when ``save()`` is called.
  401. You can now update your index view with links to edit specific
  402. posts:
  403. .. code-block:: php
  404. <!-- File: /app/View/Posts/index.ctp (edit links added) -->
  405. <h1>Blog posts</h1>
  406. <p><?php echo $this->Html->link("Add Post", array('action' => 'add')); ?></p>
  407. <table>
  408. <tr>
  409. <th>Id</th>
  410. <th>Title</th>
  411. <th>Action</th>
  412. <th>Created</th>
  413. </tr>
  414. <!-- Here's where we loop through our $posts array, printing out post info -->
  415. <?php foreach ($posts as $post): ?>
  416. <tr>
  417. <td><?php echo $post['Post']['id']; ?></td>
  418. <td>
  419. <?php
  420. echo $this->Html->link(
  421. $post['Post']['title'],
  422. array('action' => 'view', $post['Post']['id'])
  423. );
  424. ?>
  425. </td>
  426. <td>
  427. <?php
  428. echo $this->Html->link(
  429. 'Edit',
  430. array('action' => 'edit', $post['Post']['id'])
  431. );
  432. ?>
  433. </td>
  434. <td>
  435. <?php echo $post['Post']['created']; ?>
  436. </td>
  437. </tr>
  438. <?php endforeach; ?>
  439. </table>
  440. Deleting Posts
  441. ==============
  442. Next, let's make a way for users to delete posts. Start with a
  443. ``delete()`` action in the PostsController::
  444. public function delete($id) {
  445. if ($this->request->is('get')) {
  446. throw new MethodNotAllowedException();
  447. }
  448. if ($this->Post->delete($id)) {
  449. $this->Session->setFlash(
  450. __('The post with id: %s has been deleted.', h($id))
  451. );
  452. } else {
  453. $this->Session->setFlash(
  454. __('The post with id: %s could not be deleted.', h($id))
  455. );
  456. }
  457. return $this->redirect(array('action' => 'index'));
  458. }
  459. This logic deletes the post specified by $id, and uses
  460. ``$this->Session->setFlash()`` to show the user a confirmation
  461. message after redirecting them on to ``/posts``. If the user attempts to
  462. do a delete using a GET request, we throw an Exception. Uncaught exceptions
  463. are captured by CakePHP's exception handler, and a nice error page is
  464. displayed. There are many built-in :doc:`/development/exceptions` that can
  465. be used to indicate the various HTTP errors your application might need
  466. to generate.
  467. Because we're just executing some logic and redirecting, this
  468. action has no view. You might want to update your index view with
  469. links that allow users to delete posts, however:
  470. .. code-block:: php
  471. <!-- File: /app/View/Posts/index.ctp -->
  472. <h1>Blog posts</h1>
  473. <p><?php echo $this->Html->link('Add Post', array('action' => 'add')); ?></p>
  474. <table>
  475. <tr>
  476. <th>Id</th>
  477. <th>Title</th>
  478. <th>Actions</th>
  479. <th>Created</th>
  480. </tr>
  481. <!-- Here's where we loop through our $posts array, printing out post info -->
  482. <?php foreach ($posts as $post): ?>
  483. <tr>
  484. <td><?php echo $post['Post']['id']; ?></td>
  485. <td>
  486. <?php
  487. echo $this->Html->link(
  488. $post['Post']['title'],
  489. array('action' => 'view', $post['Post']['id'])
  490. );
  491. ?>
  492. </td>
  493. <td>
  494. <?php
  495. echo $this->Form->postLink(
  496. 'Delete',
  497. array('action' => 'delete', $post['Post']['id']),
  498. array('confirm' => 'Are you sure?')
  499. );
  500. ?>
  501. <?php
  502. echo $this->Html->link(
  503. 'Edit', array('action' => 'edit', $post['Post']['id'])
  504. );
  505. ?>
  506. </td>
  507. <td>
  508. <?php echo $post['Post']['created']; ?>
  509. </td>
  510. </tr>
  511. <?php endforeach; ?>
  512. </table>
  513. Using :php:meth:`~FormHelper::postLink()` will create a link that uses
  514. JavaScript to do a POST request to delete our post. Allowing content to be
  515. deleted using GET requests is dangerous, as web crawlers could accidentally
  516. delete all your content.
  517. .. note::
  518. This view code also uses the FormHelper to prompt the user with a
  519. JavaScript confirmation dialog before they attempt to delete a
  520. post.
  521. Routes
  522. ======
  523. For some, CakePHP's default routing works well enough. Developers
  524. who are sensitive to user-friendliness and general search engine
  525. compatibility will appreciate the way that CakePHP's URLs map to
  526. specific actions. So we'll just make a quick change to routes in
  527. this tutorial.
  528. For more information on advanced routing techniques, see
  529. :ref:`routes-configuration`.
  530. By default, CakePHP responds to a request for the root of your site
  531. (e.g., http://www.example.com) using its PagesController, rendering
  532. a view called "home". Instead, we'll replace this with our
  533. PostsController by creating a routing rule.
  534. CakePHP's routing is found in ``/app/Config/routes.php``. You'll want
  535. to comment out or remove the line that defines the default root
  536. route. It looks like this:
  537. .. code-block:: php
  538. Router::connect(
  539. '/',
  540. array('controller' => 'pages', 'action' => 'display', 'home')
  541. );
  542. This line connects the URL '/' with the default CakePHP home page.
  543. We want it to connect with our own controller, so replace that line
  544. with this one::
  545. Router::connect('/', array('controller' => 'posts', 'action' => 'index'));
  546. This should connect users requesting '/' to the index() action of
  547. our PostsController.
  548. .. note::
  549. CakePHP also makes use of 'reverse routing'. If, with the above
  550. route defined, you pass
  551. ``array('controller' => 'posts', 'action' => 'index')`` to a
  552. function expecting an array, the resulting URL used will be '/'.
  553. It's therefore a good idea to always use arrays for URLs, as this
  554. means your routes define where a URL goes, and also ensures that
  555. links point to the same place.
  556. Conclusion
  557. ==========
  558. Creating applications this way will win you peace, honor, love, and
  559. money beyond even your wildest fantasies. Simple, isn't it? Keep in
  560. mind that this tutorial was very basic. CakePHP has *many* more
  561. features to offer, and is flexible in ways we didn't wish to cover
  562. here for simplicity's sake. Use the rest of this manual as a guide
  563. for building more feature-rich applications.
  564. Now that you've created a basic CakePHP application, you're ready for
  565. the real thing. Start your own project and read the rest of the
  566. :doc:`Cookbook </index>` and `API <http://api.cakephp.org>`_.
  567. If you need help, there are many ways to get the help you need - please see the :doc:`/cakephp-overview/where-to-get-help` page.
  568. Welcome to CakePHP!
  569. Suggested Follow-up Reading
  570. ---------------------------
  571. These are common tasks people learning CakePHP usually want to study next:
  572. 1. :ref:`view-layouts`: Customizing your website layout
  573. 2. :ref:`view-elements`: Including and reusing view snippets
  574. 3. :doc:`/controllers/scaffolding`: Prototyping before creating code
  575. 4. :doc:`/console-and-shells/code-generation-with-bake`: Generating basic CRUD code
  576. 5. :doc:`/tutorials-and-examples/blog-auth-example/auth`: User authentication and authorization tutorial
  577. .. meta::
  578. :title lang=en: Blog Tutorial Adding a Layer
  579. :keywords lang=en: doc models,validation check,controller actions,model post,php class,model class,model object,business logic,database table,naming convention,bread and butter,callbacks,prefixes,nutshell,interaction,array,cakephp,interface,applications,delete