Yii and MVC pattern - php

I am creating a new Portlet in Yii. this widget show most recent Comment of a Issue. I want to show this only if Issue has comment and do not show it (event title) if Issue doesn't have any comments.
So my psudo code in view file as below:
Check number of comments process:
<?php
$countIssue = count($model->issues);
$i = 0; $j = 0;
while($i < $countIssue)
{
$j += $model->issues[$i]->commentCount;
$i ++;
}
?>
if ($countIssue >0 ) {
if ($j >0)
Display the widget
}
Else
Don't display the widget
I am just wonderring is my code suitable for MVC model. Could you give me a direction? Should I bring the Check number of comment process to Model or Controller , or is the above Ok fo MVC pattern?
Thank you!

First, I would have moved this logic into the run() method of your portlet class (the one that extends CPorlet).
Next, I would have defined a STAT relation in Issue class. This relation is only for counting of comments and would allow you to use a statement like:
$issue = Issue::model()->findByPk($issue_id);
// $comments_count below is exactly what you would expect... .
$comments_count = $issue->commentsCount;
Finally, combining it all I recommend the following approach inside the portlet's run() method as follows:
If ($someIssue->commentsCount > 0) {
// do something and in the end, when you want to render the portlet, you do...
$this->render("view_file_name");
}

I think there are many MVC friendly ways to do this, mainly the idea is to put data logic in Models then handle requests through Controllers while ideally Views should only be used for displaying purposes.
Personally i will use Yii named-scopes (which comes originally from Rails) to achieve the most recent filter like this:
Model:
class Comment extends CActiveRecord
{
......
public function scopes()
{
return array(
'recently'=>array(
'order'=>'create_time DESC',
'limit'=>5,
),
);
}
}
To get a list comments if only the issue has some you can do something like this in the Controller:
if($issue->comments)
$isseComments = Comment::model()->recently()->findAll(); //using the named scope to the recent ones only
$this->render('your_action',array(
.....
'issue' => $issue,
'issueComments'=>$isseComments,
));
And your View will remain tidy & clean:
if($issueComments > 0)
//Display the widget
else
// Don't

Related

Laravel Eloquent - Model extends other model

