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

Baldur Rensch
Baldur RenschSoftware Architect at HauteLook
Moving a ZF1 Application to SF2
-
Lessons learned
Baldur Rensch
Agenda
About me
What is Hautelook
HAL
Before
Switching to Symfony
Lessons Learned
About me
architecting at Hautelook
contributing on GitHub: @baldurrensch
opinionating on twitter: @brensch
What is ?
Member only
shopping site
Private, limited-
time sale events
daily email
invitation at 8am
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]
[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
Our Technologies
Our stack
API
Website Admin
Mobile Clients
(iOS / Android)
HAL+JSON HAL+JSON HAL+JSON
[13, 14]
HAL
[3]
Zend for
thee!
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
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;
}
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;
}
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
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);
}
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 model
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);
}
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);
}
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
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
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!
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.
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.”
[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
Let’s use Symfony
instead, shall we?
[2]
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.
Bundles we use
Friends of Symphony: RestBundle
Nelmio: ApiDocBundle, SolariumBundle
JMS: SerializerBundle
Football Social Club: HateoasBundle
Hautelook: GearmanBundle
/**
* 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
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;
}
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;
}
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;
}
Controller
Service
Model/
View
Create
response
/**
* 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;
}
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
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;
}
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;
}
use JMSSerializerAnnotation as JMS;
use FSCHateoasBundleAnnotation as Rest;
/**
* @author Baldur Rensch <baldur.rensch@hautelook.com>
*
* @RestRelation("self",
* href = @RestRoute("hautelook_api_cart_getcart",
* parameters = { "memberId": ".member.memberId" }
* )
* )
* @RestRelation("http://hautelook.com/rels/cart/item",
* href = @RestRoute("hautelook_api_cart_getcart",
* parameters = { "memberId": ".member.memberId" }
* ),
* embed = @RestContent(
* property = ".cartItems"
* )
* )
*/
class Cart
Controller
Service
Model/
View
use JMSSerializerAnnotation as JMS;
use FSCHateoasBundleAnnotation as Rest;
/**
* @author Baldur Rensch <baldur.rensch@hautelook.com>
*
* @RestRelation("self",
* href = @RestRoute("hautelook_api_cart_getcart",
* parameters = { "memberId": ".member.memberId" }
* )
* )
* @RestRelation("http://hautelook.com/rels/cart/item",
* href = @RestRoute("hautelook_api_cart_getcart",
* parameters = { "memberId": ".member.memberId" }
* ),
* embed = @RestContent(
* property = ".cartItems"
* )
* )
*/
class Cart
Link
Controller
Service
Model/
View
use JMSSerializerAnnotation as JMS;
use FSCHateoasBundleAnnotation as Rest;
/**
* @author Baldur Rensch <baldur.rensch@hautelook.com>
*
* @RestRelation("self",
* href = @RestRoute("hautelook_api_cart_getcart",
* parameters = { "memberId": ".member.memberId" }
* )
* )
* @RestRelation("http://hautelook.com/rels/cart/item",
* href = @RestRoute("hautelook_api_cart_getcart",
* parameters = { "memberId": ".member.memberId" }
* ),
* embed = @RestContent(
* property = ".cartItems"
* )
* )
*/
class Cart
Embedded
Controller
Service
Model/
View
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
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
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
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}"
URI Templates are sexy
/demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*}
/**
* @RestRelation("http://hautelook.com/rels/image/resizable",
* href = @RestRoute("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
Documentation with NelmioAPIDocBundle
Yeay,
RESTful :)
Documentation with NelmioAPIDocBundle
HTML form -
makes testing
easy
Documentation with NelmioAPIDocBundle
Documentation with NelmioAPIDocBundle
Measuring performance with declarative
programming
Why its difficult
[8, 9, 10]
Measuring performance with declarative
programming
use JMSSerializerAnnotation as JMS;
use FSCHateoasBundleAnnotation as Rest;
/**
* @author Baldur Rensch <baldur.rensch@hautelook.com>
*
* @RestRelation("self",
* href = @RestRoute("hautelook_api_cart_getcart",
* parameters = { "memberId": ".member.memberId" }
* )
* )
* @RestRelation("http://hautelook.com/rels/cart/item",
* href = @RestRoute("hautelook_api_cart_getcart",
* parameters = { "memberId": ".member.memberId" }
* ),
* embed = @RestContent(
* property = ".cartItems"
* )
* )
*/
class Cart
Why its difficult
[8, 9, 10]
Measuring performance with declarative
programming
Why its difficult
XHProf to the rescue
[8, 9, 10]
Measuring performance with declarative
programming
Why its difficult
XHProf to the rescue
Hierarchical function-level profiler
[8, 9, 10]
Measuring performance with declarative
programming
Why its difficult
XHProf to the rescue
Hierarchical function-level profiler
PHP Extension
[8, 9, 10]
Measuring performance with declarative
programming
Why its difficult
XHProf to the rescue
Hierarchical function-level profiler
PHP Extension
Written by Facebook
[8, 9, 10]
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]
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]
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]
Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned
Which functions
are most
expensive?
How often was a
function called,
how long did it
take?
WTF?
Lessons learned
Large scale Symfony deployments are not that
common
Lessons learned
Large scale Symfony deployments are not that
common
Lessons learned
Large scale Symfony deployments are not that
common
A lot of modules that larger applications need don’t
exist
Lessons learned
Large 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
Lessons learned
Large 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]
Lessons learned
Large 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
Lessons learned
Large 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
Questions?
Let’s get in touch for feedback, questions, discussion
Leave feedback at: https://joind.in/8676
Connect on Twitter or Github.
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
1 of 68

