namespace
Application\Controller;
use
CleanPhp\Invoicer\Domain\Repository\OrderRepositoryInterface;
use
Zend\Mvc\Controller\AbstractActionController;
class
OrdersController
extends
AbstractActionController {
protected
$orderRepository
;
public function
__construct
(OrderRepositoryInterface
$orders
) {
$this
->
orderRepository
=
$orders
;
}
public function
indexAction
() {
return
[
'orders'
=>
$this
->
orderRepository
->
getAll
()
];
}
}
To use this controller, we’ll need to configure it in the controller service config. We can do so
right after the
Customers
controller definition:
// module/Application/config/module.config.php
return
[
// ...
'controllers'
=>
[
// ...
'Application\Controller\Orders'
=>
function
(
$sm
) {
return new
\Application\Controller\OrdersController(
$sm
->
getServiceLocator
()
->
get
(
'OrderTable'
)
);
},
Our Application in Zend Framework 2
154
],
// ...
];
Finally, let’s drop in a view file to render our list of orders:
module
/
Application
/
views
/
application
/
orders
/
index
.
php
-->
<
div class
=
"page-header clearfix"
>
<
h2 class
=
"pull-left"
>
Orders
h2
>
<
a href
=
"/orders/new"
class
=
"btn btn-success pull-right"
>
Create Order
a
>
div
>
<
table class
=
"table table-striped clearfix"
>
<
thead
>
<
tr
>
<
th
>
#
<
th
>
Order Number
th
>
<
th
>
Customer
th
>
<
th
>
Description
th
>
<
th class
=
"text-right"
>
Total
th
>
tr
>
thead
>
php
foreach
(
$this
->
orders
as
$order
)
:
?>
=
$this
->
escapeHtmlAttr
(
$order
->
getId
())
?>
">
=
$this
->
escapeHtml
(
$order
->
getId
())
?>
|
=
$this
->
escapeHtml
(
$order
->
getOrderNumber
())
?>
|
=
$this
->
escapeHtmlAttr
(
$order
->
getCustomer
()
->
getId
())
?>
">
=
$this
->
escapeHtml
(
$order
->
getCustomer
()
->
getName
())
?>
|
=
$this
->
escapeHtml
(
$order
->
getDescription
())
?>
|
$
=
number_format
(
$order
->
getTotal
(),
2
)
?>
|
endforeach
;
?>
If you refresh, you should see an empty grid! If we manually drop a couple orders in the database,
we should see some data show up. And a big fat error, because we’re trying to access the
Customer associated to the Order, but we haven’t actually hydrated one.
Our Application in Zend Framework 2
155
This is where we start to see the pitfalls of ZF2’s Data Table Gateway. But for the sake of getting
something done, let’s continue on.
Hydrating the Related Customer
In order to hydrate the Customer related to an Order, we’ll have to build a custom hydrator. To
do so, we’ll simply wrap Zend’s
ClassMethods
hydrator that we’re already using, and add some
additional functionality to it.
We’ll start by writing a spec to describe the functionality we need:
// specs/hydrator/order.spec.php
use
CleanPhp\Invoicer\Domain\Entity\Order;
use
CleanPhp\Invoicer\Persistence\Hydrator\OrderHydrator;
use
Zend\Stdlib\Hydrator\ClassMethods;
describe(
'Persistence\Hydrator\OrderHydrator'
,
function
() {
beforeEach(
function
() {
$this
->
hydrator
=
new
OrderHydrator(
new
ClassMethods());
});
describe(
'->hydrate()'
,
function
() {
it(
'should perform basic hydration of attributes'
,
function
() {
$data
=
[
'id'
=> 100
,
'order_number'
=>
'20150101-019'
,
'description'
=>
'simple order'
,
'total'
=> 5000
];
$order
=
new
Order();
$this
->
hydrator
->
hydrate
(
$data
,
$order
);
expect(
$order
->
getId
())
->
to
->
equal
(
100
);
expect(
$order
->
getOrderNumber
())
->
to
->
equal
(
'20150101-019'
);
expect(
$order
->
getDescription
())
->
to
->
equal
(
'simple order'
);
expect(
$order
->
getTotal
())
->
to
->
equal
(
5000
);
});
});
});
If first test case is to make sure that our
OrderHydrator
performs basic hydration of our scalar
type values. We’ll be passing off this work to the
ClassMethods
hydrator since it’s pretty good
at it. Next, we’ll need to handle our use case for persisting a
Customer
object on the
Order
:
Our Application in Zend Framework 2
156
use
CleanPhp\Invoicer\Domain\Entity\Customer;
use
CleanPhp\Invoicer\Domain\Entity\Order;
use
CleanPhp\Invoicer\Persistence\Hydrator\OrderHydrator;
use
Zend\Stdlib\Hydrator\ClassMethods;
describe(
'Persistence\Hydrator\OrderHydrator'
,
function
() {
beforeEach(
function
() {
$this
->
repository
=
$this
->
getProphet
()
->
prophesize
(
'CleanPhp\Invoicer\Domain\Repository\CustomerRepositoryInterface'
);
$this
->
hydrator
=
new
OrderHydrator(
new
ClassMethods(),
$this
->
repository
->
reveal
()
);
});
describe(
'->hydrate()'
,
function
() {
// ...
it(
'should hydrate a Customer entity on the Order'
,
function
() {
$data
=
[
'customer_id'
=> 500
];
$customer
=
(
new
Customer())
->
setId
(
500
);
$order
=
new
Order();
$this
->
repository
->
getById
(
500
)
->
shouldBeCalled
()
->
willReturn
(
$customer
);
$this
->
hydrator
->
hydrate
(
$data
,
$order
);
expect(
$order
->
getCustomer
())
->
to
->
equal
(
$customer
);
$this
->
getProphet
()
->
checkPredictions
();
});
});
});
We’ve added a dependency to our hydrator for an instance of
CustomerRepositoryInterface
.
We’ll use this to query for the customer record when we find a
customer_id
value in the data
being hydrated. This is now mocked and injected into the constructor.
Our test verifies that this properly occurs by checking the value of
$order->getCustomer()
and
making sure that its the same customer that we mocked
CustomerRepository
to return.
Our Application in Zend Framework 2
157
Now let’s build this class and make our tests work!
// src/Persistence/Hydrator/OrderHydrator.php
namespace
CleanPhp\Invoicer\Persistence\Hydrator;
use
CleanPhp\Invoicer\Domain\Repository\CustomerRepositoryInterface;
use
Zend\Stdlib\Hydrator\HydratorInterface;
class
OrderHydrator
implements
HydratorInterface {
protected
$wrappedHydrator
;
protected
$customerRepository
;
public function
__construct
(
HydratorInterface
$wrappedHydrator
,
CustomerRepositoryInterface
$customerRepository
)
{
$this
->
wrappedHydrator
=
$wrappedHydrator
;
$this
->
customerRepository
=
$customerRepository
;
}
public function
extract
(
$object
) { }
public function
hydrate
(
array
$data
,
$order
) {
$this
->
wrappedHydrator
->
hydrate
(
$data
,
$order
);
if
(
isset
(
$data
[
'customer_id'
])) {
$order
->
setCustomer
(
$this
->
customerRepository
->
getById
(
$data
[
'customer_id'
])
);
}
return
$order
;
}
}
This simple functionality works just like we drew it up in our tests. We use the
ClassMethods()
hydrator to do most of the work for us. When a
customer_id
value is present, we query our
CustomerRepository
for that customer and set it on the
Order
object. Our tests should now
pass!
This eagerly loading of relationships wouldn’t always be ideal. What if we don’t need the
Customer
for the current use case? One great way to implement some lazy loading of these
resources, which would only load the
Customer
if we requested it in client code, would be to use
Our Application in Zend Framework 2
158
Marco Pivetta’s⁴⁴
awesome
ProxyManager⁴⁵
library, which allows us to use Proxy classes instead
of Entities directly, and lazily load related resources. Check out his library if you’re interested.
Another solution, which we’ll explore later, is to just use a better persistence library, such as
Doctrine ORM.
For now, however, we can use this new
OrderHydrator
in our
OrderTable
by modifying the
service locator definition for
OrderTable
to use
OrderHydrator
instead of class methods:
// config/autoload/global.php
return
[
// ...
'service_manager'
=>
[
'factories'
=>
[
// ...
'OrderHydrator'
=>
function
(
$sm
) {
return new
OrderHydrator(
new
ClassMethods(),
$sm
->
get
(
'CustomerTable'
)
);
},
// ...
'OrderTable'
=>
function
(
$sm
) {
$factory
=
new
TableGatewayFactory();
$hydrator
=
$sm
->
get
(
'OrderHydrator'
);
return new
OrderTable(
$factory
->
createGateway
(
$sm
->
get
(
'Zend\Db\Adapter\Adapter'
),
$hydrator
,
new
Order(),
'orders'
),
$hydrator
);
},
],
// ...
],
// ...
];
⁴⁴
https://github.com/Ocramius
⁴⁵
https://github.com/Ocramius/ProxyManager
Our Application in Zend Framework 2
159
We declare a new entry in the service locator for
OrderHydrator
, so that we can use it wherever
we need it. Our first use of it is in the definition for
OrderTable
in the service locator which
now, instead of
ClassMethods
as it was previously using, it now uses
OrderHydrator
.
If we refresh our
/orders
page, we should now see our test Order with the associated Customer
rendered to the page, error free.
To recap: when call
OrderTable->getAll()
, we’re hydrating all Orders in the database, as
well as eagerly loading the associated Customer. When we render these to the page, and call
Order->getCustomer()
, we’re using that eagerly loaded Customer object to render the name
and ID of the customer to the page.
Our last step is to implement the
extract()
method of our hydrator that we left blank. For this,
we’re simply going to pass off work to the
ClassMethods->extract()
method as we don’t have
a specific use case for anything else right now.
// src/Persistence/Hydrator/OrderHydrator.php
Do'stlaringiz bilan baham: |