I have a question about extending my own Models eloquent.
In the project I am currently working on is table called modules and it contains list of project modules, number of elements of that module, add date etc.
For example:
id = 1; name = 'users'; count = 120; date_add = '2007-05-05';
and this entity called users corresponds to model User (Table - users) so that "count" it's number of Users
and to update count we use script running every day (I know that it's not good way but... u know).
In that script is loop and inside that loop a lot of if statement (1 per module) and inside the if a single query with count. According to example it's similar to:
foreach($modules as $module) {
if($module['name'] == 'users') {
$count = old_and_bad_method_to_count('users', "state = 'on'");
}
}
function old_and_bad_method_to_count($table, $sql_cond) {}
So its look terrible.
I need to refactor that code a little bit, because it's use a dangerous function instead of Query/Builder or Eloquent/Model and looks bad.
I came up with an idea that I will use a Models and create Interface ElementsCountable and all models that do not have an interface will use the Model::all()->count(), and those with an interface will use the interface method:
foreach ($modules as $module) {
$className = $module->getModelName();
if($className) {
$modelInterfaces = class_implements($className);
if(isset($modelInterfaces[ElementsCountable::class])) {
/** #var ElementsCountable $className */
$count = $className::countModuleElements();
} else {
/** #var Model $className */
$count = $className::all()->count();
}
}
}
in method getModelName() i use a const map array (table -> model) which I created, because a lot of models have custom table name.
But then I realize that will be a good way, but there is a few records in Modules that use the same table, for example users_off which use the same table as users, but use other condition - state = 'off'
So it complicated things a little bit, and there is a right question: There is a good way to extends User and add scope with condition on boot?
class UserOff extends User
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(function (Builder $builder) {
$builder->where('state', '=', 'off');
});
}
}
Because I have some concerns if this is a good solution. Because all method of that class NEED always that scope and how to prevent from method withoutGlobalScope() and what about other complications?
I think it's a good solution to create the UserOff model with the additional global scope for this purpose.
I also think the solution I would want to implement would allow me to do something like
$count = $modules->sum(function ($module) {
$className = $module->getModelName();
return $className::modulesCount();
}
I would create an interface ModulesCountable that mandates a modulesCount() method on each of the models. The modulesCount() method would return either the default count or whatever current implementation you have in countModuleElements().
If there are a lot of models I would probably use a trait DefaultModulesCount for the default count, and maybe the custom version too eg. ElementsModuleCount if that is consistent.

Assign a relationship after loop in php

I want to loop over a collection of items and attach a relationship based on if a particular condition is satisfied. Here is my code
public function bulkAssign()
{
$trainers = MasterTrainer::all();
for ($i=0; $i < count($trainers); $i++) {
$this->assignToManager($trainers[$i]);
}
// return redirect()->back()->with('success', 'Project Managers Assigned Successfully');
}
private function assignToManager($trainer)
{
$manager = ProjectManager::where('state', $trainer->state)->first();
return $trainer->update([
'project_manager_id' => $manager->id
]);
}
What I get is it attaches only the first manager to all the elements in the collection. What am i doing wrong?
can you inline the func for now? do some sort of echo/debugging?
but also I see several issues:
yes do use foreach because that is a bit better and you avoid having to use $i (making code a little more easy to read)
you are not attaching a relationship, you are setting a project_manager_id (i say this because initially i automatically thought you were going to dynamically add a relationship to model)
without knowing your db schema.. could you not do some sort of trick to avoid having to do this nth times?
$manager = ProjectManager::where('state', $trainer->state)->first();
you could either do:
$states = $trainers->pluck('states');
$managers = // do a query to get one trainer per state using group by
foreach ($trainers... ) {
$manager = $managers->where('state', $trainers->state)->first() // this is collection not eloquent
$trainer->update([
'project_manager_id' => $manager->id
]);
other would be to create a scope where you do a sub query to get manager id when u query for trainers

how to achieve pagination html php mysql

I am working on a web project in php that could potentially have a lot of users. From an administrative point of view, I am attempting to display a table containing information about each user, and seeing as the users table could grow extremely large, I would like to use pagination.
On the backend, I created a service that expects a limit and an offset parameter that will be used to query the database for records within the appropriate range. The service returns the total count of records in the table, along with the records matching the query
public static function getUsersInfo($limit = 50, $offset=1)
{
$users_count = Users::count(
array(
"column" => "user_id"
)
);
$users_info = array();
$users = Users::query()
->order('created_at')
->limit($limit, $offset)
->execute()
->toArray();
foreach ($users as $index => $user) {
$users_info[$index]['user_id'] = $user['user_id'];
$users_info[$index]['name'] = $user['first_name'] . " " . $user['last_name'];
$users_info[$index] ['phone'] = $user['phone'];
$users_info[$index] ['profile_image_url'] = $user['profile_image_url'];
}
$results = array(
'users_count' => $users_count,
'users_info' => $users_info
);
return !empty($results) ? $results : false;
}
On the frontend, what I would like to achieve ideally is, have the navigation displayed at the bottom of the table, with the typical previous, next buttons, and additionally a few numbers that allow the user to quickly navigate to a desired page if the page number displayed. This is what I have so far for the UsersController, with no pagination.
class UsersController extends ControllerBase
{
public function indexAction()
{
$usersObject = new Users();
$data = $usersObject->getUsers();
if ($data['status'] == Constants::SUCCESS) {
$users = $data['data']['users_info'];
$users_count = $data['data']['users_count'];
$this->view->setVar('users', $users);
}
echo $this->view->render('admin/users');
}
public function getUsersAction()
{
echo Pagination::create_links(15, 5, 1);
}
}
I don't have any working pagination yet, but I was thinking a good way to go would be to create a Pagination library with a create_links function that takes the
total_count of records in the database, so I know how many pages are expected
limit so I know how many records to collect
cur_page so I know where to start retrieving from
So when that function is called with the correct parameters, it would generate the html code to achieve the pagination, and that in turn can then be passed to the view and displayed.
I have never done this before, but from the research I have done so far, it seems like this might be a good way to approach it. Any guidance, suggestions, or anything at all really, regarding this would be greatly appreciated.
It looks like you are you using some bespoke MVC-ish framework. While it does not answer your question exactly I have a few points:
If you are looking at a lot of users, pagination is the least of your problems. You need to consider how the database is indexed, how the results are returned and much more.
Without understanding the underlying database abstract layer / driver you are using it is difficult to determine whether or not your ->limit($limit, $offset) line will work correctly. The offset should probably default to 0, but without knowing the code it is hard to say.
The ternary operator in your first method (return !empty($results) ? $results : false;) is currently valueless, because the statement before it will mean the variable will always be an array.
Avoid echo statements in controllers. They should return to a templating engine to output a view.
You Users class would be better named User, as the MVC framework implies that the 'Model' is a singular entity.
While it is not a general rule, most pagination systems I have used have worked on a zero-index system (Page 1 is Page 0), so calculating the limit range is simple:
$total_records = 1000;
$max_records = 20;
$current_page = 0;
$offset = $max_records * $current_page;
$sql = "SELECT * FROM foo LIMIT $offset, $max_records";

Confused with PHP model controller view style

Am getting my hands wet with this whole MVC thing and am really lost. I have a mysql table tblowner (ownerid,ownername).
Now what I want is to view the detail and edit it.
created the MVC directories and began with index.php
index.php?view=owner&action=view
require('controllers/controller.php');
$controller=new controller();
controllers/controller.php
class controller {
function controller(){
//Check action and view.
$view="home";
$allowedViews=array("home","owner");
if(isset($_GET['view'])){
$view=strtolower($_GET['view']);
if (!in_array($_GET['view'],$allowedViews)){
$view="home";
}
}
//requested action?
$action="view";
if(isset($_GET['action'])){
$action=strtolower($action);
}
//require model based on view now.
$controllerClass=$view.'controller';
require('controllers/'.$controllerClass.".php");
$controller=new $controllerClass($action);
}
}
models/ownermodel.php
class ownermodel
{
//contains two methods: edit and view related. Thats all.
//**********************************************************************
function ownermodeledit()
{
//edits owner data. It is always update since program cames prefilled with data.
if (isset($_POST['btnedit']))
{
//save changes.
}
else
{//show form now.
$this->showEditForm();
}
}
//**********************************************************************
function showEditForm($msg = '')
{
if ($msg)
{
echo '<p class="error">' . $msg . '</p>';
}
$ownerInfo = $this->ownermodelview();
if (is_array($ownerInfo))
{
?>
<form action="index.php?action=edit&view=owner" method="POST" id="frmeditowner" name="frmeditowner">
Name: <input type="text" id="oname" name="oname" value="<?php echo $ownerInfo['ownername']; ?>"/>
<?php
}//if(is_array($ownerInfo)) {
else
{
echo '<p class="error">There was an error retrieving owner information</p>';
}
}
//**********************************************************************
function ownermodelview()
{
//views model. return an array here
$mydb = new dbACW();
$params = array();
$return = "";
$result = $mydb->runSelectQuery("SELECT * FROM tbllicencee LIMIT 1", $params);
if (is_array($result) && count($result) > 0)
{
$return = array();
foreach ($result as $info)
{
$return['ownerid'] = $info['id'];
$return['ownername'] = $info['ownername'];
}
}
return $return;
}
}
That calls e.g. ownercontroller.php providing the necessary action (i.e. edit). And the model is where am lost at.
For viewing owner info, a method in the model would return an array containing information and a method in the viewer would display that array in anyway.
But for actions such as edit, what's the role of the view at all? Where would the form to edit the info be made?
And the view...currently it is inform of OOP (with class stuff). But if view is supposed to make the job of a web author easier to play around design? If it has a bunch of PHP code inside it, what differentiates it from a model?
Sorry for the long post...
In MVC structure, there is three main terms - Model, View & Controller, which sometimes confuses people.
In Model, we basically defines the classes with for the db interactions. Say, if I am trying to create a school management system, then I would be creating a class student_model, in which I defines the functions that will write to db or read from db, etc. Like, readStudentDetails(), insertNewStudent(), etc.
In View, we just display the contents to the user. So, basically we don't do much tasks in here rather than just echoing the outputs in HTML. For example, we have a View named home.php. And it will contain just the html and some echoing of data. That's all. No major processing or calculations and other stuff is done in Views. It's just for outputting the data to the user only.
Controller is basically an intermediary between Model and View. So, it's in Controller class that accepts HTTP requests, calls the model classes to do db interaction and access the views to display the outputs, etc.
If you are looking for a simple existing framework to learn this, try CodeIgniter: https://ellislab.com/codeigniter/user-guide/overview/mvc.html
It's pretty easy to follow the documentation and it's simple to use too. But there are other frameworks, like Zend Frameworks, etc. But for beginners, in my opinion, CodeIgniter is the best!
Hope this helps.

Zend MVC: one call from controller to model or more calls for differents results?

i need differents results from a model but i don't understand if it is correct make a single call and leave to model all the work or make more calls and collect the result to pass to the view when tables aren't joined or when i need fetch one row from a table and differents rows from others.
First example (more calls, collect and send to view):
CONTROLLER
// call functions of model
$modelName = new Application_Model_DbTable_ModelName();
$rs1 = $modelName->getTest($var);
$rs2 = $modelName->getTest2($var2);
// collect data
$pippo = $rs1->pippo;
if ($rs2->pluto == 'test') {
$pluto = 'ok';
} else {
$pluto = 'ko';
}
// send to view
$this->view->pippo = $pippo;
$this->view->pluto = $pluto;
MODEL
public function getTest($var) {
...
select from db...
return $result;
...
}
public function getTest2($var) {
...
select from db...
return $result;
...
}
Second example (one call, model collect all data, return to controller and send to view):
CONTROLLER
// call one function of model
$modelName = new Application_Model_DbTable_ModelName();
$rs = $modelName->getTest($var);
MODEL
public function getTest($var) {
...
select from db...
if ($result > 0) {
call other function
call other function
collect data
return $result;
...
}
Thanks
There's no one correct answer to this question, but in general, you should endeavor to keep your business logic in one place. Think of it as, "thin controller, thick model." I.e., keep the controllers as small and simple as possible and put all the business logic in the models.
There seems to be a few questions here:
But if i don't need to interact with db and i need only a simply
function is better put that function in model? For example:
CONTROLLER:
public function printAction() {
$data = $this->getRequest()->getPost();
$label = "blablabla";
$this->view->label = $label;
}
first, in the context of Zend Framework this particular example doesn't make much sense. The whole point of the controller is to populate the view template. However, I do get the idea. I would point you to Action Helpers and View helpers as a means to address your concerns. You can always add a utility class to your library for those pieces of code that don't seem to fit anywhere else.
Action Helpers typically are employed to encapsulate controller code that may be repetitive or reusable. They can be as simple or as complex as required, here is a simple example:
class Controller_Action_Helper_Login extends Zend_Controller_Action_Helper_Abstract
{
/**
* #return \Application_Form_Login
*/
public function direct()
{
$form = new Application_Form_Login();
$form->setAction('/index/login');
return $form;
}
}
//add the helper path to the stack in the application.ini
resources.frontController.actionhelperpaths.Controller_Action_Helper = APPLICATION_PATH "/../library/Controller/Action/Helper"
//the helper is called in the controller
$this->_helper->login();
a View helper does the same thing for the view templates:
class Zend_View_Helper_PadId extends Zend_View_Helper_Abstract
{
/**
* add leading zeros to value
* #param type $id
* #return string
*/
public function padId($id)
{
return str_pad($id, 5, 0, STR_PAD_LEFT);
}
}
//in this example the helper path is added to the stack from the boostrap.php
protected function _initView()
{
//Initialize view
$view = new Zend_View();
//add custom view helper path
$view->addHelperPath('/../library/View/Helper');
//truncated for brevity
$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer');
$viewRenderer->setView($view);
//Return it, so that it can be stored by the bootstrap
return $view;
}
//and to use the helper in the view template
//any.phtml
<?php echo $this->padId($this->id) ?>
i need differents results from a model but i don't understand if it is
correct make a single call and leave to model all the work or make
more calls and collect the result to pass to the view when tables
aren't joined or when i need fetch one row from a table and differents
rows from others.
This question is more about structure then about correctness.
You can interact with your database table models in Action and View helpers for simple/repetitive queries if you need to, however most developers might frown on this approach as being difficult to maintain or just ugly.
Many people seem to favor Doctrine or Propel to help them manage their database needs.
At this point I like to roll my own and currently favor domain models and data mappers, not an end all be all pattern, but seems to be appropriate to your question.
This is not a simple suggestion to implement for the first time, however i found two articles helpful to get started:
http://phpmaster.com/building-a-domain-model/
http://phpmaster.com/integrating-the-data-mappers/
and if you really want to get into it try:
http://survivethedeepend.com/
I hope this answers at least a part of your questions.

Categories