{"id":2164,"date":"2013-08-09T13:39:36","date_gmt":"2013-08-09T13:39:36","guid":{"rendered":"https:\/\/intelligentbee.com\/blog\/?p=2164"},"modified":"2024-05-31T09:02:23","modified_gmt":"2024-05-31T09:02:23","slug":"symfony2-jobeet-day-3-data-model","status":"publish","type":"post","link":"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-3-data-model\/","title":{"rendered":"Symfony2 Jobeet Day 3: The Data Model"},"content":{"rendered":"<p><span style=\"font-family: timesnew roman; font-size: 12px;\">* This article is part of the original <a href=\"http:\/\/symfony.com\/legacy\/doc\/jobeet?orm=Doctrine\" target=\"_blank\" rel=\"noopener\">Jobeet Tutorial<\/a>, created by Fabien Potencier, for Symfony 1.4.<\/span><\/p>\n<p>If you\u2019re itching to open your text editor and lay down some PHP, you will be happy to know that today will get us into some development. We will define the Jobeet data model, use an ORM to interact with the database and build the first module of the application. But as Symfony does a lot of work for us, we will have a fully functional web module without writing too much PHP code.<span id=\"more-181\"><\/span><\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_68_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title \" >Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-3-data-model\/#The_Relational_Model\" title=\"The Relational Model\">The Relational Model<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-3-data-model\/#The_Database\" title=\"The Database\">The Database<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-3-data-model\/#The_Schema\" title=\"The Schema\">The Schema<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-3-data-model\/#The_ORM\" title=\"The ORM\">The ORM<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-3-data-model\/#See_it_in_the_browser\" title=\"See it in the browser\">See it in the browser<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"The_Relational_Model\"><\/span>The Relational Model<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The user stories from the previous day describe the main objects of our project: <strong>jobs<\/strong>, <strong>affiliates<\/strong>, and <strong>categories<\/strong>. Here is the corresponding entity relationship diagram:<\/p>\n<p><a href=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day3-entity_diagram.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-17\" src=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day3-entity_diagram.png\" alt=\"Day3 - entity_diagram\" width=\"455\" height=\"274\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<p>In addition to the columns described in the stories, we have also added <code>created_at<\/code> and <code>updated_at<\/code> columns. We will configure Symfony to set their value automatically when an object is saved or updated.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Database\"><\/span>The Database<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>To store the jobs, affiliates and categories in the database, Symfony 2.3.2 uses <a href=\"http:\/\/www.doctrine-project.org\/projects\/orm.html\" target=\"_blank\" rel=\"noopener\">Doctrine ORM<\/a>. To define the database connection parameters, you have to edit the <code>app\/config\/parameters.yml<\/code> file (for this tutorial we will use MySQL):<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:yaml decode:true\" title=\"app\/config\/parameters.yml\">parameters:\r\n    database_driver: pdo_mysql\r\n    database_host: localhost\r\n    database_port: null\r\n    database_name: jobeet\r\n    database_user: root\r\n    database_password: password\r\n    # ...<\/pre>\n<p>Now that Doctrine knows about your database, you can have it create the database for you by typing the following command in your terminal:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false lang:default decode:true \">php app\/console doctrine:database:create<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"The_Schema\"><\/span>The Schema<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>To tell Doctrine about our objects, we will create \u201cmetadata\u201d files that will describe how our objects will be stored in the database. Now go to your code editor and create a directory named <code>doctrine<\/code>, inside <code>src\/Ibw\/JobeetBundle\/Resources\/config<\/code> directory. Doctrine will contain three files: <code>Category.orm.yml<\/code>, <code>Job.orm.yml<\/code> and <code>Affiliate.orm.yml.<\/code><\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:yaml decode:true\" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/doctrine\/Category.orm.yml\">IbwJobeetBundleEntityCategory:\r\n    type: entity\r\n    table: category\r\n    id:\r\n        id:\r\n            type: integer\r\n            generator: { strategy: AUTO }\r\n    fields:\r\n        name:\r\n            type: string\r\n            length: 255\r\n            unique: true\r\n    oneToMany:\r\n        jobs:\r\n            targetEntity: Job\r\n            mappedBy: category\r\n    manyToMany:\r\n        affiliates:\r\n            targetEntity: Affiliate\r\n            mappedBy: categories<\/pre>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:yaml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/doctrine\/Job.orm.yml\">IbwJobeetBundleEntityJob:\r\n    type: entity\r\n    table: job\r\n    id:\r\n        id:\r\n            type: integer\r\n            generator: { strategy: AUTO }\r\n    fields:\r\n        type:\r\n            type: string\r\n            length: 255\r\n            nullable: true\r\n        company:\r\n            type: string\r\n            length: 255\r\n        logo:\r\n            type: string\r\n            length: 255\r\n            nullable: true\r\n        url:\r\n            type: string\r\n            length: 255\r\n            nullable: true\r\n        position:\r\n            type: string\r\n            length: 255\r\n        location:\r\n            type: string\r\n            length: 255\r\n        description:\r\n            type: text\r\n        how_to_apply:\r\n            type: text\r\n        token:\r\n            type: string\r\n            length: 255\r\n            unique: true\r\n        is_public:\r\n            type: boolean\r\n            nullable: true\r\n        is_activated:\r\n            type: boolean\r\n            nullable: true\r\n        email:\r\n            type: string\r\n            length: 255\r\n        expires_at:\r\n            type: datetime\r\n        created_at:\r\n            type: datetime\r\n        updated_at:\r\n            type: datetime\r\n            nullable: true\r\n    manyToOne:\r\n        category:\r\n            targetEntity: Category\r\n            inversedBy: jobs\r\n            joinColumn:\r\n                name: category_id\r\n                referencedColumnName: id\r\n    lifecycleCallbacks:\r\n        prePersist: [ setCreatedAtValue ]\r\n        preUpdate: [ setUpdatedAtValue ]<\/pre>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:yaml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/doctrine\/Affiliate.orm.yml\">IbwJobeetBundleEntityAffiliate:\r\n    type: entity\r\n    table: affiliate\r\n    id:\r\n        id:\r\n            type: integer\r\n            generator: { strategy: AUTO }\r\n    fields:\r\n        url:\r\n            type: string\r\n            length: 255\r\n        email:\r\n            type: string\r\n            length: 255\r\n            unique: true\r\n        token:\r\n            type: string\r\n            length: 255\r\n        is_active:\r\n            type: boolean\r\n            nullable: true\r\n        created_at:\r\n            type: datetime\r\n    manyToMany:\r\n        categories:\r\n            targetEntity: Category\r\n            joinTable:\r\n                name: category_affiliate\r\n                joinColumns:\r\n                    affiliate_id:\r\n                        referencedColumnName: id\r\n                inverseJoinColumns:\r\n                    category_id:\r\n                        referencedColumnName: id\r\n    lifecycleCallbacks:\r\n        prePersist: [ setCreatedAtValue ]<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"The_ORM\"><\/span>The ORM<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Now Doctrine can generate the classes that define our objects for us with the command:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false lang:default decode:true\">php app\/console doctrine:generate:entities IbwJobeetBundle<\/pre>\n<p>If you take a look into <code>Entity<\/code> directory from <code>IbwJobeetBundle<\/code>, you will find the newly generated classes in there: <code>Category.php<\/code>, <code>Job.php<\/code> and <code>Affiliate.php. <\/code>Open <code>Job.php<\/code> and set the <code>created_at<\/code> and <code>updated_at<\/code> values as below:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Entity\/Job.php\">\/\/ ...\r\n\r\n    \/**\r\n     * @ORMPrePersist\r\n     *\/\r\n    public function setCreatedAtValue()\r\n    {\r\n        if(!$this-&gt;getCreatedAt()) {\r\n            $this-&gt;created_at = new DateTime();\r\n        }\r\n    }\r\n\r\n    \/**\r\n     * @ORMPreUpdate\r\n     *\/\r\n    public function setUpdatedAtValue()\r\n    {\r\n        $this-&gt;updated_at = new DateTime();\r\n    }<\/pre>\n<p>You will do the same for <code>created_at<\/code> value of the <code>Affiliate<\/code> class:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:php decode:true\" title=\"src\/Ibw\/JobeetBundle\/Entity\/Affiliate.php\">\/\/ ...\r\n\r\n    \/**\r\n     * @ORMPrePersist\r\n     *\/\r\n    public function setCreatedAtValue()\r\n    {\r\n        $this-&gt;created_at = new DateTime();\r\n    }\r\n\r\n\/\/ ...<\/pre>\n<p>This will make Doctrine to set the created_at and updated_at values when saving or updating objects. This behaviour was defined in the <code>Affiliate.orm.yml<\/code> and <code>Job.orm.yml<\/code> files listed above.<\/p>\n<p>We will also ask Doctrine to create our database tables with the command below:<\/p>\n<pre class=\"theme:tomorrow-night show-lang:2 nums:false lang:default decode:true\">php app\/console doctrine:schema:update --force<\/pre>\n<blockquote><p>This task should only be used during the development. For a more robust method of systematically updating your production database, read about Doctrine migrations.<\/p><\/blockquote>\n<p>The tables have been created in the database but there is no data in them. For any web application, there are three types of data: <strong>initial data<\/strong> (this is needed for the application to work, in our case we will have some initial categories and an admin user), <strong>test data<\/strong> (needed for the application to be tested) and <strong>user data<\/strong> (created by users during the normal life of the application).<\/p>\n<p>To populate the database with some initial data, we will use DoctrineFixturesBundle. To setup this bundle, we have to follow the next steps:<\/p>\n<p>1. Add the following to your <code>composer.json<\/code> file, in the <code>require<\/code> section:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 lang:default decode:true\">\/\/ ...\r\n    \"require\": {\r\n        \/\/ ...\r\n        \"doctrine\/doctrine-fixtures-bundle\": \"dev-master\",\r\n        \"doctrine\/data-fixtures\": \"dev-master\"\r\n    },\r\n\r\n\/\/ ...<\/pre>\n<p>2. Update the <code>vendor<\/code> libraries:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false lang:default decode:true \">php composer.phar update<\/pre>\n<p>3. Register the bundle DoctrineFixturesBundle in <code>app\/AppKernel.php<\/code>:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:php decode:true\" title=\"app\/AppKernel.php\">\/\/ ...\r\n\r\npublic function registerBundles()\r\n{\r\n    $bundles = array(\r\n        \/\/ ...\r\n        new DoctrineBundleFixturesBundleDoctrineFixturesBundle()\r\n    );\r\n\r\n    \/\/ ...\r\n}<\/pre>\n<p>Now that everything is set up, we will create some new classes to load data in a new folder, named <code>src\/Ibw\/JobeetBundle\/DataFixtures\/ORM<\/code>, in our bundle:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:php decode:true\" title=\"src\/Ibw\/JobeetBundle\/DataFixtures\/ORM\/LoadCategoryData.php\">&lt;?php\r\nnamespace IbwJobeetBundleDataFixturesORM;\r\n\r\nuse DoctrineCommonPersistenceObjectManager;\r\nuse DoctrineCommonDataFixturesAbstractFixture;\r\nuse DoctrineCommonDataFixturesOrderedFixtureInterface;\r\nuse IbwJobeetBundleEntityCategory;\r\n\r\nclass LoadCategoryData extends AbstractFixture implements OrderedFixtureInterface\r\n{\r\n    public function load(ObjectManager $em)\r\n    {\r\n        $design = new Category();\r\n        $design-&gt;setName('Design');\r\n\r\n        $programming = new Category();\r\n        $programming-&gt;setName('Programming');\r\n\r\n        $manager = new Category();\r\n        $manager-&gt;setName('Manager');\r\n\r\n        $administrator = new Category();\r\n        $administrator-&gt;setName('Administrator');\r\n\r\n        $em-&gt;persist($design);\r\n        $em-&gt;persist($programming);\r\n        $em-&gt;persist($manager);\r\n        $em-&gt;persist($administrator);\r\n        $em-&gt;flush();\r\n\r\n        $this-&gt;addReference('category-design', $design);\r\n        $this-&gt;addReference('category-programming', $programming);\r\n        $this-&gt;addReference('category-manager', $manager);\r\n        $this-&gt;addReference('category-administrator', $administrator);\r\n    }\r\n\r\n    public function getOrder()\r\n    {\r\n        return 1; \/\/ the order in which fixtures will be loaded\r\n    }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:php decode:true\" title=\"src\/Ibw\/JobeetBundle\/DataFixtures\/ORM\/LoadJobData.php\">&lt;?php\r\nnamespace IbwJobeetBundleDataFixturesORM;\r\n\r\nuse DoctrineCommonPersistenceObjectManager;\r\nuse DoctrineCommonDataFixturesAbstractFixture;\r\nuse DoctrineCommonDataFixturesOrderedFixtureInterface;\r\nuse IbwJobeetBundleEntityJob;\r\n\r\nclass LoadJobData extends AbstractFixture implements OrderedFixtureInterface\r\n{\r\n    public function load(ObjectManager $em)\r\n    {\r\n         $job_sensio_labs = new Job();\r\n         $job_sensio_labs-&gt;setCategory($em-&gt;merge($this-&gt;getReference('category-programming')));\r\n         $job_sensio_labs-&gt;setType('full-time');\r\n         $job_sensio_labs-&gt;setCompany('Sensio Labs');\r\n         $job_sensio_labs-&gt;setLogo('sensio-labs.gif');\r\n         $job_sensio_labs-&gt;setUrl('http:\/\/www.sensiolabs.com\/');\r\n         $job_sensio_labs-&gt;setPosition('Web Developer');\r\n         $job_sensio_labs-&gt;setLocation('Paris, France');\r\n         $job_sensio_labs-&gt;setDescription('You've already developed websites with symfony and you want to work with Open-Source technologies. You have a minimum of 3 years experience in web development with PHP or Java and you wish to participate to development of Web 2.0 sites using the best frameworks available.');\r\n         $job_sensio_labs-&gt;setHowToApply('Send your resume to fabien.potencier [at] sensio.com');\r\n         $job_sensio_labs-&gt;setIsPublic(true);\r\n         $job_sensio_labs-&gt;setIsActivated(true);\r\n         $job_sensio_labs-&gt;setToken('job_sensio_labs');\r\n         $job_sensio_labs-&gt;setEmail('job@example.com');\r\n         $job_sensio_labs-&gt;setExpiresAt(new DateTime('+30 days'));\r\n         $job_extreme_sensio = new Job();\r\n         $job_extreme_sensio-&gt;setCategory($em-&gt;merge($this-&gt;getReference('category-design')));\r\n         $job_extreme_sensio-&gt;setType('part-time');\r\n         $job_extreme_sensio-&gt;setCompany('Extreme Sensio');\r\n         $job_extreme_sensio-&gt;setLogo('extreme-sensio.gif');\r\n         $job_extreme_sensio-&gt;setUrl('http:\/\/www.extreme-sensio.com\/');\r\n         $job_extreme_sensio-&gt;setPosition('Web Designer');\r\n         $job_extreme_sensio-&gt;setLocation('Paris, France');\r\n         $job_extreme_sensio-&gt;setDescription('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in.');\r\n         $job_extreme_sensio-&gt;setHowToApply('Send your resume to fabien.potencier [at] sensio.com');\r\n         $job_extreme_sensio-&gt;setIsPublic(true);\r\n         $job_extreme_sensio-&gt;setIsActivated(true);\r\n         $job_extreme_sensio-&gt;setToken('job_extreme_sensio');\r\n         $job_extreme_sensio-&gt;setEmail('job@example.com');\r\n         $job_extreme_sensio-&gt;setExpiresAt(new DateTime('+30 days'));\r\n\r\n         $em-&gt;persist($job_sensio_labs);\r\n         $em-&gt;persist($job_extreme_sensio);\r\n         $em-&gt;flush();\r\n    }\r\n\r\n    public function getOrder()\r\n    {\r\n        return 2; \/\/ the order in which fixtures will be loaded\r\n    }\r\n}<\/pre>\n<p>Once your fixtures have been written, you can load them via the command line by using the<code>doctrine:fixtures:load<\/code> command:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false lang:default decode:true\">php app\/console doctrine:fixtures:load<\/pre>\n<p>Now, if you check your database, you should see the data loaded into tables.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"See_it_in_the_browser\"><\/span>See it in the browser<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>If you run the command below, it will create a new controller <code>src\/Ibw\/JobeetBundle\/Controllers\/JobController.php<\/code> with actions for listing, creating, editing and deleting jobs (and their corresponding templates, form and routes):<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false lang:default decode:true \">php app\/console doctrine:generate:crud --entity=IbwJobeetBundle:Job --route-prefix=ibw_job --with-write --format=yml<\/pre>\n<p>After running this command, you will need to do some configurations the prompter requires you to. So just select the default answers for them.<\/p>\n<p>To view this in the browser, we must import the new routes that were created in <code>src\/Ibw\/JobeetBundle\/Resources\/config\/routing\/job.yml<\/code> into our bundle main routing file:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:yaml decode:true\" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/routing.yml\">IbwJobeetBundle_job:\r\n        resource: \"@IbwJobeetBundle\/Resources\/config\/routing\/job.yml\"\r\n        prefix:   \/job\r\n\r\n# ...<\/pre>\n<p>We will also need to add a <code>_toString()<\/code> method to our <code>Category<\/code> class to be used by the category drop down from the edit job form:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:php decode:true\" title=\"src\/Ibw\/JobeetBundle\/Entity\/Category.php\">\/\/ ...\r\n\r\npublic function __toString()\r\n{\r\n    return $this-&gt;getName() ? $this-&gt;getName() : \"\";\r\n}\r\n\r\n\/\/ ...<\/pre>\n<p>Clear the cache:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false lang:default decode:true\">php app\/console cache:clear --env=dev\r\nphp app\/console cache:clear --env=prod<\/pre>\n<p>You can now test the job controller in a browser: <code>http:\/\/jobeet.local\/job\/<\/code> or, in development environment, <code>http:\/\/jobeet.local\/app_dev.php\/job\/<\/code> .<\/p>\n<p><a href=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-3-index_page.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-23\" src=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-3-index_page.png\" alt=\"Day 3 - index_page\" width=\"1910\" height=\"1038\" \/><\/a><\/p>\n<p>You can now create and edit jobs. Try to leave a required field blank, or try to enter invalid data. That\u2019s right, Symfony has created basic validation rules by introspecting the database schema.<\/p>\n<p><strong>That\u2019s all. Today, we have barely written PHP code but we have a working web module for the job model, ready to be tweaked and customized. Tomorrow, we will get familiar with the controller and the view. See you next time!<\/strong><\/p>\n<p><a href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/\" rel=\"license\"><img decoding=\"async\" style=\"border-width: 0;\" src=\"https:\/\/i.creativecommons.org\/l\/by-sa\/3.0\/88x31.png\" alt=\"Creative Commons License\" \/><\/a><br \/>\nThis work is licensed under a <a href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/\" target=\"_blank\" rel=\"license noopener\">Creative Commons Attribution-ShareAlike 3.0 Unported License<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>* This article is part of the original Jobeet Tutorial, created by Fabien Potencier, for Symfony 1.4. If you\u2019re itching [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[82],"tags":[],"yst_prominent_words":[371,384,415,483,564,798,853,1053,1147,1394,1556],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2164"}],"collection":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/comments?post=2164"}],"version-history":[{"count":3,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2164\/revisions"}],"predecessor-version":[{"id":133050,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2164\/revisions\/133050"}],"wp:attachment":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media?parent=2164"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/categories?post=2164"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/tags?post=2164"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/yst_prominent_words?post=2164"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}