Structuring classes and objects in CodeIgniter for future upgrades? - php

I'm new to Codeigniter and pretty new to OOP as of the last month or so. I've been playing around and have been trying to make a blog page with a list of 10 posts and have different functions and possibilities for each.
For example, in the future I want to be able to show different buttons below posts depending on whether they are a logged in user, or if they are the post creator, etc.
Would I be best off making a new class 'blog_posts' and making an instance of it for every post I show on the page? like:
$this->load->library('blog_posts'); // in library or models, I'm not too sure?
$new_inst = new blog_posts;
$new_inst->show_post();
My end goal is to get it all as easily updated, changed, or modified in future if needed, I've seen simple tutorials online:
http://blog.pisyek.com/2011/03/create-a-simple-blog-using-codeigniter-2-0-part-1/
But I can't see them being easily updated and modified in future. I haven't seen a lot of other CI apps using new instances of classes, so I'm thinking there may be a different way in CI? Are new instances used in CI a lot?

Start with the controller. With Codeigniter -- anything you can do in a model, you can do in the controller. The easiest way to start -- just put your methods in the controller and set up a simple view file.
Always do a simple sanity check to make sure the basics are proper. Set up a controller and echo out some text.
In the controller try:
Get blog posts from your database using active record commands
Pass the blog posts result to $Data
With each step, Echo out something in the controller to confirm that its working
Next -- try using $Data to pass the blog results to the view.
After its working and you understand the basics -- then start pushing your methods to a Model. As you refactor and clean, your controller will get 'thinner'.
typically for blog posts you would have one model, that would return the blog posts and might also have your insert / update / delete methods. i say might because it usually makes more sense to have a public blog controller, that just shows blog posts. and then a separate blog admin controller -- that is only available to users who are logged in.
These two controllers - can share the same blog model. So for example to show blog posts on an admin screen - you could use the same method as showing the posts on a public page. Whereas you might have an admin blog model -- which has the create / update / delete methods.
That way you enforce a very clear separation of User Roles from the start -- people who are viewing the public blog should not be able to delete posts. The public show blog class is not "responsible" for editing.
This is something that i'm grappling with -- like the normal way to demonstrate a blog is to have all the methods together. Which makes sense in terms of "what can we do with a blog post" -- but it doesn't make any sense in terms of user roles.
ok Next - work on your create and update and delete methods.
Next try creating your log in methods. Make the Login process separate controller / model / view.
Eventually you could have one method call in the constructor of your blog admin class that checks to make sure that the user is logged in - otherwise it redirects them to a login page. That way you dont have to check over and over again in the view files etc to make sure they are authorized.
( highly recommend the free codeigniter tutorial series on net tuts website )
more rambling:
looking at the process from the point of view of the actor (user,role) -- for me helps to clarify what the chain of control should be. really critical roles - like an admin that has the power to delete all blog posts - the system should know that role at the top of the class.
best 'worst example' is when there are "is this a super admin?" checks in the view code. the view should not know anything about admin roles. BUT MUCH WORSE when you put a reference to an admin role in some view file -- what happens when you want to change that admin role in some way? now you have to go into all your view files!
class and method names - i'm moving towards not revealing any business or crud or field or any other type of business information in the URL. no nouns no verbs no ids. as simple as possible, with very few public url 'doors' into the app.
whats wonderful is that codeigniter makes it really easy to make your methods private. for critical pages: only use the public index method in the class for redirecting and make all other methods private.
use routes that directly to private methods. or public methods with broad names, that if successful -- like you have confirmed their credentials - go to private methods in the class like $this->_showAdminPage()
this is where defining a role like - blog editor - makes things very clear from a functionality point of view, AND keeps the valuable business knowledge private.

Related

Yii - Dynamically Loading a Controller

