Download - Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Transcript
Page 1: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Moving a ZF1 Application to SF2 -

Lessons learned

Baldur Rensch

Page 2: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Agenda

About me

What is Hautelook

HAL

Before

Switching to Symfony

Lessons Learned

Page 3: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

About me

architecting at Hautelook

contributing on GitHub: @baldurrensch

opinionating on twitter: @brensch

Page 4: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

What is ?Member only shopping site

Private, limited-time sale events

daily email invitation at 8am

Page 5: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Some stats

Alexa traffic rank for US: 847

More than 12 million members (on average 20k new members per day)

Up to 200 orders per minute

Massive traffic spikes (remember, 8am)

[1]

Page 6: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

[1]

Some stats

Alexa traffic rank for US: 847

More than 12 million members (on average 20k new members per day)

Up to 200 orders per minute

Massive traffic spikes (remember, 8am) daily

Page 7: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Our Technologies

Page 8: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Our stack

API

Website Admin Mobile Clients (iOS / Android)

HAL+JSON HAL+JSON HAL+JSON

[13, 14]

Page 9: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

HAL

[3]

Page 10: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Zend for thee!

Page 11: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

View

Controller

Service

Model

Page 12: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

View

Controller

Service

Model

Call the service

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

Page 13: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

View

Controller

Service

Model

Prepare for response

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

Page 14: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);} View

Controller

Service

Model

Page 15: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

View

Controller

Service

Model

Input Validation

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

Page 16: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

View

Controller

Service

Model

Call modelpublic function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

Page 17: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

View

Controller

Service

Model

Prepare for response

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

Page 18: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

<?php

class V4_Model_CartItems{ public function itemsForMember($member_id) { $db = Zend_Registry::get('db');

$q = <<<EOT SELECT cart_id, cart_items.event_id, cart_items.sku, quantity, cart_item_status, expires_at, (...)EOT;

$result = $db->fetchAll($q, $member_id);

return $result; }}

View

Controller

Service

Model

Run some SQL

Page 19: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

View

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

<?php

class V4_Model_CartItems{ public function itemsForMember($member_id) { $db = Zend_Registry::get('db');

$q = <<<EOT SELECT cart_id, cart_items.event_id, cart_items.sku, quantity, cart_item_status, expires_at, (...)EOT;

$result = $db->fetchAll($q, $member_id);

return $result; }}

protected function modifyData(Halo_Response $service_response){ $member_id = $this->member_id; $data = $service_response->getData();

$items = $data['items']; unset($data['items']);

$cart = new Hal_Resource("/v4/members/{$member_id}/cart", $data); $cart->setLink(new Hal_Link("/v4/members/{$member_id}/checkout", 'http://hautelook.com/rels/checkout'));

foreach ($items as $item) { $event_id = $item['sku']['event_id']; $color = $item['sku']['color']; $expires = new DateTime($item['expires_at']);

$cart_data = array( 'quantity' => (int) $item['quantity'], (...) );

$sku_data = array( 'event_id' => $event_id, (...) );

if (isset($item['style']['images'][strtolower($color)])) { $images = $item['style']['images'][strtolower($color)]['angles']; } $image_link = $images[0]['zoom'];

$style = new Hal_Resource("/v4/events/{$event_id}/styles/{$item['style_num']}", $style_data);

$r = new Hal_Resource("/v4/members/{$member_id}/cart/{$item['cart_id']}", $cart_data);

$style->setEmbedded('http://hautelook.com/rels/sku', $sku); $style->setLink(new Hal_Link($image_link, 'http://hautelook.com/rels/images/style/zoom')); $r->setEmbedded('http://hautelook.com/rels/styles', $style);

$cart->setEmbedded('http://hautelook.com/rels/cart-items', $r); }

$service_response->success($cart->toArray());}

Controller

Service

Model

Page 20: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

View

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

<?php

class V4_Model_CartItems{ public function itemsForMember($member_id) { $db = Zend_Registry::get('db');

$q = <<<EOT SELECT cart_id, cart_items.event_id, cart_items.sku, quantity, cart_item_status, expires_at, (...)EOT;

$result = $db->fetchAll($q, $member_id);

return $result; }}

protected function modifyData(Halo_Response $service_response){ $member_id = $this->member_id; $data = $service_response->getData();

$items = $data['items']; unset($data['items']);

$cart = new Hal_Resource("/v4/members/{$member_id}/cart", $data); $cart->setLink(new Hal_Link("/v4/members/{$member_id}/checkout", 'http://hautelook.com/rels/checkout'));

foreach ($items as $item) { $event_id = $item['sku']['event_id']; $color = $item['sku']['color']; $expires = new DateTime($item['expires_at']);

$cart_data = array( 'quantity' => (int) $item['quantity'], (...) );

$sku_data = array( 'event_id' => $event_id, (...) );

if (isset($item['style']['images'][strtolower($color)])) { $images = $item['style']['images'][strtolower($color)]['angles']; } $image_link = $images[0]['zoom'];

$style = new Hal_Resource("/v4/events/{$event_id}/styles/{$item['style_num']}", $style_data);

$r = new Hal_Resource("/v4/members/{$member_id}/cart/{$item['cart_id']}", $cart_data);

$style->setEmbedded('http://hautelook.com/rels/sku', $sku); $style->setLink(new Hal_Link($image_link, 'http://hautelook.com/rels/images/style/zoom')); $r->setEmbedded('http://hautelook.com/rels/styles', $style);

$cart->setEmbedded('http://hautelook.com/rels/cart-items', $r); }

$service_response->success($cart->toArray());}