Recommended

Framework by
FrameworkFramework
FrameworkNguyen Linh
660 views28 slides
50 Laravel Tricks in 50 Minutes by
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 MinutesAzim Kurtaliev
1.7K views83 slides
購物車程式架構簡介 by
購物車程式架構簡介購物車程式架構簡介
購物車程式架構簡介Jace Ju
19.7K views171 slides
Min-Maxing Software Costs - Laracon EU 2015 by
Min-Maxing Software Costs - Laracon EU 2015Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Konstantin Kudryashov
14.2K views95 slides
Disregard Inputs, Acquire Zend_Form by
Disregard Inputs, Acquire Zend_FormDisregard Inputs, Acquire Zend_Form
Disregard Inputs, Acquire Zend_FormDaniel Cousineau
1.7K views44 slides
Frontin like-a-backer by
Frontin like-a-backerFrontin like-a-backer
Frontin like-a-backerFrank de Jonge
10.6K views91 slides

More Related Content

What's hot

Introduction to Zend Framework web services by
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web servicesMichelangelo van Dam
4.9K views50 slides
Symfony2, creare bundle e valore per il cliente by
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteLeonardo Proietti
1.3K views73 slides
PHPSpec - the only Design Tool you need - 4Developers by
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersKacper Gunia
37.3K views137 slides
Error Reporting in ZF2: form messages, custom error pages, logging by
Error Reporting in ZF2: form messages, custom error pages, loggingError Reporting in ZF2: form messages, custom error pages, logging
Error Reporting in ZF2: form messages, custom error pages, loggingSteve Maraspin
17.8K views136 slides
Rich domain model with symfony 2.5 and doctrine 2.5 by
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Leonardo Proietti
12K views90 slides
November Camp - Spec BDD with PHPSpec 2 by
November Camp - Spec BDD with PHPSpec 2November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2Kacper Gunia
5.4K views73 slides

What's hot(20)

Symfony2, creare bundle e valore per il cliente by Leonardo Proietti
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
Leonardo Proietti1.3K views
PHPSpec - the only Design Tool you need - 4Developers by Kacper Gunia
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
Kacper Gunia37.3K views
Error Reporting in ZF2: form messages, custom error pages, logging by Steve Maraspin
Error Reporting in ZF2: form messages, custom error pages, loggingError Reporting in ZF2: form messages, custom error pages, logging
Error Reporting in ZF2: form messages, custom error pages, logging
Steve Maraspin17.8K views
Rich domain model with symfony 2.5 and doctrine 2.5 by Leonardo Proietti
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
Leonardo Proietti12K views
November Camp - Spec BDD with PHPSpec 2 by Kacper Gunia
November Camp - Spec BDD with PHPSpec 2November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2
Kacper Gunia5.4K views
Forget about index.php and build you applications around HTTP! by Kacper Gunia
Forget about index.php and build you applications around HTTP!Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!
Kacper Gunia5.7K views
Advanced php testing in action by Jace Ju
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
Jace Ju2.1K views
Your code sucks, let's fix it - PHP Master Series 2012 by Rafael Dohms
Your code sucks, let's fix it - PHP Master Series 2012Your code sucks, let's fix it - PHP Master Series 2012
Your code sucks, let's fix it - PHP Master Series 2012
Rafael Dohms34.5K views
Your code sucks, let's fix it (CakeFest2012) by Rafael Dohms
Your code sucks, let's fix it (CakeFest2012)Your code sucks, let's fix it (CakeFest2012)
Your code sucks, let's fix it (CakeFest2012)
Rafael Dohms24.7K views
Bag Of Tricks From Iusethis by Marcus Ramberg
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From Iusethis
Marcus Ramberg6.4K views
PhpSpec 2.0 ilustrated by examples by Marcello Duarte
PhpSpec 2.0 ilustrated by examplesPhpSpec 2.0 ilustrated by examples
PhpSpec 2.0 ilustrated by examples
Marcello Duarte13.2K views
Crafting beautiful software by Jorn Oomen
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
Jorn Oomen2K views
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf by Rafael Dohms
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
Rafael Dohms3.9K views
Your code sucks, let's fix it by Rafael Dohms
Your code sucks, let's fix itYour code sucks, let's fix it
Your code sucks, let's fix it
Rafael Dohms36.3K views

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

Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need by
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needKacper Gunia
50.4K views140 slides
Tidy Up Your Code by
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your CodeAbbas Ali
3 views52 slides
Zend framework service by
Zend framework serviceZend framework service
Zend framework serviceMichelangelo van Dam
803 views37 slides
Zend framework service by
Zend framework serviceZend framework service
Zend framework serviceMichelangelo van Dam
1.3K views37 slides
Zend Framework Study@Tokyo #2 by
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Shinya Ohyanagi
1.3K views68 slides
Silex meets SOAP & REST by
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & RESTHugo Hamon
14.8K views62 slides

