Web Design, Programming, Tutorials
Posts tagged MVC
MVC and n-layer architecture
Jan 18th
When I write a program, no matter which tool I use, one problem I face is how to design the application. No matter if you use Java, PHP, or Visual Basic; if you design the application badly, you will have problems later when you try to fix bugs or enhance the product to the next version.
The MVC design pattern
MVC stands for Model, View, Controller. This is a very common design pattern used in programming today. When someone uses a MVC application, they make a request to a controller. The controller then talks to the model, which consists of business logic, to perform the action requested. The controller then processes the information from the model and sends it to the appropriate view.
By using the MVC design pattern, you will separate your business logic from your presentation HTML. This makes your application more easily maintainable because a bug can be fixed in the business logic without all that HTML display code getting in the way. Also, when multiple developers are working on a project, the developers who write HTML and CSS can work independently of the core programmers.
N-Layer Architecture
N-Layer, or multi-layer, applications are designed using layers that handle a single responsibility. Many applications use three layers; Presentation, Business Logic, and Data-Access. This is similar to the n-tier architecture, but the primary difference is tiers represent physical hardware, while layers represent software.
In the three-layer approach; the Presentation layer would consist of the user interface, the Business Logic would consist of objects that process Business Rules, and the Data-Access layer would talk directly to the data source. This design usually works well.
Putting them together
One of the things I found a little confusing about MVC at first was what exactly is the model? After much research and testing, I finally found something that makes sense to me.
The diagram above is the general layout that I use when developing a MVC application. Here is a brief description of the various components of the diagram:
- Model – In this diagram, the model would consist of the Services, Repositories, and DatabaseConnection layers along with Domain Objects.
- View - The view is the presentation or design elements of the application; such as HTML or CSS.
- Controller – The controller is the layer that drives the application. It processes requests from the user and sends them to the model and then returns the processed information to the view.
- Services – This layer handles requests from the controller. This is the layer that contains the business rules on how the application should function.
- Repositories - This layer performs CRUD (Create, Read, Update, and Delete) operations on the database.
- DatabaseConnection - This layer is further abstraction between the data source and the repository. It is not required, but I typically use it when I need to work with multiple databases. The DatabaseConnection layer was built to generalize between three different database drivers and use common methods to work with each.
- Data-Access layer- This layer would consist of the Repositories, DatabaseConnection, SQL Data Source and Other Data Sources.
- Other Data Sources – This could include a XML file, other database, or even a web service.
- Domain Objects – This is not necessarily a layer, just a collection of objects used to make it easier to work with the data from the data source. Notice that all of the other layers need to be able to work with these objects. An example of a Domain object might be an Employee class that stores information about an employee and is populated from a data source in the Repository. The Employee object can then be passed from each layer back to the view where it is displayed to the user.
This is just a brief overview of MVC with the n-layer approach.
Installing ASP.NET MVC
Jan 15th
Here are the steps to install into Visual Studio 2008:
- Download ASP.NET MVC 1.0 from Microsoft’s website. Make sure to download the MSI file and the source code if you would like to see how it works.
- Install the AspNetMVC1.msi file.
- Open Visual Studio 2008 and go to:
- File -> New -> Project
- Under Project Types, select Web under either Visual Basic or C#
- Under Templates, select ASP.NET MVC Web Application
- Give your application a name and change its location if you wish
That’s it! Upon creating a new web application, the default MVC application will open.
Note:
ASP.NET MVC should also work using the free development tool from Microsoft, Visual Web Designer 2008.
Why you should use ASP.NET MVC
Jan 14th
If you’re like me, you like to have control over how your websites look and the HTML returned to the browser. ASP.NET MVC gives you that control by letting you keep your HTML separate from the core application code. If you learn to use tools like CSS and JavaScript, you can use them in the exact same way you would if you were working with a static web page. There are no generated IDs or missing/custom HTML attributes. While it does give you some helpers to generate HTML, you do not have to use them if you don’t want to.
ASP.NET MVC is very extensible and flexible. Nearly any component of the framework can be swapped out for your own version, if you so choose to delve into the inner workings.
ASP.NET MVC was built with Unit Testing in mind. If you’re not using Unit Testing, you should definitely start researching how it can improve your code reliability.
So, why should you use ASP.NET MVC?
- Based on the MVC pattern.
- Allows you to use Standards such as XHTML and CSS.
- Separation of concerns allows for more maintainable code.
- Gives you help when you need it and gets out of your way when you want control.
- Unit Testing is built in and easy to do.
- Fully supported by Microsoft.
- You can download the code for the ASP.NET MVC Framework and see how it works.
If you know HTML, CSS, or JavaScript and you are familiar with ASP, then you should definitely look into ASP.NET MVC.
ASP.NET MVC Using Forms Authentication With LDAP
Oct 23rd
If you are using ASP.NET MVC and you want to authenticate your users against Active Directory using LDAP, you need to do a little work to get everything set up. It is pretty easy to authenticate users with Active Directory using the <Authorize()> attribute, but I ran into some problems when I wanted to authorize a user based on a Windows Group.
Here are the steps I took to be able to authenticate active directory users and authorize their use of actions based on being members to user groups.
Web.Config
Find the authentication tag and change it to the following:
<authentication mode="Forms"> <forms loginUrl="~/Account/LogOn" timeout="2880" /> </authentication>
The LoginUrl points to the Controller/Action where the login function is.
UserRepository
This is the class object that does the communication with Active Directory via LDAP. The project where this class resides should also add a reference to the System.DirectoryServices DLL.
Imports System.DirectoryServices
Public Class UserRepository
Private _server As String
Public Sub New(ByVal server As String)
_server = server
End Sub
Public Function GetUser(ByVal userName As String) As Data.User
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://" + _server)
Dim search As DirectorySearcher = New DirectorySearcher(root)
search.SearchScope = SearchScope.Subtree
search.Filter = "(sAMAccountName=" + userName.Substring(userName.IndexOf("\") + 1) + ")"
Dim results As SearchResultCollection = search.FindAll()
Return _GetUser(results(0).Path)
End Function
Public Function GetUserByFullName(ByVal fullName As String) As Data.User
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://" + _server)
Dim search As DirectorySearcher = New DirectorySearcher(root)
search.SearchScope = SearchScope.Subtree
search.Filter = "(displayName=" + fullName + ")"
Dim results As SearchResultCollection = search.FindAll()
Return _GetUser(results(0).Path)
End Function
Public Function GetMembers(ByVal groupPath As String) As IQueryable(Of Data.User)
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://" + _server)
Dim search As DirectorySearcher = New DirectorySearcher(root)
Dim members As List(Of Data.User) = New List(Of Data.User)()
search.SearchScope = SearchScope.Subtree
search.Filter = "(memberOf=" + groupPath + ")"
Dim results As SearchResultCollection = search.FindAll()
For Each result As SearchResult In results
members.Add(_GetUser(result.Path))
Next
root.Close()
Return members.AsQueryable
End Function
Public Function Authenticate(ByVal userName As String, ByVal password As String) As Data.User
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://" + _server, userName, password)
Dim search As DirectorySearcher = New DirectorySearcher(root)
Dim user As User = Nothing
search.SearchScope = SearchScope.Subtree
search.Filter = "(sAMAccountName=" + userName + ")"
Dim results As SearchResultCollection = search.FindAll()
If (Not (results Is Nothing)) Then
user = _GetUser(results(0).Path)
End If
Return user
End Function
Private Function _GetUser(ByVal userPath As String) As Data.User
Dim entry As DirectoryEntry = New DirectoryEntry(userPath)
Dim user As Data.User = New Data.User()
user.UserName = entry.Properties("sAMAccountName").Value
user.FirstName = entry.Properties("givenname").Value
user.LastName = entry.Properties("sn").Value
user.Email = entry.Properties("mail").Value
For Each group In entry.Properties("memberOf")
user.Groups.Add(_GetGroup(group))
Next
entry.Close()
Return user
End Function
Private Function _GetGroup(ByVal path As String) As String
Dim value As String = ""
Dim index1 As Integer = path.IndexOf("=", 1)
Dim index2 As Integer = path.IndexOf(",", 1)
If (Not (index1 = -1)) Then
value = path.Substring((index1 + 1), (index2 - index1) - 1)
End If
Return value
End Function
End Class
User
This class hold the user information that we want to work with from Active Directory. Here I am storing the username, first and last names, email address, and the list of groups the user is a member of.
This class is in my Data project and that is why you will see Data.User in the UserRepository. If you want everything to be in one project, you could create the Data namespace around the User class. I did run into problems with MVC getting confused between my User class and a built in one if I did not specify the namespace. You could also simply rename the User class to something else.
Public Class User
Private _userName As String
Private _firstName As String
Private _lastName As String
Private _email As String
Private _groups As List(Of String)
Public Property UserName() As String
Get
Return _userName
End Get
Set(ByVal value As String)
_userName = value
End Set
End Property
Public Property FirstName() As String
Get
Return _firstName
End Get
Set(ByVal value As String)
_firstName = value
End Set
End Property
Public Property LastName() As String
Get
Return _lastName
End Get
Set(ByVal value As String)
_lastName = value
End Set
End Property
Public Property Email() As String
Get
Return _email
End Get
Set(ByVal value As String)
_email = value
End Set
End Property
Public Property Groups() As List(Of String)
Get
Return _groups
End Get
Set(ByVal value As List(Of String))
_groups = value
End Set
End Property
Public Sub New()
Me.UserName = ""
Me.FirstName = ""
Me.LastName = ""
Me.Email = ""
Me.Groups = New List(Of String)()
End Sub
Public Function GetFullName() As String
Dim s As System.Text.StringBuilder = New System.Text.StringBuilder()
If (Not (Me.FirstName = "")) Then
s.Append(Me.FirstName)
s.Append(" ")
End If
s.Append(Me.LastName)
Return s.ToString()
End Function
Public Function SerializeGroups() As String
Dim text As String = ""
For Each item In Groups
If (text = "") Then
text = item
Else
text = text + "|" + item
End If
Next
Return text
End Function
End Class
AccountController
Next is the controller that handles signing in and out of the application. I’ve simplified this controller code for the sake of simplicity. Under normal circumstances, I would have the controller communicate to a service class that then interacts with the repository. The service class could do validation on the login form before attempting to authenticate empty user and password strings.
Imports System.Globalization
Imports System.Security.Principal
Imports S3.Data
<HandleError()> _
Public Class AccountController
Inherits System.Web.Mvc.Controller
'data members.
Private _userRepository as UserRepository
Private _formsAuthentication As IFormsAuthentication
Public Sub New()
_userRepository = New UserRepository(ConfigurationManager.AppSettings("LDAPServer")
_formsAuthentication = New FormsAuthentication()
End Sub
'GET: /Account/LogOn
Public Function LogOn() As ActionResult
Return View("LogOn")
End Function
'POST: /Account/LogOn
<AcceptVerbs(HttpVerbs.Post)> _
Public Function LogOn( _
ByVal userName As String, _
ByVal password As String, _
ByVal rememberMe As Boolean, _
ByVal returnUrl As String) As ActionResult
Dim result As ActionResult = View("LogOn")
Dim user As User = _userRepository.Authenticate(userName, password)
If (Not (user Is Nothing)) Then
_formsAuthentication.SignIn(user, rememberMe)
If (returnUrl Is Nothing) Then
result = RedirectToAction("Index", "Home")
Else
result = Redirect(returnUrl)
End If
End If
Return result
End Function
Public Function LogOff() As ActionResult
_formsAuthentication.SignOut()
Return RedirectToAction("Index", "Home")
End Function
End Class
FormsAuthentication
The next class is the FormsAuthentication class. The main reason that I made this code a class of its own is for the sake of unit testing my project. During unit testing, the commands that deal with writing and clearing the cookie would error out. To get around this, I created an empty FormsAuthenticationTest class that does nothing for the SignIn and SignOut methods. Note: The example code does not show the interfaces. Check out the repository pattern and factory pattern to see how to setup services and repositories for unit testing.
After a user has been authenticated by the UserRepository, the controller passes the returned user into the SignIn method of the FormsAuthentication. This method then stores the user’s full name and serialized groups in a cookie. The list of groups is serialized by creating a string with each group name separated by a “|” character. This serialization takes place in the User object.
Imports System.Web.Security
Public Class FormsAuthentication
Public Sub SignIn(ByVal user As Data.User, ByVal createPersistentCookie As Boolean)
Dim authTicket As System.Web.Security.FormsAuthenticationTicket = _
New System.Web.Security.FormsAuthenticationTicket( _
1, _
user.GetFullName(), _
Now, _
Now.AddMinutes(60), _
createPersistentCookie, _
user.SerializeGroups())
Dim encryptedTicket As String = System.Web.Security.FormsAuthentication.Encrypt(authTicket)
Dim authCookie As HttpCookie = New HttpCookie( _
System.Web.Security.FormsAuthentication.FormsCookieName, _
encryptedTicket)
If (createPersistentCookie) Then
authCookie.Expires = authTicket.Expiration
End If
HttpContext.Current.Response.Cookies.Add(authCookie)
End Sub
Public Sub SignOut()
System.Web.Security.FormsAuthentication.SignOut()
End Sub
End Class
Global.asax
With the previous code, you should be able to use Forms Authentication to log into your application and authenticate a user against the Active Directory via LDAP. The <Authorize()> attribute will allow you to secure your controller or actions. There’s just one problem. Now, if you want to use <Authorize(Roles:=”GroupName”), your user does not get authorization to the action or controller.
There’s just one change left that we need to make. ASP MVC makes the current logged in user available by accessing the Context.User. When you log into the application MVC is setting the user as authenticated, but no roles or groups have been added to that user. We need to modify the Global.asax file and use the AuthenticationRequest event.
The following code will fire each time an authentication request is triggered. The code will check if the authentication cookie exists and if it does, reads the user’s name and groups from it and stores them in the Context.User so MVC can access the groups.
Private Sub MvcApplication_AuthenticateRequest(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.AuthenticateRequest
Dim cookieName As String = System.Web.Security.FormsAuthentication.FormsCookieName
Dim authCookie As HttpCookie = Context.Request.Cookies(cookieName)
If (Not (authCookie Is Nothing)) Then
Dim authTicket As System.Web.Security.FormsAuthenticationTicket = Nothing
Try
authTicket = System.Web.Security.FormsAuthentication.Decrypt(authCookie.Value)
If (Not (authTicket Is Nothing)) Then
Dim groups As String() = authTicket.UserData.Split(New Char() {"|"})
Dim id As System.Security.Principal.GenericIdentity = _
New System.Security.Principal.GenericIdentity(authTicket.Name, "LdapAuthentication")
Dim principal As System.Security.Principal.GenericPrincipal = _
New System.Security.Principal.GenericPrincipal(id, groups)
Context.User = principal
End If
Catch ex As Exception
'Do nothing.
End Try
End If
End Sub
And that should be all you need to use FormsAuthentication with LDAP and group authorization.
ASP.NET MVC Full URL From A Controller
Oct 16th
In working with ASP.NET MVC, I came across the need to send an email with the URL of a document that is to be approved. There are helper functions accessible from the View to build an ActionLink or get the URL of an action, but I had problems finding how to build this URL from within a controller. Here is the simple code that I finally pieced together to do what I wanted:
Dim link As String = HttpContext.Request.Url.Scheme + _
"://" + HttpContext.Request.Url.Authority + _
Url.Action("ActionName", "ControllerName", New With {.id = idOfDocument})
HttpContext.Request.Url.Scheme returns “http” or “https”, which ever one you’re using.
HttpContext.Request.Url.Authority returns the Base Url of your application.
And, the Url.Action method creates the Url to the Controller, Action, and ID of the document that is being sent out for approval.
How to setup CodeIgniter
Jun 30th
Introduction
This article explains the process I go through to setup the CodeIgniter framework and how to configure it so that I can start developing an application.
What is CodeIgniter?
If you don’t already know, CodeIgniter is an MVC framework built on PHP. There are many features and built in functions that make building a web application fairly easy. You can download the current version, 1.7.1, from CodeIgniter.com. Also, check out the very well documented User Guide.
If you are interested in using the Zend library of tools with CodeIgniter, please check out How to use Zend_Search_Lucene with CodeIgniter.
Step-By-Step
For this tutorial, I’m using a WAMP Server running on my local Windows computer. You could just as easily perform these same actions on a web server running PHP, although the paths may be different depending on if you are using Windows or a Linux based server.
Step 1
Download the latest version of CodeIgniter (1.7.1).
Before extracting the files to your server, let’s talk about where to put the files. For security purposes, it is recommended to place the CodeIgniter files outside the path of your web server so those files cannot be accessed by typing in a URL. So, if I access my web server by typing in http://localhost and the web server loads the website at C:\wamp\www\, then I want to place the System folder of CodeIgniter inside the C:\wamp\ folder. Go ahead and extract the System folder from the download package to the C:\wamp\ folder. You can place the System folder in the C:\wamp\www\ folder if you desire. Just make sure to adjust the path name later on.
Step 2
Next, I have decided that I may want to use CodeIgniter for several applications that I develop. To save on hosting space, I can setup all CodeIgniter applications to use the same core framework. First, we need to move some files around from their default locations.
Open the C:\wamp\system\application\ folder. You should see several folders listed here. The default CodeIgniter application is setup for a single application. We will be changing it to run multiple applications. Create a new folder called baseApplication_1234 or something unique. Make a copy of the index.html file and paste it inside baseApplication_1234. Next, move all of the folders located at C:\wamp\system\application\ into C:\wamp\system\application\baseApplication_1234\.
We will configure this base application with all of our default settings that we want to use for all CodeIgniter applications. Then, when you want to make a new application, you can simply copy the baseApplication_1234 and rename it.
Step 3
Next, we will configure the CodeIgniter application files.
Browse to C:\wamp\system\application\baseApplication_1234\config\ and open the following:
autoload.php
Modify line 42 to show:
$autoload['libraries'] = array('database', 'session');
config.php
Modify line 14 to show (change localhost to your domain name):
$config['base_url'] = "http://localhost/";
Modify line 26 to show:
$config['index_page'] = "index.php?";
This line will be very important for the way our URLs are displayed later on.
Modify line 44 to show:
$config['uri_protocol'] = "QUERY_STRING";
Modify line 57 to show (this is optional if you want a .html to be shown on the end of your URLs):
$config['url_suffix'] = ".html";
Modify line 220 to show (add your own unique value within the quotes):
$config['encryption_key'] = "12345";
Modify lines 234 – 241 to show:
$config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_encrypt_cookie'] = TRUE; $config['sess_use_database'] = TRUE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = TRUE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300;
This will require that we have a database and a table within called ci_sessions. This table will need to have certain fields that CodeIgniter will be attempting to write session data to for each users who visits your site. I’ll explain more later on.
database.php
Modify lines 41 – 45 to show:
$db['default']['username'] = "dbuser"; $db['default']['password'] = "mypassword"; $db['default']['database'] = "mydbname"; $db['default']['dbdriver'] = "mysql"; $db['default']['dbprefix'] = "dev_";
You will need to enter your correct username, password, database name, and prefix if you wish to use one. If you use a prefix, you will need to have a table called dev_ci_session instead of ci_session.
routes.php
For the base application, the routes.php file is probably OK. There are two lines that need to be modified when you build an application.
$route['default_controller'] = "welcome"; $route['scaffolding_trigger'] = "12345";
Again, use some unique value for the scaffolding_trigger. If you use a value that is hackable or nothing at all, your application will have a possible security hole. The default controller name can be changed here as well.
Step 4
There are two helper files that I add to my projects that help translate the URLs the way I like them (http://localhost/controller/action/id.html).
MY_form_helper.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
if ( ! function_exists('form_open'))
{
function form_open($action = '', $attributes = '', $hidden = array())
{
$CI =& get_instance();
if ($attributes == '')
{
$attributes = 'method="post"';
}
//Modify ->site_url to ->item('base_url').$action
$action = ( strpos($action, '://') === FALSE) ? $CI->config->item('base_url').$action : $action;
$form = '<form action="'.$action.'"';
$form .= _attributes_to_string($attributes, TRUE);
$form .= '>';
if (is_array($hidden) AND count($hidden) > 0)
{
$form .= form_hidden($hidden);
}
return $form;
}
}
?>
MY_url_helper.php
<?php
function redirect($uri = '', $method = 'location', $http_response_code = 302)
{
$CI =& get_instance();
switch($method)
{
case 'refresh' : header("Refresh:0;url=".site_url($uri));
break;
default : header("Location: ".$CI->config->item('base_url').$uri, TRUE, $http_response_code);
break;
}
exit;
}
?>
Create these files and save them to C:\wamp\system\application\baseApplication_1234\helpers\. These functions will override the original CodeIgniter functions and fix some URL rewriting issues.
Step 5
Enabling URL rewriting may depend if your server supports it. Apache has a rewrite_module that must be enabled before this will work. Most hosting providers should already have enabled URL rewriting.
Create a new text file called .htaccess and paste the following code into it:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php?$1 [L]
This will redirect all requests on your domain back to the index.php file, unless the actual path does happen to exist, then the server will serve up whatever file/directory is at that location.
Notice the ? behind index.php. The web server is rewriting the pretty URL into a QUERY_STRING that CodeIgniter is expecting and passes in the controller and action as variables such as index.php?var1=this&var2=that.
The .htaccess file should be stored in the C:\wamp\www\ folder or the root of your web site.
Step 6
Next, the database will need to be created and the ci_sessions table created to store the user sessions to.
After you create the MySQL database, run the following SQL and it will create the table for you:
CREATE TABLE `dev_ci_sessions` ( `session_id` varchar(32) NOT NULL, `ip_address` varchar(15) NOT NULL, `user_agent` text NOT NULL, `last_activity` datetime NOT NULL, PRIMARY KEY (`session_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Make sure to remove the “dev_” if you did not setup a dbprefix.
Step 7
Next, for testing purposes, we’ll setup the baseApplication_1234 web application and attempt to run it.
Find the index.php file that came with the CodeIgniter 1.7.1 download. It should be located in the same folder as the system folder and user_guide folder. Copy this file into your web site root at C:\wamp\www\.
Modify line 26 as shown:
$system_folder = "c:/wamp/system";
Modify line 43 as shown:
$application_folder = "application/baseApplication_1234";
This is the line that specifies which application to load.
This index.php file is the main entry point for the application. It will use these modifications to find the C:\wamp\system\ folder and run the correct web application. The only other files that need to be in the C:\wamp\www\ folder are files that HTML needs to be able to access via URLs such as images and JavaScript.
Conclusion
That should be everything. You can now try to open your web application by going to the URL, http://localhost. The default controller is Welcome and the default action is Index, so you can also test http://localhost/welcome/index.html to verify URL rewriting works. Remember the “.html” can be used to hide that PHP is running your web site. In reality, index is an action of the welcome controller and doesn’t need the “.html” to be there at all.
If you run into problems, you can add this line to an action or the controller’s constructor for further information:
$this->output->enable_profiler(TRUE);
Be sure to shut this down when not needed, or it could give your users information you may not wish them to have.
How to use Zend_Search_Lucene with the PHP framework CodeIgniter
Feb 17th
If you’ve heard the buzz about Apache’s open source search engine, Lucene, then you probably already know what a great search engine tool it is. The search engine is fast, has ports to various languages, and was written to be able to share the search index between the different Lucene ports.
The PHP version of Lucene is packaged in the Zend frameworkand is called Zend_Search_Lucene. When it comes to PHP frameworks, I tend to prefer using CodeIgniteras opposed to Zend. So, you might ask, how can you use a favored framework such as CodeIgniter with the power of Lucene’s search capabilities?
Install CodeIgniter 1.7.1
I downloaded a copy of the latest version of CodeIgniter 1.7.1 and configured it to run the default welcome action. Next, I made a copy of the welcome controller and view to test my indexer and search actions (which we’ll get to in just a minute).
Install Zend Framework 1.7
Next, I downloaded the latest version of the Zend Framework 1.7.5. After extracting the zip file, copy the Zend folder inside ZendFramework-1.7.5/library and paste it into the CodeIgniter framework under System/application/libraries.
Create A Zend Loader Class
The next thing that needs to be done is create a loader file to load the Zend library classes in CodeIgniter. This tutorial also explains how to create this loader class. This file below is named Zend.php and should be located in the System/application/libraries folder of CodeIgniter.
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class CI_Zend
{
function __construct($class = NULL)
{
// include path for Zend Framework
// alter it accordingly if you have put the 'Zend' folder elsewhere
ini_set('include_path', ini_get('include_path') .
PATH_SEPARATOR . APPPATH . 'libraries');
if ($class)
{
require_once (string) $class . EXT;
log_message('debug', "Zend Class $class Loaded");
}
else
{
log_message('debug', "Zend Class Initialized");
}
}
function load($class)
{
require_once (string) $class . EXT;
log_message('debug', "Zend Class $class Loaded");
}
}
//End of File: Zend.php
Creating An Indexer
Now we will create the search index. For demonstration purposes, I’m going to place the indexer and search functions in the same controller. You should have your indexer in a separate controller with security that will keep everyone from being able to run it.
We’ll start with the copy of the welcome controller, which I named home.php. After changing the class name and function calls to home instead of welcome, the contents of the file should look like this. Also, add the sanitize function below.
<?php
class Home extends Controller
{
function Home()
{
parent::Controller();
}
function index()
{
$this->load->view('home_view');
}
function sanitize($input)
{
return htmlentities(strip_tags($input));
}
}
/* End of file home.php */
/* Location: ./system/application/controllers/home.php */
Now, we can just replace the contents of the index() function with the following.
$this->load->library('zend', 'Zend/Feed');
$this->load->library('zend', 'Zend/Search/Lucene');
$this->load->library('zend');
$this->zend->load('Zend/Feed');
$this->zend->load('Zend/Search/Lucene');
//Create index.
$index = new Zend_Search_Lucene('c:\wamp\www\ci\tmp\feeds_index', true);
$feeds = array(
'http://www.cmjackson.net/feed/rss/',
'http://andrewmjackson.com/feed/rss');
//grab each feed.
foreach($feeds as $feed)
{
$channel = Zend_Feed::import($feed);
echo $channel->title().'<br />';
//index each item.
foreach($channel->items as $item)
{
if ($item->link() && $item->title() && $item->description())
{
//create an index doc.
$doc = new Zend_Search_Lucene_Document();
$doc->addField(Zend_Search_Lucene_Field::Keyword(
'link', $this->sanitize($item->link())));
$doc->addField(Zend_Search_Lucene_Field::Text(
'title', $this->sanitize($item->title())));
$doc->addField(Zend_Search_Lucene_Field::Unstored(
'contents', $this->sanitize($item->description())));
echo "\tAdding: ". $item->title() .'<br />';
$index->addDocument($doc);
}
}
}
$index->commit();
echo $index->count() .' Documents indexed.<br />';
This indexer will read in the RSS feeds from this website as well as my brother’swebsite and index the contents of the feed. When the index is created, you must specify a location to store the index. These are binary files that Lucene creates and it does not require a database for storage.
In this article, the author further explains the fields of the index document and when each should be used.
Feeds are not the only resource that Lucene can index. Web sites, databases, Microsoft Office documents, etc. Find out more information on Zend Search Lucene in the Zend Framework Manual .
A Basic Search
After running the indexer, you are ready to try searching the documents that are indexed. For demonstration purposes, I’ve added another function to the same controller as the index called search(). This function does not get the results of a form, but instead simulates a string query as if it were from a form.
function search()
{
$this->load->library('zend', 'Zend/Search/Lucene');
$this->load->library('zend');
$this->zend->load('Zend/Search/Lucene');
$index = new Zend_Search_Lucene('c:\wamp\www\ci\tmp\feeds_index');
$query = 'new movie';
$hits = $index->find($query);
echo 'Index contains '. $index->count() .
' documents.<br /><br />';
echo 'Search for "'. $query .'" returned '. count($hits) .
' hits<br /><br />';
foreach($hits as $hit)
{
echo $hit->title .'<br />';
echo 'Score: '. sprintf('%.2f', $hit->score) .'<br />';
echo $hit->link .'<br /><br />';
}
}
This function loads the same index that we previously created and searches for the key phrase ‘new movie’. The results that are returned are sorted by their score ranking. To make the search results look more like google, styling could be added as well as formatting the result entries, but this gives you a good idea of the basic functions of the search engine and how it works.