Controller

Service

Model

Convert array results to HAL+Json,

yuck!

Page 21: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Issues

This is fine when you have 5 end points and simple responses.

Lots of boiler plate code

Zend Framework 1 did not scale very well. We constantly had to overwrite parts of the framework.

Page 22: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Moving from Imperative to Declarative Programming

[4,5]

Imperative Declarative

“In computer science, imperative programming is a programming paradigm that

describes computation in terms of statements that change a program state.”

“In computer science, declarative programming is a programming paradigm that

expresses the logic of a computation without

describing its control flow.”

Page 23: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

[4,5]

Imperative Declarative

“In computer science, imperative programming is a programming paradigm that

describes computation in terms of statements that change a program state.”

“In computer science, declarative programming is a programming paradigm that

expresses the logic of a computation without

describing its control flow.”

Moving from Imperative to Declarative Programming

Page 24: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Let’s use Symfony instead, shall we?

[2]

Page 25: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Advantages:

Symfony allows for way more declarative programming which allows us to write less code.

Allows us to extend way easier. And it’s actually fun.

Community is great.

Page 26: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Bundles we use

Friends of Symphony: RestBundle

Nelmio: ApiDocBundle, SolariumBundle

JMS: SerializerBundle

Football Social Club: HateoasBundle

Hautelook: GearmanBundle

Page 27: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

/** * This function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */public function getCartAction($memberId){ $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member);

$response = $this->get('serializer')->serialize($cart, 'json');

$response = new Response($response); $response->headers->set('Content-Type', 'application/json'); $response->setETag(md5($response->getContent()));

return $response;}

Controller

Service

Model/View

Page 28: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/View

Routing, Input Validation

/** * This function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */public function getCartAction($memberId){ $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member);

$response = $this->get('serializer')->serialize($cart, 'json');

$response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent()));

return $response;}

Page 29: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/View

Documentation

/** * This function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */public function getCartAction($memberId){ $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member);

$response = $this->get('serializer')->serialize($cart, 'json');

$response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent()));

return $response;}

Page 30: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/View

Call Service to get data

/** * This function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */public function getCartAction($memberId){ $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member);

$response = $this->get('serializer')->serialize($cart, 'json');

$response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent()));

return $response;}

Page 31: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/View

Createresponse

/** * This function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */public function getCartAction($memberId){ $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member);

$response = $this->get('serializer')->serialize($cart, 'json');

$response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent()));

return $response;}

Page 32: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

public function getCart(Members $member){ $cartItems = $this->doctrine->getRepository("HautelookApiBundle:CartItems") ->getCartItems($member->getMemberId());

$cartItemArray = array(); foreach ($cartItems as $cartItem) { $cartItemArray []= new CartItem($cartItem); } $cart = new CartModel($member, $cartItemArray);

return $cart;}

Controller

Service

Model/View

Page 33: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/View

Get entities from database

public function getCart(Members $member){ $cartItems = $this->doctrine->getRepository("HautelookApiBundle:CartItems") ->getCartItems($member->getMemberId());

$cartItemArray = array(); foreach ($cartItems as $cartItem) { $cartItemArray []= new CartItem($cartItem); } $cart = new CartModel($member, $cartItemArray);

return $cart;}

Page 34: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/ViewConvert to view

model

Get entities from database

public function getCart(Members $member){ $cartItems = $this->doctrine->getRepository("HautelookApiBundle:CartItems") ->getCartItems($member->getMemberId());

$cartItemArray = array(); foreach ($cartItems as $cartItem) { $cartItemArray []= new CartItem($cartItem); } $cart = new CartModel($member, $cartItemArray);

return $cart;}

Page 35: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

use JMS\Serializer\Annotation as JMS;use FSC\HateoasBundle\Annotation as Rest;

/** * @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */class Cart

Controller

Service

Model/View

Page 36: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

use JMS\Serializer\Annotation as JMS;use FSC\HateoasBundle\Annotation as Rest;

/** * @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */class Cart

Link

Controller

Service

Model/View

Page 37: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

use JMS\Serializer\Annotation as JMS;use FSC\HateoasBundle\Annotation as Rest;

/** * @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */class Cart

Embedded

Controller

Service

Model/View

Page 38: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

URI Templates are sexy

There is a RFC for it: RFC-6570

[6, 7, 12]

/demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*}

1 And are the magic that make Hateoas possible

1

Page 39: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

URI Templates are sexy/demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*}

There is a RFC for it: RFC-6570

There is a bundle for it™: TemplatedURIBundle

