Workshop: Symfony2 Intruduction: (Controller, Routing, Model)

Post on 27-Jan-2015

116 views 1 download

description

Slides from workshop: Symfony2 Intruduction at eZ Publish S

Transcript of Workshop: Symfony2 Intruduction: (Controller, Routing, Model)

Workshop: Symfony2 introduction

Antonio Perić-Mažar, CEOLuka Vidoš, Frontend Dev

04.09.2013, eZ Publish Summer Camp

Who we are?

• locastic – since 2010• Web and mobile development• UI/UX design

• Located in Split, Croatia• 9 team members

• www.locastic.com• studio@locastic.com

Our works?

Speakers?

• Antonio Perić-Mažar, mag. ing. comp.

• CEO and partner @ locastic• Last 6 years developing

custom web apps• Last 3 years developing

custom apps with Symfony

• www.locastic.com• antonio@locastic.com• twitter: @antonioperic

Speakers?

• Luka Vidoš, mag. Diz.• Frontend Dev @ locastic● Last 13 years designing, slicing

and implementing beautiful web design into various web apps

● In love with Symfony for last 3 years

• www.locastic.com• luka@locastic.com• twitter: @lukavidos

Symfony is project● PHP framework● Philosophy● Community

• Fabien Potencier, SensioLabs (France)

• 2005. first version was relased• 2007. symfony 1.0• 2011. Symfony2• ATM Symfony 2.3.4 (LTS)

10 criteria for choosing the correct framework1. Popularity and community size2. Philosophy3. Sustainability4. Support5. Technique6. Security7. Documentation ( always can be better )8. License (free)9. Availability of resources on the market10. Try it out!

6 good reasons to use Symfony

1. Reputation2. Permanence3. References (Yahoo!, Dailymotion, Opensky.com, Exercise.com, phpBB, Drupal,

eZPublish, Youporn :) )

4. Innovation5. Resources6. Interoperability

The technological benefits of Symfony in 6 easy lessons1. Faster and less greedy2. Unlimited flexibility3. Expandable4. Stable and sustainable5. The joy of developing6. Ease of use

Symfony2 community

• Github – 10 000+ commits, 5 branches, 50 releases, 746 contributors

