I have a model which is used by two modules. I don't want to duplicate the code for that model into each modules.
For example I have 2 modules. First takes the blog posts from model and print them for normal users, and the other one prints them for admin users but with a few more options (which are set in the view). I would have the same model in two places. And.. that's bad.
Just a piece of code:
<?php
namespace Blog\Model;
use Zend\Db\Adapter\Adapter;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\AbstractTableGateway;
class BlogTable extends AbstractTableGateway
{
protected $table = 'blog_posts';
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
$this->initialize();
}
/**
* Gets the blog post list
*
* #return array
**/
public function fetchAll()
{
//..
}
}
So, how should I design this application?
The biggest question might be why you separate the administrative side outside of the 'Blog-Scope'. An administrative Module should only be the front-end to gather all backend-options (if that makes any sense)
However to get back into your question: you can simply include those classes from model A into model B, like use Blog\Model\BlogTable
Ultimately though i see management of a module as a responsible of the Blog-Module itself, as hinted within the first paragraph.
Edit: One thing i forgot to mention. Ideally if you decide to go the dependant approach, your module should make the BlogTable accessible through a service. That way your ModuleAdmin only has to do something like $serviceLocator->get('my-blog-table') which is a much more clean approach.
You could achieve this by having a base/shared module that provides various models, helpers etc.
But personally I would be looking to architect modules around functionality rather than user access levels.
Related
Is it considered a bad practice to add fields to Symfony entity in controller? For example lets say that I have a simple entity:
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
}
And then in UserController.php I want to do the following:
foreach($users as $user){
$user->postsCount = someMethodThatWillCountPosts();
}
So later that postsCount can be displayed in Twig. Is it a bad practice?
Edit:
It's important to count posts on side of mysql database, there will be more than 50.000 elements to count for each user.
Edit2:
Please take a note that this questions is not about some particular problem but rather about good and bad practices in object oriented programming in Symfony.
As #Rooneyl explained that if you have relation between user and post then you can get count easily in your controller, refer this for the same. But if you are looking to constructing and using more complex queries from inside a controller. In order to isolate, reuse and test these queries, it's a good practice to create a custom repository class for your entity.Methods containing your query logic can then be stored in this class.
To do this, add the repository class name to your entity's mapping definition:
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
*/
class Product
{
//...
}
Doctrine can generate empty repository classes for all the entities in your application via the same command used earlier to generate the missing getter and setter methods:
$ php bin/console doctrine:generate:entities AppBundle
If you opt to create the repository classes yourself, they must extend
Doctrine\ORM\EntityRepository.
More Deatils
Updated Answer
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. where posts can be commented, you always have to assume that a post draws hundreds of comments. In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This can lead to pretty serious performance problems, if your associations contain several hundreds or thousands of entities.
With Doctrine 2.1 a feature called Extra Lazy is introduced for associations. Associations are marked as Lazy by default, which means the whole collection object for an association is populated the first time its accessed. If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection: SOURCE
"rather about good and bad practices in object oriented programming"
If that's the case then you really shouldn't have any business logic in controller, you should move this to services.
So if you need to do something with entities before passing them to twig template you might want to do that in specific service or have a custom repository class that does that (maybe using some other service class) before returning the results.
i.e. then your controller's action could look more like that:
public function someAction()
{
//using custom repository
$users = $this->usersRepo->getWithPostCount()
//or using some other service
//$users = $this->usersFormatter->getWithPostCount(x)
return $this->render('SomeBundle:Default:index.html.twig', [
users => $users
]);
}
It's really up to you how you're going to do it, the main point to take here is that best practices rather discourage from having any biz logic in controller. Just imagine you'll need to do the same thing in another controller, or yet some other service. If you don't encapsulate it in it's own service then you'll need to write it every single time.
btw. have a read there:
http://symfony.com/doc/current/best_practices/index.html
So I'm working on a small CMS in Laravel 5, and the one of the first things that I don't fully get is passing variables to a view.
I have seen things like
return View('view', array('name' => 'your name here'));
Or variables in a view composer
public function compose($view) {
$view->with(Config::get('configfile'));
}
but I'm still wondering if there is a better/more elegant way to do this. For example, with the first approach, I have to give it that array at every view, which just seems like a hassle, and with the view composers, it just feels like there should be a better solution.
Is there a recommended way to push these variables on the the view?
Also, I'm talking about a set of variables that are needed in every view. for example the name and the slogan of the website.
Thanks.
Have you considered view()->share see the docs Sharing Data with All Views
<?php
namespace App\Providers;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
view()->share('key', 'value');
}
}
it allows you to share a piece to data with all your views. I don't see any reason why you couldn't load all your config in this way if your needing to access it on the majority of views.
You can use Route Model Binding: https://laravel.com/docs/5.2/routing#route-model-binding for model specific data binding. Other than that, there's no (practical) way to do it.
I'm writing a library search engine where a user can search based on various criteria (e.g., author, title, publisher, etc.) with CodeIgniter. So, I defined the interface BookSearch which all classes responsible for searching the database will implement
interface BookSearch{
/**
Returns all the books based on a given criteria as a query result.
*/
public function search($search_query);
}
If I want to implement a search based on authors I can write he class AuthorSearch as
class AuthorSearch implements BookSearch extends CI_Model{
function __construct(){
parent::__construct();
}
public function search($authorname){
//Implement search function here...
//Return query result which we can display via foreach
}
}
Now, I define a Controller to make use of these classes and display my results,
class Search extends CI_Controller{
/**
These constants will contain the class names of the models
which will carry out the search. Pass as $search_method.
*/
const AUTHOR = "AuthorSearch";
const TITLE = "TitleSearch";
const PUBLISHER = "PublisherSearch";
public function display($search_method, $search_query){
$this->load->model($search_method);
}
}
This is where I hit my problem. The CodeIgniter manual says that, to invoke a method in a model (i.e., search), I write $this->AuthorSearch->search($search_query). But since I have the class name of the search classes as strings, I can't really do $this->$search_method->search($search_query) right?
If this was in Java, I'd load objects into my constants. I'm aware that PHP5 has type hinting but the target platform for this project has PHP4. And also, I'm looking for a more "CodeIgniter" way of doing this abstraction. Any hints?
You can really do $this->$search_method->search($search_query). Also in CI you can assign library name as you want.
public function display($search_method, $search_query){
$this->load->model($search_method, 'currentSearchModel');
$this->currentSearchModel->search($search_query);
}
What you're talking about it the driver model. You can, in fact, do what you're suggesting can't be done:
<?php
$this->{$search_method}->search($search_query);
CodeIgniter has CI_Driver_Library & CI_Driver classes to do this (See CodeIgniter Drivers).
However, I've found that it's usually simpler to implement an interface / extend an abstract class like you're doing. The inheritance works better than CI's drivers.
I have four models in CodeIgniter for a forum I am building:
forum_model
category_model
user_model
subject_model
I wish to access the category, user and subject models within the forum model (ie: browse thread by user / category / subject) but they need to be independent for individual functions (ie: add user / subject / category)
Theres a lot more independent functions and basically what I was wondering was if it is bad practice to create a 'master' forum model (and what the best way to create the model would be) or if I should just do the linking in the controller?
I was thinking about setting the forum_model up like this:
class Forum_model extends CI_Model {
function __construct() {
parent::construct();
$this->load->model('Category_model', 'category');
$this->load->model('User_model', 'user');
$this->load->model('Subject_model', 'subject');
}
}
then possibly accessing the other models within methods using variable variables $this->$model->method()
In 2.1.0, any property (i.e. model reference, library, etc.) not defined in a model's scope will magically access the CI super object:
<?php
function __get($key)
{
return get_instance()->$key;
}
So, as long as you don't have $category $user or $subject member variables in your forum model, this should work.
However, usually it's good practice in MVC for models NOT to know about each other. I'd caution you not to let your forum model function like a controller or a library (otherwise, it should be a controller or a library!).
I have a controller/model for projects. so this controls the projects model, etc, etc. I have a homepage which is being controlled by the pages_controller. I want to show a list of projects on the homepage. Is it as easy as doing:
function index() {
$this->set('projects', $this->Project->find('all'));
}
I'm guessing not as I'm getting:
Undefined property: PagesController::$Project
Can someone steer me in the right direction please,
Jonesy
You must load every model in the controller class by variable $uses, for example:
var $uses = array('Project');
or in action use method
$this->loadModel('Project');
In my opinion the proper way to do this is add a function to your current model which instantiates the other model and returns the needed data.
Here's an example which returns data from the Project model in a model called Example and calls the data in the Example controller:
Using Project Model inside Example Model:
<?php
/* Example Model */
App::uses('Project', 'Model');
class Example extends AppModel {
public function allProjects() {
$projectModel = new Project();
$projects = $projectModel->find('all');
return $projects;
}
}
Returning that data in Example Controller
// once inside your correct view function just do:
$projects = $this->Example->allProjects();
$this->set('projects', $projects);
In the Example view
<?php
// Now assuming you're in the .ctp template associated with
// your view function which used: $projects = $this->Example->allProjects();
// you should be able to access the var: $projects
// For example:
print_r($projects['Project']);
Why is this "better" practice than loading both models into your controller? Well, the Project model is inherited by the Example model, so Project data now becomes part of the Example model scope. (What this means on the database side of things is the 2 tables are joined using SQL JOIN clauses).
Or as the manual says:
One of the most powerful features of CakePHP is the ability to link relational mapping provided by the model. In CakePHP, the links between models are handled through associations.
Defining relations between different objects in your application should be a natural process. For example: in a recipe database, a recipe may have many reviews, reviews have a single author, and authors may have many recipes. Defining the way these relations work allows you to access your data in an intuitive and powerful way. (source)
For me it's more reasonable to use requestAction. This way the logic is wrapped in the controller.
In example:
//in your controller Projects:
class ProjectsController extends AppController {
function dashboard(){
$this->set('projects', $this->Project->find('all'));
}
$this->render('dashboard');
}
Bear in mind that you need to create dashboard.ctp in /app/views/projects of course.
In the Page's dashboard view (probably /app/views/pages/dashboard.ctp) add:
echo $this->requestAction(array('controller'=>'projects', 'action'=>'dashboard'));
This way the logic will remain in the project's controller. Of course you can request /projects/index, but the handling of the pagination will be more complicated.
more about requestAction(). but bear in mind that you need to use it carefully. It could slow down your application.