[6, 7, 12]1 And are the magic that make Hateoas possible

1

Page 40: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

URI Templates are sexy/demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*}

$templateLink = $this->get('hautelook.router.template')->generate('hautelook_demo_route', array( 'page' => '{page}', 'sort' => array('{sort}'), 'filter' => array('{filter}'), ));

There is a RFC for it: RFC-6570

There is a bundle for it™: TemplatedURIBundle

[6, 7, 12]1 And are the magic that make Hateoas possible

1

Page 41: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

URI Templates are sexy/demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*}

$templateLink = $this->get('hautelook.router.template')->generate('hautelook_demo_route', array( 'page' => '{page}', 'sort' => array('{sort}'), 'filter' => array('{filter}'), ));

There is a RFC for it: RFC-6570

There is a bundle for it™: TemplatedURIBundle

It even integrates with the HateoasBundle:

[6, 7, 12]1 And are the magic that make Hateoas possible

1

hautelook_style_image_resizable: pattern: /resizer/{width}x{height}/products/{styleNum}/{size}/{imageId}.jpg defaults: width: "{width}" height: "{height}"

Page 42: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

URI Templates are sexy/demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*}

/** * @Rest\Relation("http://hautelook.com/rels/image/resizable", * href = @Rest\Route("hautelook_style_image_resizable", * parameters = { "styleNum": ".solrDocument.styleNum", "imageId": ".firstImageId" }, * options = { "router": "templated" } * ), * excludeIf = { ".firstImageId": null }, * attributes = { "templated": true } * ) */

$templateLink = $this->get('hautelook.router.template')->generate('hautelook_demo_route', array( 'page' => '{page}', 'sort' => array('{sort}'), 'filter' => array('{filter}'), ));

There is a RFC for it: RFC-6570

There is a bundle for it™: TemplatedURIBundle

It even integrates with the HateoasBundle:

[6, 7, 12]1 And are the magic that make Hateoas possible

1

Page 43: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Documentation with NelmioAPIDocBundle

Page 44: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Yeay, RESTful :)

Documentation with NelmioAPIDocBundle

Page 45: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

HTML form -makes testing

easy

Documentation with NelmioAPIDocBundle

Page 46: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Documentation with NelmioAPIDocBundle

Page 47: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

[8, 9, 10]

Page 48: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

use JMS\Serializer\Annotation as JMS;use FSC\HateoasBundle\Annotation as Rest;

/** * @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */class Cart

Why its difficult

[8, 9, 10]

Page 49: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

[8, 9, 10]

Page 50: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

[8, 9, 10]

Page 51: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

PHP Extension

[8, 9, 10]

Page 52: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

PHP Extension

Written by Facebook

[8, 9, 10]

Page 53: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

PHP Extension

Written by Facebook

XHGui on top of XHProf

[8, 9, 10]

Page 54: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

PHP Extension

Written by Facebook

XHGui on top of XHProf

Uses a shared database backend

[8, 9, 10]

Page 55: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

PHP Extension

Written by Facebook

XHGui on top of XHProf

Uses a shared database backend

There is a bundle for that™ as well![8, 9, 10]

Page 56: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned
Page 57: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Which functions are most

expensive?

Page 58: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

How often was a function called, how long did it

take?

Page 59: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

WTF?

Page 60: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

Page 61: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

Page 62: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

A lot of modules that larger applications need don’t exist

Page 63: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

A lot of modules that larger applications need don’t exist

Example: Session storage in multiple storage layers such as: Memcached and Database

Page 64: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

A lot of modules that larger applications need don’t exist

Example: Session storage in multiple storage layers such as: Memcached and Database

There is a bundle for that™ now as well:

SessionStorageHandlerChainBundle

[11]

Page 65: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

A lot of modules that larger applications need don’t exist

Need more documentation / community around enterprise level Symfony development

Page 66: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

A lot of modules that larger applications need don’t exist

Need more documentation / community around enterprise level Symfony development

Our Developers love developing in Symfony

Page 67: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Questions?

Let’s get in touch for feedback, questions, discussion

Leave feedback at: https://joind.in/8676

Connect on Twitter or Github.

Page 68: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Sources[1] http://www.alexa.com/siteinfo/hautelook.com[2] http://fc08.deviantart.net/fs50/f/2009/280/3/c/And_Then_There_Was_Light_by_GTwerks.jpg[3] http://stateless.co/hal_specification.html[4] https://en.wikipedia.org/wiki/Declarative_programming[5] https://en.wikipedia.org/wiki/Imperative_programming[6] https://tools.ietf.org/html/rfc6570[7] https://github.com/hautelook/TemplatedUriBundle[8] https://github.com/facebook/xhprof[9] https://github.com/preinheimer/xhprof[10] https://github.com/jonaswouters/XhprofBundle[11] https://github.com/hautelook/SessionStorageHandlerChainBundle[12] https://github.com/fxa/uritemplate-js[13] https://play.google.com/store/apps/details?id=com.hautelook.mcom&hl=en[14] https://itunes.apple.com/us/app/hautelook/id390783984?mt=8