I have had a good play around with the Yii Framework and now I want to take it a bit deeper and what I want to do is set up an application where several different URLs would point to the same controller.
Normally domain.com/content will point to class ContentController which is standard in MVC.
What I want to do is set up three controllers (maybe more but this will do to start), i.e. ArticlesController, DisplayController and SplashController.
I would then set up what is essentially a CMS for a client, and they would be able to create as many pages as they want and point them to the above three controllers, which I have already set up to handle the data.
So for instance my client could set up the following pages: news, notices, technical and have them all pointed to the ArticlesController, and also set up pages: management, specials, support and have them all pointed to the DisplayController.
I know that all those controllers could be creating using the Gii module, but in this case its not an option as I don't think that's suitable for non technical people.
I just want my client to be able to log into the CMS, decide he wants to create a new page called "randompage", point it to the ArticlesController using a drop down menu, then write a bunch of articles for it and now have those articles accessible at domain.com/randompage/article-1 domain.com/randompage/article-2
With the standard set up that would point to site/error because there is no controller RandompageController
What I've done so far is create a constructor in Controller class where I can overwrite the controller id
class Controller extends CController {
function __construct($id) {
// Code here which successfully pulls from the database
// which controller the current page should point to.
parent::__construct($newControllerID)
}
}
If I check in the CController class, $this->_id = either articles, display or splash but the application itself stills loads site/error
I'm guessing I must have to set/override the Controller elsewhere. I have tried
Yii::app()->setController($newControllerID)
but that doesn't have any effect
Perhaps Yii is set up and must require a specific controller for each URL but that would mean developing rather rigid solutions for clients, and requiring them to call in the developer every time they want to add a new controller.
Hope I have explained what I'm trying to do well.
create a page object (id, title, slug, description, image)
create an article object (id, pageId, title, slug, content)
configure your urlManager:
'<pageSlug>/<articleSlug>' => 'article/view'
in your ArticleController->ViewAction evaluate
Yii::app()->request->getParam('pageSlug')
Yii::app()->request->getParam('articleSlug')
to find the right article for that page

One-To-Many member-to-articles relationship

We are new to Expression Engine and could maybe use some help from anyone who has built a few sites using this CMS.
We have a small dashboard system where users log in and read articles that are submitted from other authors. We are wanting to have a form which allows a member to chose which categories of articles he would like to be on his home page when he logs in. Then we'd like to figure out how to actually create that home page so that only the categories of articles that the user has chosen are visible.
From a DB standpoint this seems to be a one to many relationship between a member and categories, but we don't have a clear idea how to accomplish this inside of EE (without raw PHP/queries), especially using the Member module, since it isn't a channel and doesn't seem to allow creating relationships in the member custom fields.
We looked at creating a custom field for every category type, but there are about 95, and as we add or remove them, it would be extremely cumbersome to try to keep up with them all. Even if we did we still aren't sure how to connect 1 member to many categories or many entries via a category.
We'll update the question later to add some of the code we (if any) that we've come up with
In short, there isn't going to be a built in way to handle this. That isn't to say however that this can't be handled with some modules that are already out there. Let me ask you this. When you have users logging in, are they logging in to Expression Engine itself's backend, or the login system that you have created on a public facing website.

Adding interactions to admin pages generated by the admin generator

I am using Symfony 1.2.9 (with Propel ORM) to create a website. I have started using the admin generator to implement the admin functionality.
I have come accross a slight 'problem' however. My models are related (e.g. one table may have several 1:N relations and N:N relations). I have not found a way to address this satisfactorily yet. As a tactical solution (for list views), I have decided to simply show the parent object, and then add interactions to show the related objects.
I'll use a Blog model to illustrate this.
Here are the relationships for a blog model:
N:M relationship with Blogroll (models a blog roll)
1:N relationship with Blogpost (models a post submitted to a blog)
I had originally intended on displaying the (paged) blogpost list for a blog,, when it was selected, using AJAX, but I am struggling enough with the admin generator as it is, so I have shelved that idea - unless someone is kind enough to shed some light on how to do this.
Instead, what I am now doing (as a tactical/interim soln), is I have added interactions to the list view which allow a user to:
View a list of the blog roll for the
blog on that row
View a list of the posts for the blog on that row
Add a post for the blog on tha row
In all of the above, I have written actions that will basically forward the request to the approriate action (admin generated). However, I need to pass some parameters (like the blog id etc), so that the correct blog roll or blog post list etc is returned.
I am sure there is a better way of doing what I want to do, but in case there isn't here are my questions:
How may I obtain the object that relates to a specific row (of the
clicked link) in the list view (e.g. the blog object in this example)
Once I have the object, I may choose to extract various fields: id etc.
How can I pass these arguments to the admin generated action ?
Regarding the second question, my guess is that this may be the way to do it (I may be wrong)
public function executeMyAddedBlogRollInteractionLink(sfWebRequest $request)
{
// get the object *somehow* (I'm guessing this may work)
$object = $this->getRoute()->getObject();
// retrieve the required parameters from the object, and build a query string
$query_str=$object->getId();
//forward the request to the generated code (action to display blogroll list in this case)
$this->forward('backendmodulename',"getblogrolllistaction?params=$query_string");
}
This feels like a bit of a hack, but I'm not sure how else to go about it. I'm also not to keen on sending params (which may include user_id etc via a GET, even a POST is not that much safer, since it is fairly sraightforward to see what requests a browser is making). if there is a better way than what I suggest above to implement this kind of administration that is required for objects with 1 or more M:N relationships, I will be very glad to hear the "recommended" way of going about it.
I remember reading about marking certain actions as internal. i.e. callable from only within the app. I wonder if that would be useful in this instance?
I'm assuming your application is called backend. Suppose there are two models, BlogPost and BlogPostComment. These are managed using admin generated modules called blog_post and blog_post_comment.
I believe you want a link against each BlogPost displayed on the list page at backend.php/blog_post. The links take you to backend.php/blog_post_comment, which should only show comments related to the relevant BlogPost.
Under apps/backend/blog_post/templates, create a file called _commentslink.php and put this in it:
View Comments
Then in apps/backend/blog_post/config/generator.yml, you need to include this partial in the fields for the list view:
....
param:
config:
list:
display: [ id, title, _commentslink ]
Note the _commentslink - the _ tells it to use a partial instead of looking for the field in the model. Your object is available in this partial as $<name of model> - $blog_post in this case.
Essentially, all this method does is links to the same action as the filter on the comments list normally goes to, passing the relevant condition to it to make it filter by blogpost_id.
If you've got CSRF protection enabled in the backend, you'll need to disable it, or this method won't work. This is set in apps/backend/config/settings.yml. There will be a setting in there called csrf_secret - it should be set to false to disable csrf.
You should try symfony 1.3/1.4 out if you need support for 1:N relationships in forms. 1.3 is in my experience a relatively hassle free upgrade from 1.2.x - 1.4 is the same, but with deprecated features removed.