Similar to Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned(20)

Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need by Kacper Gunia
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Kacper Gunia50.4K views
Tidy Up Your Code by Abbas Ali
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
Abbas Ali3 views
Zend Framework Study@Tokyo #2 by Shinya Ohyanagi
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2
Shinya Ohyanagi1.3K views
Silex meets SOAP & REST by Hugo Hamon
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
Hugo Hamon14.8K views
You code sucks, let's fix it by Rafael Dohms
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix it
Rafael Dohms56.4K views
Your code sucks, let's fix it - DPC UnCon by Rafael Dohms
Your code sucks, let's fix it - DPC UnConYour code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnCon
Rafael Dohms32.5K views
WordPress Realtime - WordCamp São Paulo 2015 by Fernando Daciuk
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015
Fernando Daciuk3.7K views
laravel tricks in 50minutes by Barang CK
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutes
Barang CK393 views
PHP and Rich Internet Applications by elliando dias
PHP and Rich Internet ApplicationsPHP and Rich Internet Applications
PHP and Rich Internet Applications
elliando dias2.8K views
WordPress as an application framework by Dustin Filippini
WordPress as an application frameworkWordPress as an application framework
WordPress as an application framework
Dustin Filippini416 views
Doctrine For Beginners by Jonathan Wage
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
Jonathan Wage1.5K views

Recently uploaded

DALI Basics Course 2023 by
DALI Basics Course  2023DALI Basics Course  2023
DALI Basics Course 2023Ivory Egg
14 views12 slides
Spesifikasi Lengkap ASUS Vivobook Go 14 by
Spesifikasi Lengkap ASUS Vivobook Go 14Spesifikasi Lengkap ASUS Vivobook Go 14
Spesifikasi Lengkap ASUS Vivobook Go 14Dot Semarang
35 views1 slide
Digital Product-Centric Enterprise and Enterprise Architecture - Tan Eng Tsze by
Digital Product-Centric Enterprise and Enterprise Architecture - Tan Eng TszeDigital Product-Centric Enterprise and Enterprise Architecture - Tan Eng Tsze
Digital Product-Centric Enterprise and Enterprise Architecture - Tan Eng TszeNUS-ISS
19 views47 slides
How to reduce cold starts for Java Serverless applications in AWS at JCON Wor... by
How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...
How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...Vadym Kazulkin
70 views64 slides
Emerging & Future Technology - How to Prepare for the Next 10 Years of Radica... by
Emerging & Future Technology - How to Prepare for the Next 10 Years of Radica...Emerging & Future Technology - How to Prepare for the Next 10 Years of Radica...
Emerging & Future Technology - How to Prepare for the Next 10 Years of Radica...NUS-ISS
15 views28 slides
Uni Systems for Power Platform.pptx by
Uni Systems for Power Platform.pptxUni Systems for Power Platform.pptx
Uni Systems for Power Platform.pptxUni Systems S.M.S.A.
38 views21 slides

Recently uploaded(20)

