├── README.md ├── chapter_I.md └── chapter_II.md /README.md: -------------------------------------------------------------------------------- 1 | # SymfonyVue 2 | 3 | A set of rules && laws in order to use VueJS inside Symfony applications. 4 | 5 | ## Usage 6 | 7 | In order to use this repo, you MUST be aware of the structure of Symfony 8 | and be aware of the logic of Javascript, a deep knowledge of PHP POO and 9 | MVC pattern is a plus. 10 | 11 | ## Goals 12 | 13 | ## Chapters 14 | 15 | [Chapter I](chapter_I.md) 16 | 17 | [Chapter II](chapter_II.md) 18 | 19 | ## Repository 20 | 21 | [Repository](https://github.com/Guikingone/SymfonyVue-Repository) -------------------------------------------------------------------------------- /chapter_I.md: -------------------------------------------------------------------------------- 1 | Hi everyone, welcome to this completely new approach of developing web applications (well, in fact, let's use the word "reactive app") using Symfony. 2 | 3 | During this "course", we gonna show you how to integrate, use and optimize VueJS inside of Symfony, this way, 4 | your application can be improved and have a better time for user experience. 5 | 6 | Alright, time to discover why this course, the idea behind this course is to help you understand why Symfony + VueJS is the best way to build 7 | 'future proof' applications, in fact, it can seems strange but Vue is already used by Laravel and the way you can improve things 8 | in this type of architecture is pretty amazing, things says, Symfony NEED to be improved and we're proud of being 9 | contributors to the documentation of this amazing framework but sometimes, the infos that we need is not on the doc's 10 | or not completely explained and we lost a huge amount of time into research and research for nothing. 11 | In this vision, we gonna try to merge parts of this course on the Symfony doc and help you use the framework with the latest 12 | tools available. 13 | 14 | Alright, enough talks, let's build our first page with Vue and Symfony ! 15 | 16 | # Part I - You say Vue ? 17 | 18 | Alright, so, what's VueJS ? In fact, Vue (pronounced view) is a frontend framework, a JS framework, yeah, 19 | we know, a JS framework inside a PHP framework, can seems a little bit overwhelming but that's the future 20 | of development and in fact, it can make you better developer over time. 21 | 22 | That's say, Vue is a lightweight framework and like other JS framework, his goal is to help you manage 23 | the frontend part of your application and improve the 'reactivity' of this part, talking about reactivity, 24 | a Symfony app is already capable of managing some part of the frontend experience but Vue allow us to go 25 | further and deeper into the user experience. 26 | For more informations, we let you read the official documentation of Vue, this course is not intended to teach you all 27 | the parts of this huge framework. 28 | 29 | Ok, time to build something ! 30 | 31 | ## I - Building our skeleton 32 | 33 | Alright, let's build our application, first, Symfony installation : 34 | 35 | ```bash 36 | composer create-project symfony/framework-standard-edition 37 | ``` 38 | 39 | Once the installation is over, the structure should not scare you, we gonna update the DefaultController first : 40 | 41 | ```php 42 | render('default/index.html.twig'); 59 | } 60 | } 61 | ``` 62 | 63 | Now, time to include VueJS in our application, for this, let's update the base.html.twig file : 64 | 65 | ```twig 66 | 67 | 68 | 69 | 70 | {% block title %}Vue in Symfony !{% endblock %} 71 | {% block stylesheets %}{% endblock %} 72 | 73 | 74 | 75 |
76 | {% block body %}{% endblock %} 77 |
78 | {% block javascripts %} 79 | 80 | {% endblock %} 81 | 82 | 83 | ``` 84 | 85 | Ok, here, we load the Vue library from unpkg, this help to improve the speed of loading the library but let's be clear, 86 | we gonna see later how to 'include' a complete 'manageable' installation of Vue via Webpack. 87 | Once this is done, let's update our index.html.twig file : 88 | 89 | ```twig 90 | {% extends 'base.html.twig' %} 91 | 92 | {% block body %} 93 | {% verbatim %} 94 |

{{ message }}