● 1898 open source Bundles (http://knpbundles.com/)

• Symfony2 CMF (Content Management Framework), eZpublish, Drupal, Sylius

What are we going to do today?

What are we going to do today?

What are we going to do today?

To do list app

● Creating unlimited number of task lists● Creating, deleting and updating lists● Creating, deleting and updating tasks inside of

lists

Let's go... How to install Sf2?

Instalation: Option A

http://getcomposer.org/

> php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"

> php composer.phar> php composer.phar create-project symfony/framework-standard-edition /path/to/webroot/Symfony 2.3.0

Instalation: Option B

Download, Extract, Start

Done!

What is inside?

What is inside?

> php app/console

Standalone Tools: The Symfony2 ComponentsHttpFoundation - Contains the Request and Response classes, as well as other classes for handling

sessions and file uploads;

Routing - Powerful and fast routing system that allows you to map a specific URI (e.g. /contact) to some information about how that request should be handled (e.g. execute the contactAction() method);

Form - A full-featured and flexible framework for creating forms and handling form submissions;

Validator A system for creating rules about data and then validating whether or not user-submitted data follows those rules;

ClassLoader An autoloading library that allows PHP classes to be used without needing to manually require the files containing those classes;

Templating A toolkit for rendering templates, handling template inheritance (i.e. a template is decorated with a layout) and performing other common template tasks;

Security - A powerful library for handling all types of security inside an application;

Translation A framework for translating strings in your application.

Coding standardStructure

● Add a single space after each comma delimiter;● Add a single space around operators (==, &&, ...);● Add a comma after each array item in a multi-line array, even after the last one;● Add a blank line before return statements, unless the return is alone inside a statement-group (like an if

statement);● Use braces to indicate control structure body regardless of the number of statements it contains;● Define one class per file - this does not apply to private helper classes that are not intended to be instantiated

from the outside and thus are not concerned by the PSR-0 standard;● Declare class properties before methods;● Declare public methods first, then protected ones and finally private ones;● Use parentheses when instantiating classes regardless of the number of arguments the constructor has;● Exception message strings should be concatenated using sprintf.

Coding standardNaming Conventions

● Use camelCase, not underscores, for variable, function and method names, arguments;● Use underscores for option names and parameter names;● Use namespaces for all classes;● Prefix abstract classes with Abstract. Please note some early Symfony2 classes do not follow this convention and

have not been renamed for backward compatibility reasons. However all new abstract classes must follow this naming convention;

● Suffix interfaces with Interface;● Suffix traits with Trait;● Suffix exceptions with Exception;● Use alphanumeric characters and underscores for file names;● Don't forget to look at the more verbose Conventions document for more subjective naming considerations.

Coding standardService Naming Conventions

● A service name contains groups, separated by dots;● The DI alias of the bundle is the first group (e.g. fos_user);● Use lowercase letters for service and parameter names;● A group name uses the underscore notation;● Each service has a corresponding parameter containing the class name, following the SERVICE

NAME.class convention.

Documentation

● Add PHPDoc blocks for all classes, methods, and functions;● Omit the @return tag if the method does not return anything;● The @package and @subpackage annotations are not used.

Coding standardnamespace Acme;

/**

* Coding standards demonstration.

*/

class FooBar

{

const SOME_CONST = 42;

private $fooBar;

...

Coding standard /**

* @param string $dummy Some argument description

*/

public function __construct($dummy)

{

$this->fooBar = $this->transformText($dummy);

}

Coding standard

/** * @param string $dummy Some argument

description * @param array $options * * @return string|null Transformed input */ private function transformText($dummy,

array $options = array()) { $mergedOptions = array_merge( $options, array( 'some_default' => 'values', 'another_default' => 'more values', ) );

Coding standard if (true === $dummy) {

return;

}

if ('string' === $dummy) {

if ('values' === $mergedOptions['some_default']) {

return substr($dummy, 0, 5);

}

return ucwords($dummy);

}

throw new \RuntimeException(sprintf('Unrecognized dummy option "%s"', $dummy));

}

}

Creating Pages in Symfony2

Creating a new page in Symfony2 is a simple two-step process:

● Create a route: A route defines the URL (e.g. /about) to your page and specifies a controller (which is a PHP function) that Symfony2 should execute when the URL of an incoming request matches the route path;

● Create a controller: A controller is a PHP function that takes the incoming request and transforms it into the Symfony2 Response object that's returned to the user.

● Environment: ● Prod, dev, test

But before... bundles :)

● Bundle is everything in Symfony2 :) - first-class citizens

● Directory that houses everything related to a specific feature ( configuration, PHP, JS, CSS...)

● We can compare it with modul or plugin● Flexible, independent, powerful

> php app/console generate:bundle

Creating Pages in Symfony2

Step1: Create route# app/config/routing.yml

acme_hello:

resource: "@AcmeHelloBundle/Resources/config/routing.yml"

prefix: /

# src/Acme/HelloBundle/Resources/config/routing.yml

hello:

path: /hello/{name}

defaults: { _controller: AcmeHelloBundle:Hello:index }

Creating Pages in Symfony2

Step2: Create controller// src/Acme/HelloBundle/Controller/HelloController.php

namespace Acme\HelloBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController

{

public function indexAction($name)

{

return new Response('<html><body>Hello '.$name.'!</body></html>');

}

}

Creating Pages in Symfony2

Step3: Create template// src/Acme/HelloBundle/Controller/HelloController.php

namespace Acme\HelloBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class HelloController extends Controller

{

public function indexAction($name)

{

return $this->render(

'AcmeHelloBundle:Hello:index.html.twig',

array('name' => $name)

);

}

}