DALI Basics Course 2023 by Ivory Egg
DALI Basics Course  2023DALI Basics Course  2023
DALI Basics Course 2023
Ivory Egg14 views
Spesifikasi Lengkap ASUS Vivobook Go 14 by Dot Semarang
Spesifikasi Lengkap ASUS Vivobook Go 14Spesifikasi Lengkap ASUS Vivobook Go 14
Spesifikasi Lengkap ASUS Vivobook Go 14
Dot Semarang35 views
Digital Product-Centric Enterprise and Enterprise Architecture - Tan Eng Tsze by NUS-ISS
Digital Product-Centric Enterprise and Enterprise Architecture - Tan Eng TszeDigital Product-Centric Enterprise and Enterprise Architecture - Tan Eng Tsze
Digital Product-Centric Enterprise and Enterprise Architecture - Tan Eng Tsze
NUS-ISS19 views
How to reduce cold starts for Java Serverless applications in AWS at JCON Wor... by Vadym Kazulkin
How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...
How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...
Vadym Kazulkin70 views
Emerging & Future Technology - How to Prepare for the Next 10 Years of Radica... by NUS-ISS
Emerging & Future Technology - How to Prepare for the Next 10 Years of Radica...Emerging & Future Technology - How to Prepare for the Next 10 Years of Radica...
Emerging & Future Technology - How to Prepare for the Next 10 Years of Radica...
NUS-ISS15 views
The details of description: Techniques, tips, and tangents on alternative tex... by BookNet Canada
The details of description: Techniques, tips, and tangents on alternative tex...The details of description: Techniques, tips, and tangents on alternative tex...
The details of description: Techniques, tips, and tangents on alternative tex...
BookNet Canada110 views
The Importance of Cybersecurity for Digital Transformation by NUS-ISS
The Importance of Cybersecurity for Digital TransformationThe Importance of Cybersecurity for Digital Transformation
The Importance of Cybersecurity for Digital Transformation
NUS-ISS25 views
Voice Logger - Telephony Integration Solution at Aegis by Nirmal Sharma
Voice Logger - Telephony Integration Solution at AegisVoice Logger - Telephony Integration Solution at Aegis
Voice Logger - Telephony Integration Solution at Aegis
Nirmal Sharma17 views
TouchLog: Finger Micro Gesture Recognition Using Photo-Reflective Sensors by sugiuralab
TouchLog: Finger Micro Gesture Recognition  Using Photo-Reflective SensorsTouchLog: Finger Micro Gesture Recognition  Using Photo-Reflective Sensors
TouchLog: Finger Micro Gesture Recognition Using Photo-Reflective Sensors
sugiuralab11 views
AMAZON PRODUCT RESEARCH.pdf by JerikkLaureta
AMAZON PRODUCT RESEARCH.pdfAMAZON PRODUCT RESEARCH.pdf
AMAZON PRODUCT RESEARCH.pdf
JerikkLaureta14 views
STPI OctaNE CoE Brochure.pdf by madhurjyapb
STPI OctaNE CoE Brochure.pdfSTPI OctaNE CoE Brochure.pdf
STPI OctaNE CoE Brochure.pdf
madhurjyapb12 views
AI: mind, matter, meaning, metaphors, being, becoming, life values by Twain Liu 刘秋艳
AI: mind, matter, meaning, metaphors, being, becoming, life valuesAI: mind, matter, meaning, metaphors, being, becoming, life values
AI: mind, matter, meaning, metaphors, being, becoming, life values
SAP Automation Using Bar Code and FIORI.pdf by Virendra Rai, PMP
SAP Automation Using Bar Code and FIORI.pdfSAP Automation Using Bar Code and FIORI.pdf
SAP Automation Using Bar Code and FIORI.pdf
Business Analyst Series 2023 - Week 3 Session 5 by DianaGray10
Business Analyst Series 2023 -  Week 3 Session 5Business Analyst Series 2023 -  Week 3 Session 5
Business Analyst Series 2023 - Week 3 Session 5
DianaGray10165 views
Data-centric AI and the convergence of data and model engineering: opportunit... by Paolo Missier
Data-centric AI and the convergence of data and model engineering:opportunit...Data-centric AI and the convergence of data and model engineering:opportunit...
Data-centric AI and the convergence of data and model engineering: opportunit...
Paolo Missier29 views

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

  • 1. Moving a ZF1 Application to SF2 - Lessons learned Baldur Rensch
  • 2. Agenda About me What is Hautelook HAL Before Switching to Symfony Lessons Learned
  • 3. About me architecting at Hautelook contributing on GitHub: @baldurrensch opinionating on twitter: @brensch
  • 4. What is ? Member only shopping site Private, limited- time sale events daily email invitation at 8am
  • 5. 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]
  • 6. [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
  • 8. Our stack API Website Admin Mobile Clients (iOS / Android) HAL+JSON HAL+JSON HAL+JSON [13, 14]
  • 11. 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
  • 12. 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; }
  • 13. 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; }
  • 14. 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
  • 15. 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); }
  • 16. 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 model 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); }
  • 17. 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); }
  • 18. 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
  • 19. 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
  • 20. 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!
  • 21. 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.
  • 22. 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.”
  • 23. [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
  • 25. 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.
  • 26. Bundles we use Friends of Symphony: RestBundle Nelmio: ApiDocBundle, SolariumBundle JMS: SerializerBundle Football Social Club: HateoasBundle Hautelook: GearmanBundle
  • 27. /** * 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
  • 28. 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; }
  • 29. 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; }
  • 30. 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; }
  • 31. Controller Service Model/ View Create response /** * 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; }
  • 32. 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
  • 33. 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; }
  • 34. 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; }
  • 35. use JMSSerializerAnnotation as JMS; use FSCHateoasBundleAnnotation as Rest; /** * @author Baldur Rensch <baldur.rensch@hautelook.com> * * @RestRelation("self", * href = @RestRoute("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @RestRelation("http://hautelook.com/rels/cart/item", * href = @RestRoute("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @RestContent( * property = ".cartItems" * ) * ) */ class Cart Controller Service Model/ View
  • 36. use JMSSerializerAnnotation as JMS; use FSCHateoasBundleAnnotation as Rest; /** * @author Baldur Rensch <baldur.rensch@hautelook.com> * * @RestRelation("self", * href = @RestRoute("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @RestRelation("http://hautelook.com/rels/cart/item", * href = @RestRoute("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @RestContent( * property = ".cartItems" * ) * ) */ class Cart Link Controller Service Model/ View
  • 37. use JMSSerializerAnnotation as JMS; use FSCHateoasBundleAnnotation as Rest; /** * @author Baldur Rensch <baldur.rensch@hautelook.com> * * @RestRelation("self", * href = @RestRoute("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @RestRelation("http://hautelook.com/rels/cart/item", * href = @RestRoute("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @RestContent( * property = ".cartItems" * ) * ) */ class Cart Embedded Controller Service Model/ View
  • 38. 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
  • 39. 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
  • 40. 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
  • 41. 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}"
  • 42. URI Templates are sexy /demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*} /** * @RestRelation("http://hautelook.com/rels/image/resizable", * href = @RestRoute("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
  • 45. HTML form - makes testing easy Documentation with NelmioAPIDocBundle
  • 47. Measuring performance with declarative programming Why its difficult [8, 9, 10]
  • 48. Measuring performance with declarative programming use JMSSerializerAnnotation as JMS; use FSCHateoasBundleAnnotation as Rest; /** * @author Baldur Rensch <baldur.rensch@hautelook.com> * * @RestRelation("self", * href = @RestRoute("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @RestRelation("http://hautelook.com/rels/cart/item", * href = @RestRoute("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @RestContent( * property = ".cartItems" * ) * ) */ class Cart Why its difficult [8, 9, 10]
  • 49. Measuring performance with declarative programming Why its difficult XHProf to the rescue [8, 9, 10]
  • 50. Measuring performance with declarative programming Why its difficult XHProf to the rescue Hierarchical function-level profiler [8, 9, 10]
  • 51. Measuring performance with declarative programming Why its difficult XHProf to the rescue Hierarchical function-level profiler PHP Extension [8, 9, 10]
  • 52. Measuring performance with declarative programming Why its difficult XHProf to the rescue Hierarchical function-level profiler PHP Extension Written by Facebook [8, 9, 10]
  • 53. 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]
  • 54. 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]
  • 55. 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]
  • 58. How often was a function called, how long did it take?
  • 59. WTF?
  • 60. Lessons learned Large scale Symfony deployments are not that common
  • 61. Lessons learned Large scale Symfony deployments are not that common
  • 62. Lessons learned Large scale Symfony deployments are not that common A lot of modules that larger applications need don’t exist
  • 63. Lessons learned Large 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
  • 64. Lessons learned Large 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]
  • 65. Lessons learned Large 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
  • 66. Lessons learned Large 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
  • 67. Questions? Let’s get in touch for feedback, questions, discussion Leave feedback at: https://joind.in/8676 Connect on Twitter or Github.
  • 68. 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