Best way to make Admin pages in CodeIgniter?

I'm working on an app in CodeIgniter, and I want to have admin pages for several of the objects in the application, and I'm wondering what would be the better way to put these into an MVC structure.
Idea 1:
In each controller, have an admin function, and add all of the admin pages I would like into that function.
example URL: domain.com/articles/admin
Idea 2
Make a new admin controller, which would have to reference many different models, and put all of the admin pages in there.
example URL: domain.com/admin/articles
Which way would be better?
Edit for clarification: By admin functionality, I mean being able to do the basic CRUD actions on any object, and be able to display a list of all of said object.
Definitely a different controller at least!
I used to think that I could keep all my admin functions in a single controller, but as my programs grew, I realized that I needed multiple controllers in my administration section.
So, I created a folder inside my controllers folder with the name "admin" and put all my administrative controllers in there. So my folders would look something like:
application
controllers
front.php
welcome.php
admin
dashboard.php
useradmin.php
etc...
One problem this creates, however, is when you type http://mysite.com/admin in your browser, it returns a 404 page. So, go to your "application/config/routes.php" file and add a custom route:
$routes['admin'] = 'admin/dashboard/index';
I'll echo Justin in keeping it part of the individual controllers.
You should setup some kind of authorization system that the individual controllers can use to so who is logged in (username) and what access they have (admin/member/etc). Here's a SO thread on CodeIgniter Auth Classes.
The view would then conditionally show the appropriate links, and the controller would enforce the policy by checking the auth before passing any data to the model or rendering an edit view. On unauthorized access an error could be rendered, or simply render with the non-editing view.
This approach seems to make the most sense (at least to me) because all the functionality is stored in the individual controller. Keeping admin functions in a single admin controller means you'll have to manage two controllers (the admin, and the actual controller) every time you add somethign new (or remove something).
If you're concerned about putting auth checking in every controller, you could create a generic controller class with all the auth setup, then have your controllers extend it. In the end the individual controller auth check could be as simple as:
function edit()
{
if(!$this->auth()){
//display auth error, or forward to view page
}
}
Of course some kind of ACL implementation would make this better, but I don't believe CodeIgniter has an 'official' ACL.
It's a good idea to have an admin folder in the controllers folder wherein you can access your administration e.g. yoursite.com/admin/users.
All your administrative needs will be there and all methods will be protected by checking user privileges like so:
if ( ! $this->auth->logged_in(array('login', 'admin')))
{
$this->session->set_flashdata('message', 'You do not have access to view this page');
redirect('admin/users/login');
}
Then all controllers outside the 'admin' folder will - depending on your type of site - will only be for viewing, etc.. no administrative portions.
Idea 2 is better.
system/application/controllers/admin
You keep all your admin controllers here.
Here is an extensive guide to the pro's and con's of each method:
http://philsturgeon.co.uk/news/2009/07/Create-an-Admin-panel-with-CodeIgniter
Depending on what you mean by 'Admin' functionality...typically, this is thought of as an 'Edit' view.
And typically, you use the existing controller to serve the 'Edit' view allowing the authorized users to make the edits (in your case, Admin users only).
Looks like a personal choice, i love having everything centralized so the admin controller would be my bet.
That way i wouldn't have to open up 5 different controllers while modifying admin tasks.