Creating Pages in Symfony2

Practice #1:- create controller that receives some integer as

parameter from route and returns template with that number squared.

Time: 5 minutes

Controller• Request -> Response● The response could be an HTML page, an XML document,

a serialized JSON array, an image, a redirect, a 404 error or anything else you can dream up.

● The controller contains whatever arbitrary logic your application needs to render the content of a page.

use Symfony\Component\HttpFoundation\Response;

public function helloAction(){ return new Response('Hello world!');}

Requests, Controller, Response Lifecycle

Simple Controller// src/Acme/HelloBundle/Controller/HelloController.php

namespace Acme\HelloBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController

{

public function indexAction($name)

{

return new Response('<html><body>Hello '.$name.'!</body></html>');

}

}

Mapping url to a Controller# app/config/routing.yml

hello:

path: /hello/{first_name}/{last_name}

defaults: { _controller: AcmeHelloBundle:Hello:index, color: green }

// order of arguments does not metter

public function indexAction($first_name, $color, $last_name)

{

// ... do whatever logic and return Response

}

The Request as Controller Argumentuse Symfony\Component\HttpFoundation\Request;

public function updateAction(Request $request)

{

$form = $this->createForm(...);

$form->handleRequest($request);

// ...

}

The Base Controller Class// src/Acme/HelloBundle/Controller/HelloController.php

namespace Acme\HelloBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController extends Controller

{

// ... do whatever logic and return Response

}

By extending this Controller class, you can take advantage of several helper methods.

Common Controller Tasks// redirecting

public function indexAction()

{

return $this->redirect($this->generateUrl('homepage'));

}

public function indexAction()

{

return $this->redirect($this->generateUrl('homepage'), 301);

}

Shortcut for:

use Symfony\Component\HttpFoundation\RedirectResponse;

return new RedirectResponse($this->generateUrl('homepage'));

Common Controller Tasks// forwarding

public function indexAction($name)

{

$response = $this->forward('AcmeHelloBundle:Hello:fancy', array(

'name' => $name,

'color' => 'green',

));

// ... further modify the response or return it directly

return $response;

}

public function fancyAction($name, $color)

{

// ... create and return a Response object

}

Common Controller TasksShortcut for:

$httpKernel = $this->container->get('http_kernel');

$response = $httpKernel->forward(

'AcmeHelloBundle:Hello:fancy',

array(

'name' => $name,

'color' => 'green',

)

);

Common Controller Tasks// rendering templates

use Symfony\Component\HttpFoundation\Response;

$content = $this->renderView(

'AcmeHelloBundle:Hello:index.html.twig',

array('name' => $name)

);

return new Response($content);

return $this->render(

'AcmeHelloBundle:Hello:index.html.twig',

array('name' => $name)

);

Common Controller TasksShortcut for:$templating = $this->get('templating');

$content = $templating->render(

'AcmeHelloBundle:Hello:index.html.twig',

array('name' => $name)

);

$templating->render(

'AcmeHelloBundle:Hello/Greetings:index.html.twig',

array('name' => $name)

);

404 errorpublic function indexAction(){ // retrieve the object from database $product = ...; if (!$product) { throw $this->createNotFoundException('The product does not exist'); }

return $this->render(...);}

Error 500throw new \Exception('Something went wrong!');

Accessing other Services$request = $this->getRequest();

$templating = $this->get('templating');

$router = $this->get('router');

$mailer = $this->get('mailer');

Accessing other Services$request = $this->getRequest();

$templating = $this->get('templating');

$router = $this->get('router');

$mailer = $this->get('mailer');

> php app/console container:debug

Managing the Session$session = $this->getRequest()->getSession();

// store an attribute for reuse during a later user request$session->set('foo', 'bar');

// in another controller for another request$foo = $session->get('foo');

// use a default value if the key doesn't exist$filters = $session->get('filters', array());

