What is considered the correct place for my pagination to live when using a service/datamapper/domain object trio?
Example:
Fetch ports with a few given criteria
Paginate the results
Know what page we are on, number of pages in total, number of results etc.. from the view class
The below is just something I wrote here and now, but it is similar to my application.
class PostController extends Controller
{
function viewPosts()
{
return $this->serviceFactory
->build('post')
->getPosts($aCriteria, $this->request->_get('page'), 10);
}
}
I am currently both calculating and storing pagination parameters in each service. Note that I am not pushing any data to the view from my controller.
class PostService extends AbstractService
{
public $posts;
public $iTotalPages; // This does not belong here does it?
function getPosts($aCriteria, $iPage, $iItemsPerPage)
{
$mapper = $this->dataMapperFactory->build('post');
// I do not know where to put the below
// (The parameters and the code itself)
$iCount = $mapper->count($aCriteria);
$iOffset = $iItemsPerPage * $iPage;
$this->iTotalPages = $iCount / $iItemsPerPage;
$this->posts = $mapper->find($aCriteria, $iOffset, $iOffset + $iItemsPerPage);
return $this->posts;
}
}
My views have access to the same instance of the Model layer as my controller, so I could call $service->iTotalPages from the view, but that feels wrong.
class PostsView extends AbstractView
{
function viewPosts()
{
$service = $this->serviceFactory->build('post');
if(count($service->posts)>0) {
$this->template->assign_var('TOTAL_PAGES', $service->iTotalPages);
$this->template->assign_vars('POSTS', $service->posts);
}
}
}
Solutions?
1) Create a service for pagination and have the controller exchange data with the post service as required?
2) Create a helper class for pagination that each service can include? (How would such a class look like?)
3) Add generic pagination to the AbstractService?
4) Add some sort of pagination support to my Repos?
You answer is pretty good, though I have a few suggestions.
Layer location:
The pagination infrastructure should live in the data access layer, this is so that you have low level control on how the data are retrieved.
Highest invocation
The pagination's interface should be abstracted and exposed in the UI through the service, since its a front-end concern. (I think you already have this covered in your answer)
The Abstraction:
What the UI should know are the page index, number of pages, number of items per page, and total items. Not the Offset nor the limit, these are infrastructure terms that should be encapsulated.
Input: (part of your fitlers)
Search filters
Sorting filters (if necessary)
Page index.
Number of items per page. (if the UI has control, if not then this should be encapsulated)
Output:
Filtered collection
Number of pages (for navigation)
Number of total items (if necessary in the UI)
Due to lack of answers to this question I am posting the solution I came up with. Appreciate comments/additions to it if it can be improved.
// Controller method
public function viewPosts()
{
// Create service objects
$postService = $this->serviceFactory->build('post', true);
$searchService = $this->serviceFactory->build('search');
// Set the search/filter parameters
$searchService->addFilter('author_id', $this->visitor->user_id);
$searchService->setOffset($this->request->_get('offset'));
$searchService->setLimit($this->request->_get('limit'));
// Search service will call the 'find' method on $articleService
$searchService->setServiceObject($articleService, 'find');
// Search service will return a filtered collection
return $searchService->search();
}
Doing it this way I am not leaking business logic into my controller (I think), and I have a single class to do all my filtering and analysis of the returned sql data, while still keeping each specific service in control of their specific find() method and datamappers.
Related
My View Composer passes some data to certain views (and it works, of course):
use Illuminate\View\View;
use App\Util\Helper
class PublicSettingsComposer
{
public function compose(View $view)
{
$view->with('settings', Helper::readSettingsFromFile()); // json
}
}
The appropriate provider is added into the configuration and it provides this composer correctly for all specific views:
view()->compose('public.layouts.*', 'App\Http\ViewComposers\PublicSettingsComposer');
However, inside (only) one of my views I need specific information from database, but therefore I have to use some data, that had been passed by my View Composer:
class BranchController extends Controller
{
public function branches()
{
$settings = retrieve_settings_passed_by_PublicSettingsComposer; // This is what I need
$headquartersId = $settings->headquartersid;
return view('public.layouts.branches', [
'headquarters' => Branch::find($headquartersId) // Branch is a Model
]);
}
}
FYI: Laravel version I'm using is: 5.5
P.S. #moderators: Please, be careful with considering my question as a duplicate. I know there are many questions about view composers and passing data to the views and grabbing data from within controllers. However, I really could not find any question with this context (titles are often misleading).
I see two rather simple solutions for this. The first one is to cache the parsed file within each request. The other is to use an actual cache for this job.
The first option is very straight forward to implement. In your Helper class you'll have to introduce a static property to hold the parsed contents of the read file. Then, just like you do within the singleton pattern, you either return the cached data or you first parse the file, cache the data and return it afterwards. This scenario solves your actual problem of parsing the settings twice per requests if used in two places of your app.
class Helper
{
protected static $cachedSettings;
public function readSettingsFromFile()
{
if (!self::$cachedSettings) {
self::$cachedSettings = // Do the parsing here. This should be your current implementation of Helper::readSettingsFromFile(). You can also put this in its own function.
}
return self::$cachedSettings;
}
}
The other option is to use an actual cache (an external one). You can either cache the parsed file for a specific amount of times (like 1, 3, 5 or 10 minutes or even longer). Or you cache it forever and invalidate the cache when you update the settings (if this happens within your app and you know it was updated).
This solution does only make sense if your settings do not change too frequently though. It also depends a bit on the amount of requests you expect towards your app. If your settings change not too frequently (less than every x minutes) and your app is used frequently (multiple requests every x minutes), then it could be a viable solution.
class Helper
{
public function readSettingsFromFile()
{
return Cache::remember(function () {
$settings = // Put your current calculation here
return $settings;
}, 3 * 60); // 3 * 60 = 180 seconds
}
}
Using Symfony, I am displaying a table with some entries the user is able to select from. There is a little more complexity as this might include calling some further actions e. g. for filtering the table entries, sorting by different criteria, etc.
I have implemented the whole thing in an own bundle, let's say ChoiceTableBundle (with ChoiceTableController). Now I would like to be able to use this bundle from other bundles, sometimes with some more parametrization.
My desired workflow would then look like this:
User is currently working with Bundle OtherBundle and triggers chooseAction.
chooseAction forwards to ChoiceTableController (resp. its default entry action).
Within ChoiceTableBundle, the user is able to navigate, filter, sort, ... using the actions and routing supplied by this bundle.
When the user has made his choice, he triggers another action (like choiceFinishedAction) and the control flow returns to OtherBundle, handing over the results of the users choice.
Based on these results, OtherBundle can then continue working.
Additionally, OtherOtherBundle (and some more...) should also be able to use this workflow, possibly passing some configuration values to ChoiceTableBundle to make it behave a little different.
I have read about the "Controller as Service" pattern of Symfony 2 and IMHO it's the right approach here (if not, please tell me ;)). So I would make a service out of ChoiceTableController and use it from the other bundles. Anyway, with the workflow above in mind, I don't see a "good" way to achieve this:
How can I pass over configuration parameters to ChoiceTableBundle (resp. ChoiceTableController), if neccessary?
How can ChoiceTableBundle know from where it was called?
How can I return the results to this calling bundle?
Basic approaches could be to store the values in the session or to create an intermediate object being passed. Both do not seem particularly elegant to me. Can you please give me a shove in the right direction? Many thanks in advance!
The main question is if you really need to call your filtering / searching logic as a controller action. Do you really need to make a request?
I would say it could be also doable just by passing all the required data to a service you define.
This service you should create from the guts of your ChoiceTableBundleand let both you ChoiceTableBundle and your OtherBundle to use the extracted service.
service / library way
// register it in your service container
class FilteredDataProvider
{
/**
* #return customObjectInterface or scallar or whatever you like
*/
public function doFiltering($searchString, $order)
{
return $this->filterAndReturnData($searchString, $order)
}
}
...
class OtherBundleController extends Controller {
public function showStuffAction() {
$result = $this->container->get('filter_data_provider')
->doFiltering('text', 'ascending')
}
}
controller way
The whole thing can be accomplished with the same approach as lipp/imagine bundle uses.
Have a controller as service and call/send all the required information to that controller when you need some results, you can also send whole request.
class MyController extends Controller
{
public function indexAction()
{
// RedirectResponse object
$responeFromYourSearchFilterAction = $this->container
->get('my_search_filter_controller')
->filterSearchAction(
$this->request, // http request
'parameter1' // like search string
'parameterX' // like sorting direction
);
// do something with the response
// ..
}
}
A separate service class would be much more flexible. Also if you need other parameters or Request object you can always provide it.
Info how to declare controller as service is here:
http://symfony.com/doc/current/cookbook/controller/service.html
How liip uses it:
https://github.com/liip/LiipImagineBundle#using-the-controller-as-a-service
I'm working on an application written in PHP. I decided to follow the MVC architecture.
However, as the code gets bigger and bigger, I realized that some code gets duplicated in some cases. Also, I'm still confused whether I should use static functions when quering the database or not.
Let's take an example on how I do it :
class User {
private id;
private name;
private age;
}
Now, inside this class I will write methods that operate on a single user instance (CRUD operations). On the other hand, I added general static functions to deal with multiple users like :
public static function getUsers()
The main problem that I'm facing is that I have to access fields through the results when I need to loop through users in my views. for example :
$users = User::getUsers();
// View
foreach($users as $user) {
echo $user['firstname'];
echo $user['lastname'];
}
I decided to do this because I didn't feel it's necessary to create a single user instance for all the users just to do some simple data processing like displaying their informations. But, what if I change the table fields names ? I have to go through all the code and change those fields, and this is what bothers me.
So my question is, how do you deal with database queries like that, and is it fine to use static functions when querying the database. And finally, where is it logical to store those "displaying" functions like the one I talked about ?
Your approach seems fine, howerver I would still use caching like memcached to cache values and then you can remove static.
public function getUsers() {
$users = $cacheObj->get('all_users');
if ($users === false) {
//use your query to grab users and set it to cache
$users = "FROM QUERY";
$cacheObj->set('all_users', $users);
}
return $users;
}
(M)odel (V)iew (C)ontroller is a great choice choice, but my advice is look at using a framework. The con is they can have a step learning curve, pro is it does a lot of heavy lifting. But if you want to proceed on your own fair play, it can be tough to do it yourself.
Location wise you have a choice because the model is not clearly define:
You'll hear the term "business logic" used, basically Model has everything baring views and the controllers. The controllers should be lean only moving data then returning it to the view.
You model houses DB interaction, data conversions, timezone changes, general day to day functions.
Moudle
/User
/Model
/DB or (Entities and Mapper)
/Utils
I use Zend and it uses table gateways for standard CRUD to avoid repetition.
Where you have the getUsers() method you just pass a array to it, and it becomes really reusable and you'd just have different arrays in various controller actions and it builds the queries for you from the array info.
Example:
$data = array ('id' => 26)
$userMapper->getUsers($data);
to get user 26
enter code here
$data = array ('active' => 1, 'name' => 'Darren')
$userMapper->getUsers($data);`
to get active users named Darren
I hope this help.
I've started development on a CakePHP project since a few weeks now. Since the beginning I was struggling with the amount of code inside the controllers. The controllers have, in most cases more lines of code than the models. By knowing the expression "Skinny controller, fat model" I'm searching for some days now for a way to put more code in the models.
The question arises at this point is, "where to draw the line". What should the controller do and what should the model do. There are already some questions/answers on this only I'm searching for a more practical explanation. For example I've put a function below which is now inside the controller. I think a part of this code must and can be moved to the model. So my question is: what part can I move to the model and what can remain in the controller.
/**
* Save the newly added contacts and family members.
*/
public function complete_contacts()
{
if ($this->request->is('post')) {
if (isset($this->data['FamilyMembers'])) {
$selected_user = $this->Session->read('selected_user');
$family_members = $this->data['FamilyMembers'];
$this->ContactsConnection->create();
foreach ($family_members as $family_member) {
// connection from current user to new user
$family_member['ContactsConnection']['contact_family_member_id'] = $selected_user['id'];
$family_member['ContactsConnection']['nickname'] = $selected_user['first_name'];
$this->ContactsConnection->saveAll($family_member);
// inverted connection from new user to current user
$inverted_connection['ContactsConnection']['family_member_id'] = $selected_user['id'];
$inverted_connection['ContactsConnection']['contact_family_member_id'] = $this->FamilyMember->inserted_id;
$inverted_connection['ContactsConnection']['nickname'] = $family_member['FamilyMember']['nickname'];
$this->ContactsConnection->saveAll($inverted_connection);
}
}
}
}
Should I create a function in the FamilyMember model called: "save_new_family_member($family_member, $selected_user)"?
As far as the purposes of the M and the C
The model manages the behavior and data of the application domain,
responds to requests for information about its state (usually from the
view), and responds to instructions to change state (usually from the
controller).
The controller receives user input and initiates a response by making
calls on model objects. A controller accepts input from the user and
instructs the model and a view port to perform actions based on that
input.
I would suggest you can pass
$selected_user = $this->Session->read('selected_user');
To your Model and perform your for each inside of your Model. You may want to change rules as to how the data is stored or perform some transformations on it and the Controller should be blind to this. Basically use the Controller to get your information [from the View often] to the Model. Don't directly manipulate the Model from the Controller. In short YES create the function that you suggested :)
That being said sometimes I find myself in a position where my Controller has to do more than I'd like, in which case at least break the task down into helper methods that way your controller is more manageable and you can reuse code where needed.
You are doing it right.
You can of course create some methods in model and make it fat with:
function updateContactFamilyMemberId($id){}
function updateNickname($nickname){}
...
In my opinion it still will be correct, but unnecessary.
I'm looking for a way to prevent repeated calls to the database if the item in question has already been loaded previously. The reason is that we have a lot of different areas that show popular items, latest releases, top rated etc. and sometimes it happens that one item appears in multiple lists on the same page.
I wonder if it's possible to save the object instance in a static array associated with the class and then check if the data is actually in there yet, but then how do I point the new instance to the existing one?
Here's a draft of my idea:
$baseball = new Item($idOfTheBaseballItem);
$baseballAgain = new Item($idOfTheBaseballItem);
class Item
{
static $arrItems = array();
function __construct($id) {
if(in_array($id, self::arrItems)){
// Point this instance to the object in self::arrItems[$id]
// But how?
}
else {
// Call the database
self::arrItems[id] = $this;
}
}
}
If you have any other ideas or you just think I'm totally nuts, let me know.
You should know that static variables only exist in the page they were created, meaning 2 users that load the same page and get served the same script still exist as 2 different memory spaces.
You should consider caching results, take a look at code igniter database caching
What you are trying to achieve is similar to a singleton factory
$baseball = getItem($idOfTheBaseballItem);
$baseballAgain =getItem($idOfTheBaseballItem);
function getItem($id){
static $items=array();
if(!isset($items[$id])$items[$id]=new Item($id);
return $items[$id];
}
class Item{
// this stays the same
}
P.S. Also take a look at memcache. A very simple way to remove database load is to create a /cache/ directory and save database results there for a few minutes or until you deem the data old (this can be done in a number of ways, but most approaches are time based)
You can't directly replace "this" in constructor. Instead, prepare a static function like "getById($id)" that returns object from list.
And as stated above: this will work only per page load.