MVC - How does it work in the real world? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
I've read a lot of trivial MVC examples of calculators and thermometers but I can't seem to map the pattern to real world applications.
Suppose you have a more complicated scenario. Say you have a website shopping cart which requires users to login before adding to the cart. First, the user sees the product page (/product/detail) and clicks on add an item (/cart/add/207366). The user is not logged-in yet so they need to visit the login page (/user/login) and then, being smart about the flow, takes them to the shopping cart view (/cart/list). From there, they can link back to the original product detail page to continue shopping.
Let's say we have 3 database tables: users, usercart, and products. What is/are the model(s) in this situation? Would this entire flow be encapsulated into the addProductToCartFlow function of the ShoppingCart model? That would seem to be a bit messy, since it would need to access the users table for login/authentication and access the products table for pulling the product details/price into the cart.
Instead, would you say the ShoppingCart model is SELF-CONTAINED and only deals with adding items, removing items, etc. from the cart? The "logic" of the user being logged-in would then be checked elsewhere: perhaps in the controller itself? This would make the controllers very BUSY with quite a bit of "business logic" such as checking if the user is logged-in, checking if the shopping cart is empty, etc. and the model just becomes a pretty name for the database table.
Or maybe, the very fact of being logged-in or logged-out is part of a UserAuthentication model that deals with such functions. Or maybe we need a UserPageState model to tell us whether the user should be on a login page, or a cart page, or a product detail page?
What is the best MVC design for this situation in your opinion?
Your models are, essentially, business objects. You'll have ShoppingCarts, Users, and Items (probably one cart per customer in most cases, but who's to say?). You'll have controllers that drive your flow - the controller method for /cart/add/207366 will check to see if the user is authorized, and pass them to the controller for /login if not. The login controller should be smart enough to pass the right info back to the controller for /cart/add/207366, which should then add the item to the cart.
The controllers would call Cart.AddItem(), but the business logic is contained inside the shopping cart model - it might look up the price of the item, a preferred customer discount based on the User, etc. Controllers wouldn't know or care about this. Controllers do need to know if a user is logged in (if that matters to the application), as this affects their job (determining what View to render). They don't need to know if a customer is preferred, or on credit hold, or whatever other conditions matter to the business logic. That's all handled by the models.
I will have a model for each table in my simple sites. I can use any number of models in my controllers. In more complex examples, where a customer and a cart aren't separated, I have a model that brings those together with methods that know about both tables.
Have lots of models, very slim controllers and only HTML in your views. Your controllers should have a lot of the "if" statements that get results from the controllers. Your views can have "fors" that loop and repeat results, but no other logic. No HTML tags anywhere but in your views.
So to answers your question, you can have the following models
Users [gets into the users table]
Cart [get into the cart table]
product table and the users table]
Products [gets into the product table]
Orders [users, carts and products table (as a basic simplistic example)]
PageState a model that tracks your users state in your site
You have the following controllers
Cart
users
products
The controllers are maybe very long, but each action is a small one, with a specific job. I think it is ok to have a controller with 10 actions, so long as those actions are nice and short themselves.
The models that those actions call can be fairly long, and usually will be to account for the business logic. I also use helper classes to do things that are not business logic, but still critical. Think like security and validation.
And a whole slug of views. Views for the cart, for the login box, for the product summary, product detail, each phase of checkout, the receipt, views for the HTML formatted email receipt, category listing, menus, footers. Lots and lots of views.
You would want to have some ApplicationClass that all your controllers inherit so you can do somethings over and over.
You mentioned php, so you have views that make up your template, but if you did ASP you would have a masterpage that does partial views. I don't know exactly what you do in Ruby or Python, but it is similar.
I think that your worry about busy controllers shouldn't be a worry: that's their purpose. The cart controller that has the add action will rely on the users controller or the authentication helper to see if the user is logged in. The above two replies captured this pretty well.
Also, instead of creating another model for pages, I would just use a session variable or another field in the table. Another model will really just complicate things, and it doesn't actually represent an entity that you need to have self contained. When do you see a need for a stack of pages visited? Its really just a string of the url the user came from to get to the login form, which you can keep track of in a much less code and memory intensive fashion with session variables. Just have addItem() check to see if it is logged in, and if not redirect to the login page and store the current request. Then once logged in, check to see if the redirect variable is set, and if so go to it and reset it. I don't see a need for anything more complex than this.
Also, it looked like your pastebin code was CakePHP. Good choice. If you can, take advantage of the bake console and the scaffolding code generation. It does so so so much work for you, and just leaves the good stuff for you.

Categories