Flash Messagespublic function updateAction(){ $form = $this->createForm(...);

$form->bind($this->getRequest()); if ($form->isValid()) { // do some sort of processing

$this->get('session')->getFlashBag()->add('notice', 'Your changes were saved!');

return $this->redirect($this->generateUrl(...)); }

return $this->render(...);}

Flash Messages{% for flashMessage in app.session.flashbag.get('notice') %} <div class="flash-notice"> {{ flashMessage }} </div>{% endfor %}

The Response ObjectThe only requirement for a controller is to return a Response object. The Response class is a PHP abstraction around the HTTP response - the text-based message filled with HTTP headers and content that's sent back to the client:

use Symfony\Component\HttpFoundation\Response;

// create a simple Response with a 200 status code (the default)$response = new Response('Hello '.$name, 200);

// create a JSON-response with a 200 status code$response = new Response(json_encode(array('name' => $name)));$response->headers->set('Content-Type', 'application/json');

The Response Object - JSONuse Symfony\Component\HttpFoundation\Response;

$response = new Response();$response->setContent(json_encode(array( 'data' => 123,)));$response->headers->set('Content-Type', 'application/json');

---

use Symfony\Component\HttpFoundation\JsonResponse;

$response = new JsonResponse();$response->setData(array( 'data' => 123));

The Response Object - FILEuse Symfony\Component\HttpFoundation\ResponseHeaderBag;

$d = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'foo.pdf');

$response->headers->set('Content-Disposition', $d);

---

use Symfony\Component\HttpFoundation\BinaryFileResponse

$file = 'path/to/file.txt';$response = new BinaryFileResponse($file);

The Request Object$request = $this->getRequest();

$request->isXmlHttpRequest(); // is it an Ajax request?

$request->getPreferredLanguage(array('en', 'fr'));

$request->query->get('page'); // get a $_GET parameter

$request->request->get('page'); // get a $_POST parameter

Render static pageNo need for controller!!!

acme_privacy:

path: /privacy

defaults:

_controller: FrameworkBundle:Template:template

template: 'AcmeBundle:Static:privacy.html.twig'

ControllerPractice #2:● send numbers via $_GET parameter (numA and numB). Get these two

numbers in controller from Request Object (using $_GET[] is forbidden). Send Request object as action's parameter.

If numA*numB > 100 redirect to some custom controller's action;

If numA < 0 and numB < 0 throw 404 error;

If numA+numB > 10 return JSON Response with result;

else return template with result numA-numB;

Time: 10 minutes

Routing <3index.php?article_id=57 -> don't like this/i-like/symfony-routing

One of the most powerfull Symfony2 component. Very flexible!!!

Route = map form url to controllerPosition of route is important!!!

Routing <3// example (sylius):

locastic_product_review_list:

pattern: /review-for-product/{productId}

defaults:

_controller: locastic.controller.frontend.product_review:listReviewsForProductAction

locastic_product_review_add:

pattern: /add-review-for-product/{productId}

defaults:

_controller: locastic.controller.frontend.product_review:addReviewAction

_format: json

sylius_product_show:

pattern: /p/{slug}

defaults:

_controller: sylius.controller.product:showAction

_sylius:

template: SyliusWebBundle:Frontend/Product:show.html.twig

criteria: {slug: $slug}

Routing <3

// 4 ways of defining routes1.YAML2.XML3.PHP4.Annotations

Routing - YAML# app/config/routing.ymlblog_show: path: /blog/{slug} defaults: { _controller: AcmeBlogBundle:Blog:show }

Routing - XML<!-- app/config/routing.xml --><?xml version="1.0" encoding="UTF-8" ?><routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">

<route id="blog_show" path="/blog/{slug}"> <default key="_controller">AcmeBlogBundle:Blog:show</default> </route></routes>

Routing - PHP// app/config/routing.phpuse Symfony\Component\Routing\RouteCollection;use Symfony\Component\Routing\Route;