95 | {% endverbatim %} 96 | {% endblock %} 97 | 98 | {% block javascripts %} 99 | {{ parent() }} 100 | 108 | {% endblock %} 109 | ``` 110 | 111 | Here, the first things is to use the verbatim tag via Twig, this way, our JS code is managed by Vue and the syntax 112 | can't be managed by Twig (with the holy magic error 'message' variable can't be found), Vue use the same syntax as Twig (without some shortcut), 113 | this way, we can define our logic and use it faster and smoother. 114 | 115 | Second things, we instantiate the Vue object, this way, we can manage our whole page, to do this, we add a id with the value "app" 116 | inside the html body, this way, the body bloc defined by Twig is completely managed by Vue and we can access to the "bloc" 117 | by using the el key, we target the id and voila. 118 | 119 | Once this is done, we define what variables we gonna show inside our bloc, here, it's a simple string but later, you gonna 120 | learn to manager routes and even forms ! 121 | If you do everything correctly, your application (launched by ./bin/console s:r) should show a 'Hello World !' right in the left of your screen. 122 | 123 | Alright, so, we just add Vue into our Symfony apps and we can see that you have a thousand questions, let's be clear, we gonna answer all of yours. 124 | First, let's build a context for our apps, in this course, we gonna build a online e-commerce, nothing to difficult, just a simple market process. 125 | 126 | ## Part II - Building our logic 127 | 128 | Ok, now that we have our logic and even our goals, let's build something solid. 129 | 130 | Long story short, let's add something to render to the client, in a e-commerce shop, we have multiples 'products', in this 131 | vision, we gonna build a Product entity and the manager who manage this entity : 132 | 133 | ```php 134 | 140 | * 141 | * For the full copyright and license information, please view the LICENSE 142 | * file that was distributed with this source code. 143 | */ 144 | 145 | namespace AppBundle\Entity; 146 | 147 | use Doctrine\ORM\Mapping as ORM; 148 | 149 | /** 150 | * Class Products 151 | * 152 | * @author Guillaume Loulier 153 | * 154 | * @ORM\Entity 155 | * @ORM\Table(name="products") 156 | */ 157 | class Products 158 | { 159 | /** 160 | * @var int 161 | * 162 | * @ORM\Id 163 | * @ORM\Column(name="id") 164 | * @ORM\GeneratedValue(strategy="AUTO") 165 | */ 166 | private $id; 167 | 168 | /** 169 | * @var string 170 | * 171 | * @ORM\Column(name="name", type="string", length=200, nullable=false) 172 | */ 173 | private $name; 174 | 175 | /** 176 | * @var string 177 | * 178 | * @ORM\Column(name="price", type="string", length=50, nullable=false) 179 | */ 180 | private $price; 181 | 182 | /** 183 | * @return int 184 | */ 185 | public function getId () : int 186 | { 187 | return $this->id; 188 | } 189 | 190 | /** 191 | * @return string 192 | */ 193 | public function getName () : string 194 | { 195 | return $this->name; 196 | } 197 | 198 | /** 199 | * @param $name 200 | * 201 | * @return $this 202 | */ 203 | public function setName ($name) 204 | { 205 | $this->name = $name; 206 | 207 | return $this; 208 | } 209 | 210 | /** 211 | * @return string 212 | */ 213 | public function getPrice () : string 214 | { 215 | return $this->price; 216 | } 217 | 218 | /** 219 | * @param $price 220 | * 221 | * @return $this 222 | */ 223 | public function setPrice ($price) 224 | { 225 | $this->price = $price; 226 | 227 | return $this; 228 | } 229 | } 230 | ``` 231 | 232 | Simple entity, just for the "gestion" purpose, this way, here's the manager : 233 | 234 | ```php 235 | 241 | * 242 | * For the full copyright and license information, please view the LICENSE 243 | * file that was distributed with this source code. 244 | */ 245 | 246 | namespace AppBundle\Managers; 247 | 248 | use AppBundle\Entity\Products; 249 | use Doctrine\ORM\EntityManager; 250 | 251 | /** 252 | * Class ProductsManager 253 | * 254 | * @author Guillaume Loulier 255 | */ 256 | class ProductsManager 257 | { 258 | /** @var EntityManager */ 259 | private $doctrine; 260 | 261 | /** 262 | * ProductsManager constructor. 263 | * 264 | * @param EntityManager $doctrine 265 | */ 266 | public function __construct (EntityManager $doctrine) 267 | { 268 | $this->doctrine = $doctrine; 269 | } 270 | 271 | /** 272 | * @return array 273 | */ 274 | public function getAllProducts() 275 | { 276 | return $this->doctrine->getRepository(Products::class)->findAll(); 277 | } 278 | } 279 | ``` 280 | 281 | Here, nothing to hard, we just return all the products stored into the BDD (for the moment). 282 | 283 | Alright, let's update our Controllers : 284 | 285 | ```php 286 | 292 | * 293 | * For the full copyright and license information, please view the LICENSE 294 | * file that was distributed with this source code. 295 | */ 296 | 297 | namespace AppBundle\Controller; 298 | 299 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 300 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 301 | 302 | class ProductsController extends Controller 303 | { 304 | /** 305 | * @Route(path="/products", name="products_all") 306 | */ 307 | public function getAllProductsAction() 308 | { 309 | $products = $this->get('core.products_manager')->getAllProducts(); 310 | 311 | return $this->render('Logic/products.html.twig', [ 312 | 'products' => $products 313 | ]); 314 | } 315 | } 316 | ``` 317 | 318 | The core.products_manager is declared inside the services.yml : 319 | 320 | ```yaml 321 | # Learn more about services, parameters and containers at 322 | # http://symfony.com/doc/current/service_container.html 323 | parameters: 324 | #parameter_name: value 325 | 326 | services: 327 | core.products_manager: 328 | class: AppBundle\Managers\ProductsManager 329 | arguments: 330 | - '@doctrine.orm.entity_manager' 331 | ``` 332 | 333 | For the moment, not a single products has been persisted into the BDD but we already have our logic, nice things. 334 | For the moment, we have a routes who return all the products, good idea but how can we access her from Vue ? 335 | Well, if you use Twig, you probably use something like this : 336 | 337 | ```twig 338 | {% extends 'base.html.twig' %} 339 | 340 | {% block body %} 341 | {% verbatim %} 342 |

