SlideShare a Scribd company logo
1 of 68
Download to read offline
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]
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

More Related Content

What's hot

Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web servicesMichelangelo van Dam
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteLeonardo Proietti
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersKacper Gunia
 
Error Reporting in ZF2: form messages, custom error pages, logging
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
 
Rich domain model with symfony 2.5 and doctrine 2.5
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
 
November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2Kacper Gunia
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICKonstantin Kudryashov
 
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!
Forget about index.php and build you applications around HTTP!Kacper Gunia
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in actionJace Ju
 
Your code sucks, let's fix it - PHP Master Series 2012
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 2012Rafael 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)
Your code sucks, let's fix it (CakeFest2012)Rafael Dohms
 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From IusethisMarcus Ramberg
 
PhpSpec 2.0 ilustrated by examples
PhpSpec 2.0 ilustrated by examplesPhpSpec 2.0 ilustrated by examples
PhpSpec 2.0 ilustrated by examplesMarcello Duarte
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful softwareJorn Oomen
 
“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
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonfRafael Dohms
 
Your code sucks, let's fix it
Your code sucks, let's fix itYour code sucks, let's fix it
Your code sucks, let's fix itRafael Dohms
 

What's hot (20)

Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
 
Error Reporting in ZF2: form messages, custom error pages, logging
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
 
Rich domain model with symfony 2.5 and doctrine 2.5
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
 
November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
 
Wp query
Wp queryWp query
Wp query
 
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!
Forget about index.php and build you applications around HTTP!
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
 
Your code sucks, let's fix it - PHP Master Series 2012
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
 
Crafting [Better] API Clients
Crafting [Better] API ClientsCrafting [Better] API Clients
Crafting [Better] API Clients
 
Your code sucks, let's fix it (CakeFest2012)
Your code sucks, let's fix it (CakeFest2012)Your code sucks, let's fix it (CakeFest2012)
Your code sucks, let's fix it (CakeFest2012)
 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From Iusethis
 
PhpSpec 2.0 ilustrated by examples
PhpSpec 2.0 ilustrated by examplesPhpSpec 2.0 ilustrated by examples
PhpSpec 2.0 ilustrated by examples
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
 
“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
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
 
Your code sucks, let's fix it
Your code sucks, let's fix itYour code sucks, let's fix it
Your code sucks, let's fix it
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 

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

Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
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
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your CodeAbbas Ali
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Shinya Ohyanagi
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & RESTHugo Hamon
 
You code sucks, let's fix it
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix itRafael Dohms
 
Your code sucks, let's fix it - DPC UnCon
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 UnConRafael Dohms
 
Top 5 Magento Secure Coding Best Practices
Top 5 Magento Secure Coding Best PracticesTop 5 Magento Secure Coding Best Practices
Top 5 Magento Secure Coding Best PracticesOleksandr Zarichnyi
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015Fernando Daciuk
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hackingJeroen van Dijk
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutesBarang CK
 
Add edit delete in Codeigniter in PHP
Add edit delete in Codeigniter in PHPAdd edit delete in Codeigniter in PHP
Add edit delete in Codeigniter in PHPVineet Kumar Saini
 
PHP and Rich Internet Applications
PHP and Rich Internet ApplicationsPHP and Rich Internet Applications
PHP and Rich Internet Applicationselliando dias
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hackingJeroen van Dijk
 
WordPress as an application framework
WordPress as an application frameworkWordPress as an application framework
WordPress as an application frameworkDustin Filippini
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For BeginnersJonathan Wage
 

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
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
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
You code sucks, let's fix it
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix it
 
Your code sucks, let's fix it - DPC UnCon
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
 
Zero to SOLID
Zero to SOLIDZero to SOLID
Zero to SOLID
 
Top 5 Magento Secure Coding Best Practices
Top 5 Magento Secure Coding Best PracticesTop 5 Magento Secure Coding Best Practices
Top 5 Magento Secure Coding Best Practices
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutes
 
Add edit delete in Codeigniter in PHP
Add edit delete in Codeigniter in PHPAdd edit delete in Codeigniter in PHP
Add edit delete in Codeigniter in PHP
 
PHP and Rich Internet Applications
PHP and Rich Internet ApplicationsPHP and Rich Internet Applications
PHP and Rich Internet Applications
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
 
WordPress as an application framework
WordPress as an application frameworkWordPress as an application framework
WordPress as an application framework
 
Bacbkone js
Bacbkone jsBacbkone js
Bacbkone js
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 

Recently uploaded

"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr LapshynFwdays
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024Scott Keck-Warren
 
APIForce Zurich 5 April Automation LPDG
APIForce Zurich 5 April  Automation LPDGAPIForce Zurich 5 April  Automation LPDG
APIForce Zurich 5 April Automation LPDGMarianaLemus7
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
 
Science&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdfScience&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdfjimielynbastida
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksSoftradix Technologies
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
costume and set research powerpoint presentation
costume and set research powerpoint presentationcostume and set research powerpoint presentation
costume and set research powerpoint presentationphoebematthew05
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptxLBM Solutions
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
Transcript: New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024BookNet Canada
 

Recently uploaded (20)

"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024
 
APIForce Zurich 5 April Automation LPDG
APIForce Zurich 5 April  Automation LPDGAPIForce Zurich 5 April  Automation LPDG
APIForce Zurich 5 April Automation LPDG
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
 
Science&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdfScience&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdf
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other Frameworks
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
costume and set research powerpoint presentation
costume and set research powerpoint presentationcostume and set research powerpoint presentation
costume and set research powerpoint presentation
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptx
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
Transcript: New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
 

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]
  • 56.
  • 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