$collection = new RouteCollection();$collection->add('blog_show', new Route('/blog/{slug}', array( '_controller' => 'AcmeBlogBundle:Blog:show',)));

return $collection;

Routing - Annotations/** * @Route("/blog/{slug}", name=”blog_show”) */public function showAction($slug){

// do whatever logic and return Response}

Routing :)

Basic Route Configuration_welcome: path: / defaults: { _controller: AcmeDemoBundle:Main:homepage }

<?xml version="1.0" encoding="UTF-8" ?><routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> <route id="_welcome" path="/"> <default key="_controller">AcmeDemoBundle:Main:homepage</default> </route></routes>

Routing with placeholderblog_show:

path: /blog/{slug}

defaults: { _controller: AcmeBlogBundle:Blog:show }

<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://symfony.com/schema/routing

http://symfony.com/schema/routing/routing-1.0.xsd">

<route id="blog_show" path="/blog/{slug}">

<default key="_controller">AcmeBlogBundle:Blog:show</default>

</route>

</routes>

Required and Optional Placeholderblog:

path: /blog

defaults: { _controller: AcmeBlogBundle:Blog:index }

blog:

path: /blog/{page}

defaults: { _controller: AcmeBlogBundle:Blog:index }

blog:

path: /blog/{page}

defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }

Required and Optional Placeholderblog:

path: /blog/{page}

defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }

/blog {page} = 1

/blog/1 {page} = 1

/blog/2 {page} = 2

Adding Requirementsblog:

path: /blog/{page}

defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }

blog_show:

path: /blog/{slug}

defaults: { _controller: AcmeBlogBundle:Blog:show }

URL route parameters

/blog/2 blog {page} = 2

/blog/my-blog-post blog {page} = my-blog-post

Adding Requirementsblog:

path: /blog/{page}

defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }

requirements:

page: \d+

URL route parameters

/blog/2 blog {page} = 2

/blog/my-blog-post blog_show {slug} = my-blog-post

Earlier Routes always Win!!!

What this all means is that the order of the routes is very important. If the blog_show route were placed above the blog route, the URL /blog/2 would match blog_show instead of blog since the {slug} parameter of blog_show has no requirements. By using proper ordering and clever requirements, you can accomplish just about anything.

Adding Requirementshomepage:

path: /{culture}

defaults: { _controller: AcmeDemoBundle:Main:homepage, culture: en }

requirements:

culture: en|fr

/ {culture} = en

/en {culture} = en

/fr {culture} = fr

/es won't match this route

Adding HTTP Method Requirementscontact:

path: /contact

defaults: { _controller: AcmeDemoBundle:Main:contact }

methods: [GET]

contact_process:

path: /contact

defaults: { _controller: AcmeDemoBundle:Main:contactProcess }

methods: [POST]

Advanced Routing Examplearticle_show:

path: /articles/{culture}/{year}/{title}.{_format}

defaults: { _controller: AcmeDemoBundle:Article:show, _format: html }

requirements:

culture: en|fr

_format: html|rss

year: \d+

/articles/en/2010/my-post

/articles/fr/2010/my-post.rss

/articles/en/2013/my-latest-post.html

Including External Routing Resources# app/config/routing.yml

acme_hello:

resource: "@AcmeHelloBundle/Resources/config/routing.yml"

# src/Acme/HelloBundle/Resources/config/routing.yml

acme_hello:

path: /hello/{name}

defaults: { _controller: AcmeHelloBundle:Hello:index }

Prefixing Imported Routes# app/config/routing.yml

acme_hello:

resource: "@AcmeHelloBundle/Resources/config/routing.yml"

prefix: /admin

Visualizing & Debugging RoutesWhile adding and customizing routes, it's helpful to be able to visualize and get

detailed information about your routes. A great way to see every route in your application is via the router:debug console command. Execute the command by running the following from the root of your project.

> php app/console router:debug

Generating URLs

class MainController extends Controller