{{ message }}

343 | {% endverbatim %} 344 | Products ! 345 | {% endblock %} 346 | 347 | {% block javascripts %} 348 | {{ parent() }} 349 | 358 | {% endblock %} 359 | ``` 360 | 361 | Don't get me wrong, that's a good way for approaching routing but Vue can be used in order to do the same things but 362 | faster and cleaner, in fact, Vue has his own router, we gonna use it later but for the moment, let's see how to create a 363 | link with Vue from the index to the /products route. 364 | 365 | To do so, let's update our index.html.twig file : 366 | 367 | ```twig 368 | {% extends 'base.html.twig' %} 369 | 370 | {% block body %} 371 | {% verbatim %} 372 |

{{ message }}

373 |

374 | Products ! 375 |

376 | {% endverbatim %} 377 | {% endblock %} 378 | 379 | {% block javascripts %} 380 | {{ parent() }} 381 | 390 | {% endblock %} 391 | ``` 392 | 393 | Alright, so, where's the differences here ? 394 | 395 | First, we define a new attribute in the Vue instance, we simply call the after slash part of our route, if we need 396 | to have a slash after, Vue can handle it. Once this is done, time to "bind" the value into our view, to do this, Vue 397 | allow to use a new "html attribute" called v-bind, this attribute allow to show in the raw aspect a attribute and if it's 398 | a URL attribute, to passe it into the URL, amazing little thing. 399 | In this case, we bind into the href part of the a tag, we simply passe the attribute and Vue gonna do the trick to pass 400 | the attribute to the URL, once this is done, Symfony gonna grab the request and match with the controller created earlier, 401 | yeah, we know, magic thing, amazing approach, we know, we love this type of logic ! 402 | 403 | If everything goes right, you should see a blank page with no error, logic approach, we don't have any products at this time. 404 | 405 | Alright, time to build something bigger, we know how to show something, we know how to merge the Sf router and the Vue instance, 406 | let's build the next part of our application. 407 | 408 | ## Part III - You say logic ? 409 | 410 | Alright random citizen, time to get serious, now that we have some logic, time to build something bigger. 411 | In this part, we gonna discover something bigger, for the moment, we have a backend and some frontend logic, time 412 | to connect the two and build something really smarter. 413 | 414 | For the moment, Vue only handle basic usage, we've connect the "router" aspect but later, we gonna pass through this one, 415 | so, what's the deal here ? 416 | 417 | The biggest problem is that we use Vue as a "magical" tools in order to show something, not really what Vue is designed for, 418 | in fact, Vue is build in order to manage the user experience and build a complete Single Page Application, this way, 419 | our backend only return "Json content" and Vue provide the view aspect and the user management part of the application. 420 | 421 | This way, if we go deeper, we need to have some "real" logic that Vue can handle. 422 | 423 | Alright, so, what can be build using Vue ? 424 | 425 | In our application, we can imagine a research form who can handle the user demand and show the result found on the BDD 426 | into a list or better, show a list of result and allow the user to click on the results and go to the details page. 427 | Yeah, that's what we want, time to build it ! 428 | 429 | First, let's create a SearchForm and a method inside our Manager to handle this form : 430 | 431 | ```php 432 | 438 | * 439 | * For the full copyright and license information, please view the LICENSE 440 | * file that was distributed with this source code. 441 | */ 442 | 443 | namespace AppBundle\Form\Type; 444 | 445 | use AppBundle\Entity\Products; 446 | use Symfony\Component\Form\AbstractType; 447 | use Symfony\Component\Form\Extension\Core\Type\TextType; 448 | use Symfony\Component\Form\FormBuilderInterface; 449 | use Symfony\Component\OptionsResolver\OptionsResolver; 450 | 451 | class SearchProductsType extends AbstractType 452 | { 453 | public function buildForm (FormBuilderInterface $builder, array $options) 454 | { 455 | $builder 456 | ->add('name', TextType::class) 457 | ; 458 | } 459 | 460 | public function configureOptions (OptionsResolver $resolver) 461 | { 462 | $resolver->setDefaults([ 463 | 'data_class' => Products::class 464 | ]); 465 | } 466 | 467 | } 468 | ``` 469 | 470 | Now, let's update our manager : 471 | 472 | ```php 473 | 479 | * 480 | * For the full copyright and license information, please view the LICENSE 481 | * file that was distributed with this source code. 482 | */ 483 | 484 | namespace AppBundle\Managers; 485 | 486 | use AppBundle\Entity\Products; 487 | use AppBundle\Form\Type\SearchProductsType; 488 | use Doctrine\ORM\EntityManager; 489 | use Symfony\Component\Form\FormFactory; 490 | use Symfony\Component\HttpFoundation\RequestStack; 491 | use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; 492 | 493 | /** 494 | * Class ProductsManager 495 | * 496 | * @author Guillaume Loulier 497 | */ 498 | class ProductsManager 499 | { 500 | /** @var EntityManager */ 501 | private $doctrine; 502 | 503 | /** @var FormFactory */ 504 | private $form; 505 | 506 | /** @var RequestStack */ 507 | private $request; 508 | 509 | /** 510 | * ProductsManager constructor. 511 | * 512 | * @param EntityManager $doctrine 513 | * @param FormFactory $form 514 | * @param RequestStack $request 515 | */ 516 | public function __construct ( 517 | EntityManager $doctrine, 518 | FormFactory $form, 519 | RequestStack $request 520 | ) { 521 | $this->doctrine = $doctrine; 522 | $this->form = $form; 523 | $this->request = $request; 524 | } 525 | 526 | /** 527 | * @return array 528 | */ 529 | public function getAllProducts() 530 | { 531 | return $this->doctrine->getRepository(Products::class)->findAll(); 532 | } 533 | 534 | /** 535 | * @throws InvalidOptionsException 536 | * 537 | * @return \Symfony\Component\Form\FormView 538 | */ 539 | public function searchProductsByName() 540 | { 541 | $request = $this->request->getCurrentRequest(); 542 | $form = $this->form->create(SearchProductsType::class); 543 | $form->handleRequest($request); 544 | 545 | if ($form->isSubmitted() && $form->isValid()) { 546 | // TODO 547 | } 548 | 549 | return $form->createView(); 550 | } 551 | } 552 | ``` 553 | 554 | Ok, let's update our controllers then : 555 | 556 | ```php 557 | 563 | * 564 | * For the full copyright and license information, please view the LICENSE 565 | * file that was distributed with this source code. 566 | */ 567 | 568 | namespace AppBundle\Controller; 569 | 570 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 571 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 572 | 573 | /** 574 | * Class ProductsController 575 | * 576 | * @author Guillaume Loulier 577 | */ 578 | class ProductsController extends Controller 579 | { 580 | /** 581 | * @Route(path="/products", name="products_all") 582 | */ 583 | public function getAllProductsAction() 584 | { 585 | $products = $this->get('core.products_manager')->getAllProducts(); 586 | 587 | $form = $this->get('core.products_manager')->searchProductsByName(); 588 | 589 | return $this->render('Logic/products.html.twig', [ 590 | 'products' => $products, 591 | 'searchForm' => $form 592 | ]); 593 | } 594 | } 595 | ``` 596 | 597 | Alright, time to get serious, you're about to discover something completely new in web development, we named it 598 | 'mind changer components approach' but well, way to long to say, call it components. 599 | You need to understand basic logic to understand component, in the past, when you build a HTML page, you create new tag 600 | and "cut" your page in multiples sections or article and event div, bad idea. 601 | 602 | Today, you cut your page on multiples component, each components contains his own logic and is "outside" of the other component, 603 | this approach is the heart of React and Angular, in Vue, it's also the heart but the approach is much simple, in fact, 604 | you can build the whole application without using component, if you use components, Vue's gonna be a completely different 605 | framework and your code gonna change fast and for the best ! 606 | 607 | Ok, enough talk, how can we build component in our view ? Simply by extending the Vue instance : 608 | 609 | ```twig 610 | {% extends 'base.html.twig' %} 611 | 612 | {% block body %} 613 | {% for products in products %} 614 |

{{ products.name }}

615 | {% endfor %} 616 | 617 | {% endblock %} 618 | 619 | {% block javascripts %} 620 | {{ parent() }} 621 | 632 | {% endblock %} 633 | ``` 634 | 635 | Here's your first component using Vue ! 636 | 637 | if everything goes right, you must see 'Hello World from products !' in your browser, petty cool huh ? 638 | In fact, Vue is based on Polymer approach, by the way that you can build your own html tag and 'component' as long 639 | as he contains logic and can be called in your view, pretty simple. 640 | 641 | Ok, now, time to update our view in order to show the form : 642 | 643 | ```twig 644 | {% extends 'base.html.twig' %} 645 | 646 | {% block body %} 647 | {% for products in products %} 648 |

{{ products.name }}

649 | {% endfor %} 650 | {{ form_start(searchForm) }} 651 | {{ form_label(searchForm.name) }} 652 | {{ form_widget(searchForm.name) }} 653 | {{ form_errors(searchForm.name) }} 654 | {{ form_end(searchForm) }} 655 | 656 | {% endblock %} 657 | 658 | {% block javascripts %} 659 | {{ parent() }} 660 | 671 | {% endblock %} 672 | ``` 673 | 674 | Alright, simple things here, we simply show how to display the form, now, time to get serious, how can we use Vue inside 675 | of our form ? 676 | 677 | Hum, not so easy if you think about it ... In fact, it's very simple, we learn earlier how to bind a attribute to the URL, 678 | magic happen, Vue is also capable of binding attributes FROM something, like a form for example, let's update our view : 679 | 680 | ```twig 681 | {% extends 'base.html.twig' %} 682 | 683 | {% block body %} 684 | {% for products in products %} 685 |

{{ products.name }}

686 | {% endfor %} 687 | {{ form_start(searchForm) }} 688 | {{ form_label(searchForm.name) }} 689 | {{ form_widget(searchForm.name, {'attr': {'v-model': 'name'}}) }} 690 | {{ form_errors(searchForm.name) }} 691 | {{ form_end(searchForm) }} 692 | {% verbatim %} 693 |

694 | How, seems like you're searching {{ name }}, 695 | here's the list of products wih this category that we have in stock : 696 |

697 | {% endverbatim %} 698 | {% endblock %} 699 | 700 | {% block javascripts %} 701 | {{ parent() }} 702 | 713 | {% endblock %} 714 | ``` 715 | 716 | Here's the big advantages, here, we delete the component for readability but don't be scare, he's on the return, 717 | for the logic, we simply put the v-model attribute on our form, this way, Vue can bind this attribute from our form to 718 | our Vue instance and store the result, once this is done, we show the result in our view via {{ name }}, just give a try, 719 | if you type 'illumination !' inside the form, Vue's gonna show the result in the p tag, we can even go further with 720 | a conditioning rendering : 721 | 722 | ```twig 723 | {% extends 'base.html.twig' %} 724 | 725 | {% block body %} 726 | {% for products in products %} 727 |

{{ products.name }}

728 | {% endfor %} 729 | {{ form_start(searchForm) }} 730 | {{ form_label(searchForm.name) }} 731 | {{ form_widget(searchForm.name, {'attr': {'v-model': 'name'}}) }} 732 | {{ form_errors(searchForm.name) }} 733 | {{ form_end(searchForm) }} 734 | {% verbatim %} 735 |

736 | How, seems like you're searching {{ name }}, 737 | here's the list of products wih this category that we have in stock : 738 |

739 | {% endverbatim %} 740 | {% endblock %} 741 | 742 | {% block javascripts %} 743 | {{ parent() }} 744 | 755 | {% endblock %} 756 | ``` 757 | 758 | Here, we simply add the v-if attribute on the p with the condition on "if name exist then the p tag gonna be updated" 759 | with the content of the form and displayed to the visitor. 760 | 761 | Yeah, we know, seems magic ? It is ! This way, if the form stay blank, the visitor don't have the p and he can continue 762 | to explore the page, if he enter something, the p is updated and we show the result, later, we gonna ask the server for 763 | the results and show this result to the client but for the moment, give a try by handling multiples form 764 | and binding the attributes in your view. 765 | 766 | Ok, one more things, here, we sam how to bind the value passed from the input field into our Vue instance 767 | and how to return this attributes into our view but how can we 'inject' Vue into the Submit process of Symfony and grab 768 | the data, this way, we can stop the process of the form and simply launch the research phase from Vue ? 769 | 770 | Well, for this, Vue allow to use the 'v-on' directives, this approach allow to call Vue on certain phase or events 771 | and do things related to this event. In this way, we can call the submit phase of the form and do something else than 772 | process the submission via Symfony : 773 | 774 | ```twig 775 | {% extends 'base.html.twig' %} 776 | 777 | {% block body %} 778 | {% for products in products %} 779 |

{{ products.name }}

780 | {% endfor %} 781 | {{ form_start(searchForm, {'attr': {'v-on:submit.prevent': 'searchProducts'}}) }} 782 | {{ form_label(searchForm.name) }} 783 | {{ form_widget(searchForm.name, {'attr': {'v-model': 'name'}}) }} 784 | {{ form_errors(searchForm.name) }} 785 | 786 | {{ form_end(searchForm) }} 787 | {% verbatim %} 788 |

789 | How, seems like you're searching {{ name }}, 790 | here's the list of products wih this category that we have in stock : 791 |

792 | {% endverbatim %} 793 | {% endblock %} 794 | 795 | {% block javascripts %} 796 | {{ parent() }} 797 | 813 | {% endblock %} 814 | ``` 815 | 816 | Here, we've add the v-on:submit.prevent directive on the form_start call and bind this directive to a method called 817 | searchProducts, this way, Vue's gonna do the relation and call the method once the form is submitted, we gonna stop the 818 | the submit process and log to the console the message, if everything goes smoothly, your console should show 819 | the message. 820 | 821 | Hell yeah ! We know, Vue is awesome, fast and simple to understand, his approach to a lot of things is pretty straight forward 822 | but let's be clear, you don't see the whole package that Vue offer, here, we simply inject Vue inside our view and call 823 | different method or shortcut in order to do simple things, that's not the complete way of using Vue. 824 | 825 | Alright crazy readers, here's just the beginning of your adventure with Vue and Symfony, it's over for this chapter but 826 | we gonna build something way more bigger in the second chapter, stay in contact ! 827 | 828 | Guillaume, brownie addict. 829 | 830 | 831 | 832 | 833 | -------------------------------------------------------------------------------- /chapter_II.md: -------------------------------------------------------------------------------- 1 | Welcome back dear developers, good to see you here. 2 | 3 | In the first chapter, you saw how to use Vue inside your Twig templates 4 | and how to communicate with Symfony, in this chapter, we gonna dive deeper 5 | into the Vue logic and make him really more "close" to Symfony. 6 | 7 | ## Part I - Did you say Ajax ? 8 | 9 | Alright, now, time to get in the serious business of Vue inside of Symfony, 10 | for this, we gonna update our form and make the HTTP request in order to 11 | show the results. 12 | 13 | In the earlier time of web, you may use Jquery or even pure JS for 14 | Ajax call, let's be clear, this time is out ! 15 | In fact, Vue doesn't allow HTTP call in the "classic" version of the framework, 16 | for this, you must implement a library called vue-resource, i prefer to be clear, 17 | this library was part of the ecosystem of Vue but the creator of Vue say 18 | that Vue must be concentrate on the core functionality and this library was 19 | deprecated, in fact, the control of the library was passed to a new team 20 | and Vue's gonna implement some functionality that come with vue-resource 21 | later. 22 | 23 | Here, we gonna implement this library and do simple HTTP call from our frontend 24 | to our backend, this way, our application feel more "actual" and faster. 25 | 26 | Alright, time to get serious, for implementing vue-resource, let's open 27 | github and the repo of the library, once this's done, let's implement 28 | the script tag in our main layout and build our first HTTP call ! 29 | 30 | ```twig 31 | 32 | 33 | 34 | 35 | {% block title %}Vue in Symfony !{% endblock %} 36 | {% block stylesheets %}{% endblock %} 37 | 38 | 39 | 40 |
41 | {% block body %}{% endblock %} 42 |
43 | {% block javascripts %} 44 | 45 | 46 | {% endblock %} 47 | 48 | 49 | ``` 50 | 51 | For simplicity, i use PHPStorm so i've decide to download the library 52 | (with Ctrl + Enter -> Download library). 53 | 54 | Once this's done, let's update our products view and inject HTTP calls. 55 | First, we gonna add a new components, this components gonna "contains" all 56 | the logic of showing and adding some functionality like routing to the details page 57 | via Vue. 58 | This way, the component gonna contains some attributes and Symfony gonna 59 | "inject" this attributes from Twig. 60 | 61 | Alright, let's update our view linked to the products and add somme HTTP calls : 62 | 63 | ```twig 64 | {% extends 'base.html.twig' %} 65 | 66 | {% block body %} 67 | {% for products in products %} 68 |

{{ products.name }}

69 | {% endfor %} 70 | {{ form_start(searchForm, {'attr': {'v-on:submit.prevent': 'searchProducts'}}) }} 71 | {{ form_label(searchForm.name) }} 72 | {{ form_widget(searchForm.name, {'attr': {'v-model': 'name'}}) }} 73 | {{ form_errors(searchForm.name) }} 74 | 75 | {{ form_end(searchForm) }} 76 | {% verbatim %} 77 |

78 | How, seems like you're searching {{ name }}, 79 | here's the list of products wih this category that we have in stock : 80 |

81 | {% endverbatim %} 82 | {% endblock %} 83 | 84 | {% block javascripts %} 85 | {{ parent() }} 86 | 107 | {% endblock %} 108 | ``` 109 | 110 | Alright, what's new here ? First, we add some variables 111 | in order to store the results and being able to call the API then 112 | we define a method who's gonna been call once we submit the form, 113 | this way, we parse the response and push every results into the products 114 | array stored into our Vue instance. 115 | 116 | Problem is, where's our API return ? 117 | 118 | We gonna build it now, directly from scratch without using Api Platform 119 | for example, that's not so complicated, first, let's update our Entity : 120 | 121 | ```php 122 | 128 | * 129 | * For the full copyright and license information, please view the LICENSE 130 | * file that was distributed with this source code. 131 | */ 132 | 133 | namespace AppBundle\Entity; 134 | 135 | use Doctrine\ORM\Mapping as ORM; 136 | 137 | /** 138 | * Class Products 139 | * 140 | * @author Guillaume Loulier 141 | * 142 | * @ORM\Entity 143 | * @ORM\Table(name="products") 144 | */ 145 | class Products 146 | { 147 | /** 148 | * @var int 149 | * 150 | * @ORM\Id 151 | * @ORM\Column(name="id") 152 | * @ORM\GeneratedValue(strategy="AUTO") 153 | */ 154 | private $id; 155 | 156 | /** 157 | * @var string 158 | * 159 | * @ORM\Column(name="name", type="string", length=200, nullable=false) 160 | */ 161 | private $name; 162 | 163 | /** 164 | * @var string 165 | * 166 | * @ORM\Column(name="price", type="string", length=50, nullable=false) 167 | */ 168 | private $price; 169 | 170 | /** 171 | * @var string 172 | * 173 | * @ORM\Column(name="category", type="string", length=100, nullable=false) 174 | */ 175 | private $category; 176 | 177 | /** 178 | * @return int 179 | */ 180 | public function getId () : int 181 | { 182 | return $this->id; 183 | } 184 | 185 | /** 186 | * @return string 187 | */ 188 | public function getName () : string 189 | { 190 | return $this->name; 191 | } 192 | 193 | /** 194 | * @param $name 195 | * 196 | * @return $this 197 | */ 198 | public function setName ($name) 199 | { 200 | $this->name = $name; 201 | 202 | return $this; 203 | } 204 | 205 | /** 206 | * @return string 207 | */ 208 | public function getPrice () : string 209 | { 210 | return $this->price; 211 | } 212 | 213 | /** 214 | * @param $price 215 | * 216 | * @return $this 217 | */ 218 | public function setPrice ($price) 219 | { 220 | $this->price = $price; 221 | 222 | return $this; 223 | } 224 | 225 | /** 226 | * @return string 227 | */ 228 | public function getCategory () : string 229 | { 230 | return $this->category; 231 | } 232 | 233 | /** 234 | * @param string $category 235 | * 236 | * @return $this 237 | */ 238 | public function setCategory (string $category) 239 | { 240 | $this->category = $category; 241 | 242 | return $this; 243 | } 244 | } 245 | ``` 246 | 247 | Here, we simply add a category attribute, we gonna launch the research 248 | via the category, for simplicity. 249 | Now, let's update our manager : 250 | 251 | ```php 252 | 258 | * 259 | * For the full copyright and license information, please view the LICENSE 260 | * file that was distributed with this source code. 261 | */ 262 | 263 | namespace AppBundle\Managers; 264 | 265 | use AppBundle\Entity\Products; 266 | use AppBundle\Form\Type\SearchProductsType; 267 | use Doctrine\ORM\EntityManager; 268 | use Symfony\Component\Form\FormFactory; 269 | use Symfony\Component\HttpFoundation\RequestStack; 270 | use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; 271 | 272 | /** 273 | * Class ProductsManager 274 | * 275 | * @author Guillaume Loulier 276 | */ 277 | class ProductsManager 278 | { 279 | /** @var EntityManager */ 280 | private $doctrine; 281 | 282 | /** @var FormFactory */ 283 | private $form; 284 | 285 | /** @var RequestStack */ 286 | private $request; 287 | 288 | /** 289 | * ProductsManager constructor. 290 | * 291 | * @param EntityManager $doctrine 292 | * @param FormFactory $form 293 | * @param RequestStack $request 294 | */ 295 | public function __construct ( 296 | EntityManager $doctrine, 297 | FormFactory $form, 298 | RequestStack $request 299 | ) { 300 | $this->doctrine = $doctrine; 301 | $this->form = $form; 302 | $this->request = $request; 303 | } 304 | 305 | /** 306 | * @return array 307 | */ 308 | public function getAllProducts() 309 | { 310 | return $this->doctrine->getRepository(Products::class)->findAll(); 311 | } 312 | 313 | /** 314 | * @param string $name 315 | * 316 | * @return array 317 | */ 318 | public function getProductsByName(string $name) 319 | { 320 | $products = $this->doctrine->getRepository(Products::class) 321 | ->findBy(['category' => $name]); 322 | 323 | $data = []; 324 | foreach ($products as $product) { 325 | $data = [ 326 | 'name' => $product->getName(), 327 | 'Price' => $product->getPrice(), 328 | 'Category' => $product->getCategory() 329 | ]; 330 | } 331 | 332 | return $data; 333 | } 334 | 335 | /** 336 | * @throws InvalidOptionsException 337 | * 338 | * @return \Symfony\Component\Form\FormView 339 | */ 340 | public function searchProductsByName() 341 | { 342 | $request = $this->request->getCurrentRequest(); 343 | $form = $this->form->create(SearchProductsType::class); 344 | $form->handleRequest($request); 345 | 346 | if ($form->isSubmitted() && $form->isValid()) { 347 | // TODO 348 | } 349 | 350 | return $form->createView(); 351 | } 352 | } 353 | ``` 354 | 355 | Here, we simply return the result of our query, this way, we add all 356 | the results into an array and we bind every attribute to a key, we can 357 | use the serializer but the array approach is faster. 358 | 359 | Let's update our controller : 360 | 361 | ```php 362 | 368 | * 369 | * For the full copyright and license information, please view the LICENSE 370 | * file that was distributed with this source code. 371 | */ 372 | 373 | namespace AppBundle\Controller; 374 | 375 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 376 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 377 | use Symfony\Component\HttpFoundation\JsonResponse; 378 | use Symfony\Component\HttpFoundation\Response; 379 | use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; 380 | 381 | /** 382 | * Class ProductsController 383 | * 384 | * @author Guillaume Loulier 385 | */ 386 | class ProductsController extends Controller 387 | { 388 | /** 389 | * @Route(path="/products", name="products_all") 390 | * 391 | * @throws InvalidOptionsException Thrown by the form. 392 | */ 393 | public function getAllProductsAction() 394 | { 395 | $products = $this->get('core.products_manager')->getAllProducts(); 396 | 397 | $form = $this->get('core.products_manager')->searchProductsByName(); 398 | 399 | return $this->render('Logic/products.html.twig', [ 400 | 'products' => $products, 401 | 'searchForm' => $form 402 | ]); 403 | } 404 | 405 | /** 406 | * @Route(path="/products/{name}", name="products_all_api") 407 | * 408 | * @param string $name The category of the products 409 | * 410 | * @return JsonResponse The response. 411 | */ 412 | public function getProductsByNameAction(string $name) 413 | { 414 | $data = $this->get('core.products_manager')->getProductsByName($name); 415 | 416 | return new JsonResponse($data, Response::HTTP_OK); 417 | } 418 | } 419 | ``` 420 | 421 | Here, we simply add a new method with a new route 422 | who require a attributes, this way, we can pass the attribute 423 | to the manager method and return the results via a JSONResponse. 424 | 425 | 426 | 427 | --------------------------------------------------------------------------------