Where to manipulate data after controller grabs it from entity in zf2 - model-view-controller

In zf2, I'm writing a viewAction that grabs a record from a doctrine entity and passes the data to a view. After I've grabbed the data and before I pass it to the view, there's a bunch of conversion, formatting, concatenation, parsing and other manipulation I want to do to it. For example, the script below develops different $eventDate and $eventTime strings depending upon whether an event is a single- or multi-day event:
if ($eventStartDate==$eventEndDate) {
$eventStartDate = $eventStartDate->format('n/j/y');
$eventEndDate = $eventEndDate->format('n/j/y');
$eventDate = $eventStartDate;
$eventStartTime = $eventStartTime->format('g:i a');
$eventEndTime = $eventEndTime->format('g:i a');
$eventTime = "<strong>time: </strong>" . $eventStartTime . " - " . $eventEndTime . "<br/>";
} else {
$eventStartDate = $eventStartDate->format('n/j');
$eventEndDate = $eventEndDate->format('n/j/y');
$eventDate = $eventStartDate . " - " . $eventEndDate;
$eventStartTime = $eventStartTime->format('g:i a');
$eventEndTime = $eventEndTime->format('g:i a');
$eventTime = "<strong>departure: </strong>" . $eventStartTime . "<br/>";
$eventTime .= "<strong>return: </strong>" . $eventEndTime . "<br/>";
}
This is just one of a collection of similar scripts that I've got written in my controller. However, in an MVC pattern, it doesn't seem like they belong there. My question is, where should this and similar scripts be written?

You definitely need a Service Layer to apply such business logic, process your entity, generate a result and return that result back to the caller rather than handling all of this logic in controller level.
ZF's built-in Service Manager would be good place to take a look.
I would handle that requirement by following steps:
Write an EventService class under Application\Service namespace. This EventService depends on doctrine's EventRepository and/or ObjectManager's itself. Pass this dependencies to the EventService on construction time (you'll need a factory) or take a look ObjectManagerAwareInterface to figure out other ways (ie: setter injection).
This EventService would have some public methods like getEventById($id), getEventsByDateInterval(\DateTime $from, \DateTime $to), getActiveEvents() etc..
These public methods like getEventById($id) are interfaces for your controllers to interact your application.
EventService should/may have some private methods which doesn't accessible from outside and apply some domain rules on resultset or manipulate the business objects or even generates some other lightweight data transfer objects for specific use cases, internally.
Write a FooEvent entity. This entity would have some properties like $id, $title, $startDate, $endDate and these entity properties probably will be persisted to database.
In FooEvent entity, write a one-line public method named isSingleDayEvent() which returns a boolean.
Something like:
public function isSingleDayEvent()
{
return $this->getStartDate() === $this->getEndDate();
}
After all of this fancy steps, our viewAction can be written something like this:
public function viewAction()
{
$id = (int) $this->params()->fromQuery('id');
// Event instance can be an actual entity or DTO
$event = $this->eventService->getEventById($id);
$viewModel = new ViewModel();
$viewModel->setVariable('event', $event);
return $viewModel;
}
Now, we can write a ViewHelper with a name like EventRenderer to easily render events on view layer. The key point is our view helper accepts an \FooEvent (or DTO) instance to work. (This is not a MUST too)
For example:
<?php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Application\Entity\FooEvent;
class EventRenderer extends AbstractHelper
{
public function __invoke(FooEvent $event)
{
$time = '';
if($event->isSingleDayEvent()) {
$time = '<strong>Time: </strong>: '. $event->getStartTime();
} else {
$time = '<strong>Time: </strong>: '. $event->getStartTime();
}
return $time;
}
}
I simplified the helper for the sake of other key points.
And finally, inside the view.phtml, we can output the result of all this complex processes writing fewer lines:
echo '<h1>Event: '.$this->event->getTitle().'</h1>';
echo $this->eventRenderer($this->event);
By this way, in the next stages of your project when you need a REST service for example, writing and API endpoint will be easy as pie: writing a RestfulEventController will be enough while re-using whole service layer, without duplicating bunch of code.
I tried to summarize some best practices from my perspective, some steps definitely can be improved. I write this detailed answer because I asked similar questions several times in the past and couldn't find the exact answer but the partial solutions.

Related

Resolving service based on model in AppServiceProvider

To give you a brief view: I am trying to make redirect based on conditions. If request satisfy conditions of given destination you will get redirect to certain destination.
I have Destination model which has many conditions (Condition model). I have many Conditions which extends basic Condition model like DateCondition, LocationCondition etc (made by polimorphic relation). Each of condition type should has 'service' which tells if given condition match do the request. Example DateCondition should has own DateConditionMatcher which implements ConditionMatcherInterface.
(just public function match($condition, $request)).
I would like to write it with Open Closed principle from SOLID. Firstly I thought to add getMatcher() function straight to condition model and return different ConditionMatcher for each condition type, but some of ConditionMatchers need some other services passed in contructor, so it would force me to inject them also in Condition models which is bad pratice.
Maybe using Contextual binding in ServiceProvider could resolve this but how?
I have no idea how to couple Model to to the right one ConditionMatcher to then use it freely like this:
foreach ($destination->conditions as $condition){
$isMatched = $this->conditionMatcher->match($condition, $request);
}
To always have correct ConditionMatcher under $this->conditionMatcher.
I hope someone understood my not very clear message.
I had an idea overnight to make service which keeps all the matchers. The only downside to this solution is injecting every matcher.
class ConditionResolver implements ConditionResolverInterface
{
/** #var ConditionMatcherInterface[] */
private $conditionMatchers = [];
public function __construct(
DateConditionMatcher $dateConditionMatcher,
TimeConditionMatcher $timeConditionMatcher,
LocationConditionMatcher $locationConditionMatcher
) {
$this->conditionMatchers[DateCondition::class] = $dateConditionMatcher;
$this->conditionMatchers[TimeCondition::class] = $timeConditionMatcher;
$this->conditionMatchers[LocationCondition::class] = $locationConditionMatcher;
}
}
so now I am able to use correct matcher to given condition in this way(simplified):
foreach ($model->conditions as $condition)
{
$this->conditionMatchers[get_class($condition)]->match($condition, $request);
}
It allows me to inject various of services into that matchers in AppServiceProvider.
$this->app->singleton(LocationServiceInterface::class, function($app){
return new LocationService();
});
$this->app->singleton(DateConditionMatcher::class, function($app){
return new DateConditionMatcher();
});
$this->app->singleton(TimeConditionMatcher::class, function($app){
return new TimeConditionMatcher();
});
$this->app->singleton(LocationConditionMatcher::class, function($app){
return new LocationConditionMatcher($app->make(LocationServiceInterface::class));
});
Overall I think I miessed something and it would be done in more elegant way, but for now I treat it as an answer. If you have better solution, please share :)