{

public function showAction($slug)

{

// ...

$url = $this->generateUrl( // helper method

'blog_show',

array('slug' => 'my-blog-post')

);

}

}

Generating URLs

$params = $this->get('router')->match('/blog/my-blog-post');

// array(

// 'slug' => 'my-blog-post',

// '_controller' => 'AcmeBlogBundle:Blog:show',

// )

$uri = $this->get('router')->generate('blog_show', array('slug' => 'my-blog-post'));

// /blog/my-blog-post

$this->get('router')->generate('blog_show', array('slug' => 'my-blog-post'), true);

// http://www.example.com/blog/my-blog-post

Generating URLs

// with query string

$this->get('router')->generate('blog', array('page' => 2, 'category' => 'Symfony'));

// /blog/2?category=Symfony

// from template

<a href="{{ path('blog_show', {'slug': 'my-blog-post'}) }}">

Read this blog post.

</a>

<a href="{{ url('blog_show', {'slug': 'my-blog-post'}) }}">

Read this blog post.

</a>

Routing :)Practice #3:

- create route for AcmeDemoBundle:Hello:list, that accepts parameters:

- category (string with no numbers)

- page number (integer)

- only GET method

In template list: category, slug and page number like this:

If url is: /ez-publish-summer-camp/3

Category slug: ez-publish-summer-camp

Page number: 3

Use YAML format for routing!

Time: 5 minutes

MODELORM Doctrine 2

parameters.yml → Config.yml

> php app/console doctrine:database:create

MODEL - metadata// src/Acme/StoreBundle/Entity/Product.phpnamespace Acme\StoreBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/** * @ORM\Entity * @ORM\Table(name="product") */class Product{ /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id;

...

MODEL - metadata..

/** * @ORM\Column(type="string", length=100) */ protected $name;

/** * @ORM\Column(type="decimal", scale=2) */ protected $price;

/** * @ORM\Column(type="text") */ protected $description;}

MODEL – metadata - YAML# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml

Acme\StoreBundle\Entity\Product:

type: entity

table: product

id:

id:

type: integer

generator: { strategy: AUTO }

fields:

name:

type: string

length: 100

price:

type: decimal

scale: 2

description:

type: text

MODEL – metadata - XML<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml -->

<?xml version="1.0" encoding="UTF-8" ?>

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping

http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

<entity name="Acme\StoreBundle\Entity\Product" table="product">

<id name="id" type="integer" column="id">

<generator strategy="AUTO" />

</id>

<field name="name" column="name" type="string" length="100" />

<field name="price" column="price" type="decimal" scale="2" />

<field name="description" column="description" type="text" />

</entity>

</doctrine-mapping>

MODEL A bundle can accept only one metadata definition format. For example, it's not possible to mix YAML metadata definitions with annotated PHP entity class definitions.

Be careful that your class name and properties aren't mapped to a protected SQL keyword (such as group or user).

Create getters and setters > php app/console doctrine:generate:entities Acme/StoreBundle/Entity/Product

> php app/console doctrine:schema:update –force

Persist object to database// src/Acme/StoreBundle/Controller/DefaultController.php

// ...use Acme\StoreBundle\Entity\Product;use Symfony\Component\HttpFoundation\Response;

public function createAction(){ $product = new Product(); $product->setName('A Foo Bar'); $product->setPrice('19.99'); $product->setDescription('Lorem ipsum dolor');

$em = $this->getDoctrine()->getManager(); $em->persist($product); $em->flush();

return new Response('Created product id '.$product->getId());}

Fetching Objects from the Databasepublic function showAction($id){ $product = $this->getDoctrine() ->getRepository('AcmeStoreBundle:Product') ->find($id);

if (!$product) { throw $this->createNotFoundException( 'No product found for id '.$id ); }

// ... do something, like pass the $product object into a template}

Repository Basic// query by the primary key (usually "id")$product = $repository->find($id);

// dynamic method names to find based on a column value$product = $repository->findOneById($id);$product = $repository->findOneByName('foo');

// find *all* products$products = $repository->findAll();

// find a group of products based on an arbitrary column value$products = $repository->findByPrice(19.99);

Repository Basic// query for one product matching be name and price$product = $repository->findOneBy(array('name' => 'foo', 'price' => 19.99));

// query for all products matching the name, ordered by price$products = $repository->findBy( array('name' => 'foo'), array('price' => 'ASC'));

How many queries?

Updating an Objectpublic function updateAction($id){ $em = $this->getDoctrine()->getManager(); $product = $em->getRepository('AcmeStoreBundle:Product')->find($id);

if (!$product) { throw $this->createNotFoundException( 'No product found for id '.$id ); }

$product->setName('New product name!'); $em->flush();

return $this->redirect($this->generateUrl('homepage'));}

Deleting on object$em->remove($product);$em->flush();

Querying for Object with DQL $em = $this->getDoctrine()->getManager();$query = $em->createQuery( 'SELECT p FROM AcmeStoreBundle:Product p WHERE p.price > :price ORDER BY p.price ASC')->setParameter('price', '19.99');

$products = $query->getResult();

Using Doctrine's Query Builder$repository = $this->getDoctrine() ->getRepository('AcmeStoreBundle:Product');

$query = $repository->createQueryBuilder('p') ->where('p.price > :price') ->setParameter('price', '19.99') ->orderBy('p.price', 'ASC') ->getQuery();

$products = $query->getResult();

Custom Repository Classes// using annotations

// src/Acme/StoreBundle/Entity/Product.php

namespace Acme\StoreBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**

* @ORM\Entity(repositoryClass="Acme\StoreBundle\Entity\ProductRepository")

*/

class Product

{

//...

}

Custom Repository Classes// using YAML

#src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml

Acme\StoreBundle\Entity\Product:

type: entity

repositoryClass: Acme\StoreBundle\Entity\ProductRepository

# ...

Custom Repository Classes// using XML

<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml -->

<?xml version="1.0" encoding="UTF-8" ?>

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping

http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

<entity name="Acme\StoreBundle\Entity\Product"

repository-class="Acme\StoreBundle\Entity\ProductRepository">

<!-- ... -->

</entity>

</doctrine-mapping>

Custom Repository Classes// src/Acme/StoreBundle/Entity/ProductRepository.php

namespace Acme\StoreBundle\Entity;

use Doctrine\ORM\EntityRepository;

class ProductRepository extends EntityRepository

{

public function findAllOrderedByName()

{

return $this->getEntityManager()

->createQuery(

'SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC'

)

->getResult();

}

}

Custom Repository Classes$em = $this->getDoctrine()->getManager();

$products = $em->getRepository('AcmeStoreBundle:Product')

->findAllOrderedByName();

MODEL – custom repository public function updateGallery($photoId, $galleryId) { $qb = $this->createQueryBuilder('p'); $query = $qb ->update() ->set('p.gallery', $galleryId) ->where('p.id = :id') ->setParameter('id', $photoId);

return $query->getQuery()->execute(); }

Relationship Mapping Metadata// src/Acme/StoreBundle/Entity/Category.php

// ...

use Doctrine\Common\Collections\ArrayCollection;

class Category

{

// ...

/**

* @ORM\OneToMany(targetEntity="Product", mappedBy="category")

*/

protected $products;

public function __construct()

{

$this->products = new ArrayCollection();

}

}

Product (n) <- (1) Category

Relationship Mapping Metadata<!-- src/Acme/StoreBundle/Resources/config/doctrine/Category.orm.xml -->

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping

http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

<entity name="Acme\StoreBundle\Entity\Category">

<!-- ... -->

<one-to-many field="products"

target-entity="Product"

mapped-by="category"

/>

<!--

don't forget to init the collection in

the __construct() method of the entity

-->

</entity>

</doctrine-mapping>

Product (n) <- (1) Category

Relationship Mapping Metadata#src/Acme/StoreBundle/Resources/config/doctrine/Category.orm.yml

Acme\StoreBundle\Entity\Category:

type: entity

# ...

oneToMany:

products:

targetEntity: Product

mappedBy: category

# don't forget to init the collection in the __construct() method of the entity

Product (n) <- (1) Category

Relationship Mapping Metadata// src/Acme/StoreBundle/Entity/Product.php

// ...

class Product

{

// ...

/**

* @ORM\ManyToOne(targetEntity="Category", inversedBy="products")

* @ORM\JoinColumn(name="category_id", referencedColumnName="id")

*/

protected $category;

}

Product (n) <- (1) Category

Relationship Mapping Metadata# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml

Acme\StoreBundle\Entity\Product:

type: entity

# ...

manyToOne:

category:

targetEntity: Category

inversedBy: products

joinColumn:

name: category_id

referencedColumnName: id

Product (n) <- (1) Category

Relationship Mapping Metadata<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml -->

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping

http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

<entity name="Acme\StoreBundle\Entity\Product">

<!-- ... -->

<many-to-one field="category"

target-entity="Category"

inversed-by="products"

join-column="category"

>

<join-column

name="category_id"

referenced-column-name="id"

/>

</many-to-one>

</entity>

</doctrine-mapping>

Product (n) <- (1) Category

Relationship Mapping Metadata

Saving related entities// ...

use Acme\StoreBundle\Entity\Category;

use Acme\StoreBundle\Entity\Product;

use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller

{

public function createProductAction()

{

$category = new Category();

$category->setName('Main Products');

$product = new Product();

$product->setName('Foo');

$product->setPrice(19.99);

// relate this product to the category

$product->setCategory($category);

$em = $this->getDoctrine()->getManager();

$em->persist($category);

$em->persist($product);

$em->flush();

return new Response(

'Created product id: '.$product->getId().' and category id: '.$category->getId()

);

}

}

Fetching related Objectspublic function showAction($id)

{

$product = $this->getDoctrine()

->getRepository('AcmeStoreBundle:Product')

->find($id);

$categoryName = $product->getCategory()->getName();

// ...

}

Fetching related Objects

Fetching related Objectspublic function showProductAction($id)

{

$category = $this->getDoctrine()

->getRepository('AcmeStoreBundle:Category')

->find($id);

$products = $category->getProducts();

// ...

}

Doctrine Field Types Reference Strings

string (used for shorter strings)

text (used for larger strings)

Numbers

integer

smallint

bigint

decimal

float

Dates and Times (use a DateTime object for these fields in PHP)

date

time

datetime

Other Types

boolean

object (serialized and stored in a CLOB field)

array (serialized and stored in a CLOB field)

Some advanced stuff Events and lifecycle callbacks

preRemove

postRemove

prePersist

postPersist

preUpdate

postUpdate

postLoad

LoadClassMetadata

Doctrine Extensions: Timestampable, Sluggable, etc.

MODELPractice #3:

- Create entities for schema bellow and create empty custom repository for each, use XML

Time: 15 minutes

Download

PPT: Www.locastic.com/ez2013/index.htmlOr bitly.com/ez2013

Views And templatesWww.locastic.com/ez2013/view.zip

CRUD→ create bundle

CRUD→ create bundle→ create entity

CRUD→ create bundle→ create entity→ create full CRUD* with routes, controllers and templates

CRUD→ create bundle→ create entity→ create full CRUD with routes, controllers and templates→ use console :)

FORMS AND VALIDATION

Workshop: Symfony2 Forms – tomorrow 09:00amBernhard Schussek

The Symfony2 Form component helps you to build powerful forms with little code. This workshop shows you how to use the component in your daily life.

Template

Twig!!! FTW!!!

Skipper: Luka Vidoš

Questions?

Thank you