Eloquent | Setting column value on insert

So currently, I have a table which is for business locations in order to seperate the locations from the actual businesses table.
In this table, I want to store the longitude and latitude and obviously I can't get the user to input that without requiring them to do manual work which I really want to avoid.
So I wrote a class in order to get the longitude and latitude ready for entry to the database.
I've read online about doing setLongitudeAttribute() function within the model but I'm basing it off of the whole address which they are entering so I need to capture the whole of the request and then input it in myself.
I understand I can do this in the controller and do a custom insert but I didn't know if it was possible to keep it all contained within the model.
So essentially to break it down.
User inputs the full address including all address lines and postal/zip code.
Eloquent model captures this data.
Converts the address to long and lat
Model then handles the request in order to set the longitude and latitude based on the address.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Http\Calls\LongitudeLatitude;
class BusinessLocation extends Model
{
/**
* #var array
*/
protected $fillable = [
'business_id',
'address_line_1',
'address_line_2',
'address_line_3',
'address_line_4',
'postcode',
'longitude',
'latitude'
];
/**
* #var string
*/
protected $address;
protected function setAddressLine1Attribute($value)
{
$this->address .= $value . '+';
$this->attributes['address_line_1'] = $value;
}
protected function setAddressLine2Attribute($value)
{
$this->address .= $value . '+';
$this->attributes['address_line_2'] = $value;
}
protected function setAddressLine3Attribute($value)
{
$this->address .= $value . '+';
$this->attributes['address_line_3'] = $value;
}
protected function setAddress4Attribute($value)
{
$this->address .= $value . '+';
$this->attributes['address_line_4'] = $value;
}
protected function setPostcodeAttribute($value)
{
$this->address .= $value;
$this->attributes['postcode'] = $value;
$this->setCoordinates();
}
protected function setCoordinates()
{
$long_lat = new LongitudeLatitude();
$coords = $long_lat->get($this->address);
$this->attributes['longitude'] = $coords['longitude'];
$this->attributes['latitude'] = $coords['latitude'];
}
First of all, and as you correctly mention, it doesn't look a good practice to write this logic in the model, but in the controller.
If I were you, I would leave the model logic to the model, and the rest to the controller and the other objects required to "transform" the input. This could even be done from the client with some javascript library if you will, sending just the lat and long to the server (your choice).
If you still want to create a method / setter in the model, I would recommend you to get some inspiration in any consolidated library, and the more official, the best, as for example: Laravel Cashier (https://laravel.com/docs/5.6/billing), which provides extra behaviours to the model, in a separate logic unit.
If you want to follow Laravel Cashier approach, it uses a trait to apply the behaviour:
https://github.com/laravel/cashier/blob/7.0/src/Billable.php
It uses the official stripe package (it could be, in your case, the class you wrote, or any other such as Google Maps SDK) as well as other helper and data objects, which would make your model lighter in memory and make another objects responsible of the additional logic (easier to test and maintain), while it would also embed the behaviours in your model.
About your code...
You can not guarantee that the setters are executed in the order you want (or you shouldn't trust it will always do), so, in case you want to continue with the code you already have, I would suggest to move $this->setCoordinates(); to somewhere else, for example an event observer such as creating, updating and saving:
https://laravel.com/docs/5.6/eloquent#events
And also evaluate the address every time you call it, as follows:
public function setCoordinates()
{
$address = $this->attributes['address_line_1'] . '+' .
$this->attributes['address_line_2'] . '+' .
$this->attributes['address_line_3'] . '+' .
$this->attributes['address_line_4'] . '+' .
$this->attributes['postcode'];
$long_lat = new LongitudeLatitude();
$coords = $long_lat->get($address);
$this->attributes['longitude'] = $coords['longitude'];
$this->attributes['latitude'] = $coords['latitude'];
}
Notice that the method is public, so you can call it from the event observer.
There are other business logic factors to consider:
- Do you want to give the possibility to fill manually the coordinates? if not, you should remove them from the $fillable array.
- Do you really need to evaluate the coordinates every time you update any of the address fields? Then you may want to have a status property to verify if the coordinates are pristine (in address fields setters set pristine as false, in coordinates getters check if the value is null or if it's not pristine, and if so, set the coordinates again).

Laravel, autoload classes based on an input array of class names

I have various classes, e.g.
<?php
namespace MyApp\Notifications;
class FirstNotification implements NotificationInterface {
public function getMessage() {
return 'first message';
}
}
and
<?php
namespace MyApp\Notifications;
class SecondNotification implements NotificationInterface {
public function getMessage() {
return 'second message';
}
}
I then have an array like: ['First','Second'].
I'm using:
foreach (['First','Second'] as $class_prefix) {
$class = "MyApp\Notifications\\{$class_prefix}Notification";
$object = new $class();
echo $object->getMessage();
}
but it feels a bit hacky - is there a better/more standard way to do this? The array is supplied elsewhere and will be different depending on the user - my aim is to be able to easily create classes that implement my interface and know this loop will be able to show their messages if they exist.
I ideally don't want to have to add a use statement for all the classes upfront, or pass them into a constructor, I just want magic to happen!
I'm assuming you don't actually mean autoload but rather instantiate the class. Autoloading is the process of including all the files in your application so the contents (usually classes) can be used. If you are using Laravel 5 and you follow PSR-4 (namespace matches directory structure) you don't have to do anything to make those classes available.
The code you already have looks fine and probably works. However you could make use of Laravels Service Container to resolve the class. This has quite a few advantages, one being the availability of automatic dependency injection...
foreach (['First','Second'] as $class_prefix) {
$object = app()->make("MyApp\Notifications\\{$class_prefix}Notification");
echo $object->getMessage();
}
Or even this:
foreach (['First','Second'] as $class_prefix) {
echo app()->callClass("MyApp\Notifications\\{$class_prefix}Notification#getMessage");
}
However, both will cause an exception if the class doesn't exist. You can check for that beforehand or just catch the exception:
foreach (['First','Second'] as $class_prefix) {
try{
$object = app()->make("MyApp\Notifications\\{$class_prefix}Notification");
echo $object->getMessage();
}
catch(ReflectionException $e){
// whooops
}
}

Something that escapes me about Symfony's controllers

Take a look to that code
<?php
namespace Sestante\SestanteBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sestante\SestanteBundle\Model\StrutturaManager;
class MainController extends Controller
{
public function indexAction(Request $request)
{
return $this->render('SestanteSestanteBundle:Main:index.html.twig');
}
public function showLodgingsAction(Request $request)
{
$repo = $this->getDoctrine()->getRepository('SestanteSestanteBundle:Struttura');
$usr = $this->get('security.context')->getToken()->getUser();
$usrId = $usr->getId();
$sm = new StrutturaManager($repo);
$lodgingList = $sm->retrieveLodgingsFromUser($usrId);
return $this->render('SestanteSestanteBundle:Main:showLodgings.html.twig',array('lodgingList' => $lodgingList));
}
}
This is a controller for an application that I've been writing.
Take a look to showLodgingsAction. I've tryied to place all business logic into a model (StrutturaManager) that with a repository (that I've passed directly from controller because, as far I know, they're available only here or via D.I.) query my db, do some elaboration and return a list that I'll render onto a template.
First question: Is this "code separation" good, or exists a better way for do what I'm trying to do?
Second question: suppose that, now, I want to use an object of StrutturaManager type into indexAction. Remember that mine object want a repository. So, have I to declare again, and again, and again, all my object for every controller's action where I want to use them? I suppose that must exist a smarter method but, at the moment, I don't understand which.
Define StrutturaManager as a service and inject the EntityManager into it. This way the manager will have access to repositories you need and controllers won't know about Doctrine nor repositories — which is a good practice.

how to achieve MVC in my Zend Framework

currently i am doing a project in zend the way i am doing is working perfectly but i am sure its not the way i am suppose to do i mean i am not following MVC and i want to apply MVC in my zend app.
i am pasting code of one simple module which will describe what i am doing .kindly correct me where i am making faults.
my controller
class ContactsController extends Zend_Controller_Action{
public function contactsAction(){
if(!Zend_Auth::getInstance()->hasIdentity()){
$this->_redirect('login/login');
}
else{
$request = $this->getRequest();
$user = new Zend_Session_Namespace('user');
$phone_service_id = $user->p_id;
$instance = new Contacts();
$select = $instance->Get_Contacts($p_id);
$adapter = new Zend_Paginator_Adapter_DbSelect($select);
$paginator = new Zend_Paginator($adapter);
.
.
//more code
}
plz note this 2 line in my controller
$instance = new Contacts();
$select = $instance->Get_Contacts($pid);
this is my contacts class in models
class Contacts extends Zend_Db_Table{
function Get_Contacts($p_id){
$DB = Zend_Db_Table_Abstract::getDefaultAdapter();
$select = $DB->select()
->from('contact', array('contact_id','contact_first_name','contact_mobile_no','contact_home_no','contact_email','contact_office_no'))
->where('pid = ?', $p_id)
->order('date_created DESC');
return $select;
}
}
after this i simple assign my result to my view.
note please
as its working but there is not private data members in my class,my class is not a blue print.there are no SETTERS AND GETTERS .how can i make my code that best suits MVC and OOP??
The most simple answer: you are already almost MVC. You use a Zend_Controller_Action to grab some data and pass this on to a view layer where you render the html. The only missing part is your model, which is mixed up between the controller and your data gateway (where you implemented a table data gateway pattern, that Zend_Db_Table thing).
I gave a pretty thorough explanation in an answer to another question how I'd properly set up the relations between Controller and Model. I also combined this with a Form, to handle data input, filtering and validation. Then to bundle some common functions, I introduced a Service layer between the Model and Controller.
With the controller, you perform some actions (list all my contacts, create a new contact, modify a contact) and the model is purely containing the data (id, name, phone, address). The service helps to group some functions (findContactByName, findContactById, updateContactWithForm).
If you know how to split Controller, Mode, Form and Service, your controller can become something like this:
class ContactsController extends Zend_Controller_Action
{
public function indexAction ()
{
if (!$this->hasIdentity()) {
$this->_redirect('login/login');
}
$service = new Application_Service_Contacts;
$contacts = $service->getContacts();
$paginator = $service->getPaginator($contacts);
$this->view->paginator = $paginator;
}
protected function hasIdentity ()
{
return Zend_Auth::getInstance->hasIdentity();
}
}
It is your personal taste what you want to do in your controller: I'd say you put as less as possible in your controllers, but you need to keep the control. So: a call to get data happens in the controller, retrieving this data happens somewhere else. Also: a call to convert a dataset into something else happens in the controller, the conversion happens somewhere else.
This way you can change the outcome in controllers extremely fast if you provided enough methods to your service classes to fetch the data. (Note I took the Zend_Auth to another function: if you have other actions, you can use this same function. Also, if you want to change something in your authentication, you have one place where this is located instead of every action in the controller)
keep one thing in mind when u learn new technology so first read thier own documentation. No one can explain better than them. Its hard to understand firstly but when you study it you will usedto and than u will love it like me Zend Offical Site

Resources