Skip to content
Browse files

tweaks to initial release. example docs completed

  • Loading branch information...
1 parent 5b13ba2 commit 4279b7cda6e42c24276e39248711a05ebdda7f51 @microbubble committed Nov 21, 2009
View
4 config/kosearch.php
@@ -0,0 +1,4 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+$config['index_path'] = 'searchindex';
+$config['use_english_stemming_analyser'] = TRUE;
View
94 controllers/search_example.php
@@ -0,0 +1,94 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+class Search_Example_Controller extends Controller {
+
+ const ALLOW_PRODUCTION = FALSE;
+
+ public function index($msg = NULL) {
+
+ $results = NULL;
+ $results2 = NULL;
+ $query = NULL;
+
+ $view = new View('search_example');
+
+ $view->bind("results", $results)
+ ->bind("results2", $results2)
+ ->bind("query", $query)
+ ->bind("msg", $msg);
+
+ if (!empty($_GET["q"])) {
+
+ try {
+ $query = $_GET["q"];
+ $form = $_GET["form"];
+
+ if($form == "artists") {
+ $results = Search::instance()->find($query);
+ }
+ else {
+ Search::instance()->load_search_libs();
+
+ $query = Zend_Search_Lucene_Search_QueryParser::parse($query);
+
+ $hits = Search::instance()->find($query);
+
+ $results2 = $query->highlightMatches(iconv('UTF-8', 'ASCII//TRANSLIT', $hits[0]->body));
+ }
+ }
+ catch(Exception $e) {
+ Kohana::log("error", $e);
+ }
+ }
+
+ $view->render(TRUE);
+ }
+
+ public function add() {
+
+ $items = array();
+
+ $song = new Mp3_Model(1, "Ian Brown", "My Star");
+ $items[] = $song;
+
+ $song = new Mp3_Model(2, "Rolling Stones", "Brown Sugar");
+ $items[] = $song;
+
+ $song = new Cd_Model(3, "Stone Roses", "Sugar Spun Sister");
+ $items[] = $song;
+
+ $song = new Cd_Model(4, "David Bowie", "Starman");
+ $items[] = $song;
+
+ $song = new Mp3_Model(4, "Bob Dylan", "Like a Rolling Stone");
+ $items[] = $song;
+
+
+ try {
+ Search::instance()->build_search_index($items);
+
+ $this->index("Index successfully populated");
+ }
+ catch(Exception $e) {
+ $this->index($e);
+ }
+ }
+
+ public function addurl() {
+
+ // use a local file for purpose of demo.
+ $filename = MODPATH."kosearch".DIRECTORY_SEPARATOR."examples".DIRECTORY_SEPARATOR."kohana_home.html";
+
+ // Note: the Search class is responsible for loading the Zend libraries, so as we
+ // want to instantiate Zend_Search_Lucene_Document_Html prior to calling singleton,
+ // we must first call Search::instance()->load_search_libs();
+ Search::instance()->load_search_libs();
+
+ $doc = Zend_Search_Lucene_Document_Html::loadHTMLFile($filename, TRUE, "utf-8");
+
+ Search::instance()->addDocument($doc);
+
+ $this->index('Kohana page successfully added &darr;&nbsp;<a href="#form2" title="scroll down">scroll down</a>&nbsp;&darr;');
+ }
+
+} // End Search Controller
View
171 examples/kohana_home.html
@@ -0,0 +1,171 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Home &ndash; Kohana: Swift, Secure, and Small PHP 5 Framework</title>
+<meta name="keywords" content="Kohana, PHP 5, MVC, framework, strict, XHTML, UTF-8, utf8, database abstraction, mysql, CodeIgniter, events, sessions, secure, lighweight, extensible, easy to extend, easy to use, forums, api, OOP, object oriented" />
+<meta name="description" content="Kohana is a PHP 5 framework that uses the Model View Controller architectural pattern. It aims to be secure, lightweight, and easy to use." />
+<link rel="stylesheet" type="text/css" href="http://www.kohanaphp.com/media/css/reset.css" media="all" />
+<link rel="stylesheet" type="text/css" href="http://www.kohanaphp.com/media/css/common.css" media="all" />
+<link rel="stylesheet" type="text/css" href="http://www.kohanaphp.com/media/css/web.css" media="screen" />
+<link rel="stylesheet" type="text/css" href="http://www.kohanaphp.com/media/css/print.css" media="print" />
+
+<link rel="stylesheet" type="text/css" href="http://www.kohanaphp.com/media/css/mozilla.css" />
+<script type="text/javascript" src="http://www.kohanaphp.com/media/js/jquery.js"></script>
+<script type="text/javascript" src="http://www.kohanaphp.com/media/js/plugins.js"></script>
+
+<script type="text/javascript" src="http://www.kohanaphp.com/media/js/effects.js"></script>
+
+</head>
+<body>
+<!-- Start Language Picker -->
+<ul id="languages">
+<li><a class="active" href="http://www.kohanaphp.com/home"><img alt="en_US" src="http://www.kohanaphp.com/media/img/flags/en_US.png" width="16" height="11" /></a></li>
+<li><a class="" href="http://www.kohanaphp.com.br/home"><img alt="pt_BR" src="http://www.kohanaphp.com/media/img/flags/pt_BR.png" width="16" height="11" /></a></li>
+<li><a class="" href="http://www.kohanaphp.de/home"><img alt="de_DE" src="http://www.kohanaphp.com/media/img/flags/de_DE.png" width="16" height="11" /></a></li>
+<li><a class="" href="http://www.kohanaphp.es/home"><img alt="es_ES" src="http://www.kohanaphp.com/media/img/flags/es_ES.png" width="16" height="11" /></a></li>
+<li><a class="" href="http://www.kohanaphp.fr/home"><img alt="fr_FR" src="http://www.kohanaphp.com/media/img/flags/fr_FR.png" width="16" height="11" /></a></li>
+<li><a class="" href="http://www.kohanaphp.it/home"><img alt="it_IT" src="http://www.kohanaphp.com/media/img/flags/it_IT.png" width="16" height="11" /></a></li>
+<li><a class="" href="http://www.kohanaphp.nl/home"><img alt="nl_NL" src="http://www.kohanaphp.com/media/img/flags/nl_NL.png" width="16" height="11" /></a></li>
+<li><a class="" href="http://www.kohanaphp.pl/home"><img alt="pl_PL" src="http://www.kohanaphp.com/media/img/flags/pl_PL.png" width="16" height="11" /></a></li>
+<li><a class="" href="http://www.kohanaphp.ru/home"><img alt="ru_RU" src="http://www.kohanaphp.com/media/img/flags/ru_RU.png" width="16" height="11" /></a></li>
+</ul>
+
+<!-- End Language Picker -->
+<!-- Start Developer Menu -->
+<div id="developer">
+<span>Developers:</span>
+<ul>
+<li><a href="http://dev.kohanaphp.com/">Redmine</a></li>
+<li><a href="http://dev.kohanaphp.com/projects/kohana2/activity">Timeline</a></li>
+<li><a href="http://dev.kohanaphp.com/projects/kohana2/repository/browse/trunk">Browse Source</a></li>
+<li><a href="http://dev.kohanaphp.com/projects/kohana2/issues">Tickets</a></li>
+<li><a href="http://dev.kohanaphp.com/wiki/kohana2/BeADev">Join</a></li>
+<li><a href="http://www.kohanajobs.com/">Jobs</a></li>
+
+</ul>
+</div>
+<!-- End Developer Menu -->
+<!-- Start Header -->
+<div id="header">
+<!-- Start Logo -->
+<h1 id="logo">Kohana</h1>
+<!-- End Logo -->
+</div>
+<!-- End Header -->
+<!-- Start Main Menu -->
+<div id="menu" class="clearfix">
+<ul>
+<li class="active"><a href="http://www.kohanaphp.com/home">Home</a></li>
+<li><a href="http://www.kohanaphp.com/download">Download</a></li>
+
+<li><a href="http://docs.kohanaphp.com/">Documentation</a></li>
+<li><a href="http://learn.kohanaphp.com/">Tutorials</a></li>
+<li><a href="http://forum.kohanaphp.com/">Forum</a></li>
+<li><a href="http://dev.kohanaphp.com/projects/">Modules</a></li>
+</ul>
+</div>
+<!-- End Main Menu -->
+<!-- Start Body -->
+<div id="body" class="clearfix">
+<!-- Start Content -->
+<div id="content-wrapper">
+<div id="content">
+<p class="intro">Kohana is a <strong>PHP 5 framework</strong> that uses the <strong>Model View Controller</strong> architectural pattern. It aims to be <strong>secure</strong>, <strong>lightweight</strong>, and <strong>easy</strong> to use.</p>
+
+<div style="float:left; padding-right: 4em;">
+ <h2>Features</h2>
+ <ul>
+ <li>Highly secure</li>
+ <li>Extremely lightweight</li>
+ <li>Short learning curve</li>
+ <li>Uses the <abbr title="Model View Controller">MVC</abbr> pattern</li>
+
+ <li>100% UTF-8 compatible</li>
+ <li>Loosely coupled architecture</li>
+ <li>Extremely easy to extend</li>
+ </ul>
+</div>
+<div style="float:left;">
+ <h2>Technology</h2>
+ <ul>
+
+ <li>Strict PHP 5 <abbr title="Object Oriented Programming">OOP</abbr></li>
+ <li>Simple database abstraction using SQL helpers</li>
+ <li>Multiple session drivers (native, database, and cookie)</li>
+ <!-- <li>Advanced cache system with drivers (file, database, memcache, shmop)</li> -->
+ <li>Powerful event handler allows small modifications dynamically</li>
+ <li>Originally based on <a href="http://www.codeigniter.com">CodeIgniter</a></li>
+
+ </ul>
+</div>
+<h3 style="clear:both;padding-top:1em;">How is Kohana Different?</h3>
+<p>Although Kohana reuses many common design patterns and concepts, there are some things that make Kohana stand out:</p>
+<ol>
+ <li><strong>Community, not company, driven.</strong> Kohana development is driven by a team of dedicated people that need a framework for fast, powerful solutions.</li>
+ <li><strong>Strict PHP 5 <abbr title="Object Oriented Programming">OOP</abbr>.</strong> Offers many benefits: visibility protection, automatic class loading, overloading, interfaces, abstracts, and singletons.</li>
+
+ <li><strong>Extremely lightweight.</strong> Kohana has no dependencies on PECL extensions or PEAR libraries. Large, monolithic libraries are avoided in favor of optimized solutions.</li>
+ <li><strong>GET, POST, COOKIE, <em>and</em> SESSION arrays all work as expected.</strong> Kohana does not limit your access to global data, but offers filtering and <abbr title="Cross Site Scripting">XSS</abbr> protection.</li>
+ <li><strong>True auto-loading of classes.</strong> True on-demand loading of classes, as they are requested in your application.</li>
+
+ <li><strong>No namespace conflicts.</strong> All classes are suffixed to allow similar names between components, for a more coherent API.</li>
+ <li><strong><a href="http://upload.wikimedia.org/wikipedia/en/1/1c/Kohana-modules.png">Cascading resources</a> offer unparalleled extensibility.</strong> Almost every part of Kohana can be overloaded or extended without editing core system files. Modules allow multi-file plugins to be added to your application, transparently.</li>
+ <li><strong>Library drivers and API consistency.</strong> Libraries can use different "drivers" to handle different external <abbr title="Application Programming Interface">API</abbr>s transparently. For example, multiple session storage options are available (database, cookie, and native), but the same interface is used for all of them. This allows new drivers to be developed for existing libraries, which keeps the API consistent and transparent.</li>
+
+ <li><strong>Powerful event handler.</strong> Observer-style event handlers allow for extreme levels of customization potential.</li>
+ <li><strong>Rapid development cycle.</strong> Rapid development results in faster response to user bugs and requests.</li>
+</ol></div>
+</div>
+<!-- End Content -->
+<!-- Start Sidebar -->
+<div id="sidebar">
+<!-- Start Download -->
+<div id="download">
+
+<a href="http://www.kohanaphp.com/download"><strong>Download</strong> Latest Version</a></div>
+<!-- End Download -->
+<!-- Start Sidebar Body -->
+<div id="sidebody">
+<div id="sidecontent">
+ <h6>Keep Kohana Alive</h6>
+ <p id="donate">If you use Kohana and find it worth something, please consider <strong><a href="http://www.kohanaphp.com/donate">donating</a></strong>. <a href="http://www.kohanaphp.com/donate/donation_list">Your donations</a> will be directly used to cover the costs of maintaining Kohana.</p>
+
+ <h6 class="feed"><a href="http://forum.kohanaphp.com/search.php?PostBackAction=Search&amp;Type=Comments&amp;Feed=RSS2">Latest Forum Activity</a></h6>
+ <ul>
+ <li><strong>PHPUnit update</strong> &ndash; Nov 20, 10:12:42 AM - <a href="http://forum.kohanaphp.com/comments.php?DiscussionID=4047&amp;Focus=29714#Comment_29714">Read More</a></li>
+ <li><strong>Sprig lazy loading bug</strong> &ndash; Nov 20, 10:04:37 AM - <a href="http://forum.kohanaphp.com/comments.php?DiscussionID=4103&amp;Focus=29713#Comment_29713">Read More</a></li>
+
+ <li><strong>PHPUnit update</strong> &ndash; Nov 20, 10:00:56 AM - <a href="http://forum.kohanaphp.com/comments.php?DiscussionID=4047&amp;Focus=29712#Comment_29712">Read More</a></li>
+ </ul>
+ <h6 class="feed"><a href="http://dev.kohanaphp.com/projects/kohana2/activity.atom?key=yMdCLZBLtWwV3uTVKahzgBpr0451AAiTr4prXhBr&amp;show_changesets=1">Latest Changes</a></h6>
+ <ul>
+ <li><strong>Revision 4684: Added check for mysql_set_charset() before using it, fixes #2344</strong> &ndash; Nov 18, 8:26:48 AM - <a href="http://www.kohanaphp.com/">Read More</a></li>
+
+ <li><strong>Revision 4683: Fixed errors when trying to decode corrupt data. Fixes #2334 Thanks Ștefan!</strong> &ndash; Nov 14, 11:10:53 AM - <a href="http://www.kohanaphp.com/">Read More</a></li>
+ <li><strong>Revision 4682: Fixed problem with ORM::has(), refs #2196 and #2281</strong> &ndash; Nov 11, 2:53:23 PM - <a href="http://www.kohanaphp.com/">Read More</a></li>
+ </ul>
+ </div></div>
+
+<!-- End Sidebar Body -->
+</div>
+<!-- End Sidebar -->
+</div>
+<!-- End Body -->
+<div id="footer">
+<strong>&copy;2007–2009 Kohana Team. All rights reserved.</strong>
+<span class="stats">Powered by Kohana v2.3.3. Rendered in 0.2123 seconds, using 1.69MB of memory.</span>
+</div>
+<!-- Stats -->
+<script type="text/javascript">
+var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+</script>
+<script type="text/javascript">
+var pageTracker = _gat._getTracker("UA-4300382-1");
+pageTracker._initData();
+pageTracker._trackPageview();
+</script>
+</body>
+
+</html>
View
189 libraries/Search.php
@@ -1,37 +1,78 @@
<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Search service provoding access to underlying index
+ *
+ * filename: Search.php
+ * version: 1.0
+ *
+ * @package kosearch
+ * @author Howie Weiner (howie@microbubble.net)
+ * @copyright (c) 2009, Mirobubble Web Design
+ * @license http://opensource.org/licenses/mit-license.php MIT License
+ * @version 1.0 (12th Nov 2009)
+ * @link http://github.com/microbubble/kosearch
+ **/
class Search_Core {
const CREATE_NEW = TRUE;
-
+
+ private static $instance = NULL;
+
private $index_path, $index;
-
- public function __construct() {
- $this->index_path = Kohana::config('search.index_path');
-
+
+ /**
+ * singleton pattern
+ *
+ * @return instance of this class
+ **/
+ public static function instance() {
+
+ if(self::$instance == NULL) {
+ self::$instance = new Search_Core();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Private constructor for singleton pattern
+ */
+ private function __construct() {
+ $this->index_path = Kohana::config('kosearch.index_path');
+
if( !file_exists($this->get_index_path())) {
throw new Kohana_User_Exception('Invalid index path', 'Could not find index path '.$this->get_index_path());
}
elseif(! is_dir($this->get_index_path())) {
- throw new Kohana_User_Exception('Invalid index path', 'index path id not a directory');
+ throw new Kohana_User_Exception('Invalid index path', 'index path id not a directory');
}
elseif(! is_writable($this->get_index_path())) {
- throw new Kohana_User_Exception('Invalid index path', 'Could not find index path ');
- }
-
+ throw new Kohana_User_Exception('Invalid index path', 'Could not find index path ');
+ }
+
+ // set include path
+ if ($path = Kohana::find_file('vendor', 'Zend/Loader'))
+ {
+ ini_set('include_path', ini_get('include_path').PATH_SEPARATOR.dirname(dirname($path)));
+ }
+
$this->load_search_libs();
-
- // set default analyzer to UTF8 with numbers, and case insensitive. Number are useful when searching on e.g. product codes
- //Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive());
-
- // use stemming analyser - http://codefury.net/2008/06/a-stemming-analyzer-for-zends-php-lucene/
- Zend_Search_Lucene_Analysis_Analyzer::setDefault(new StandardAnalyzer_Analyzer_Standard_English());
+
+ if( Kohana::config('kosearch.use_english_stemming_analyser') ){
+ // use stemming analyser - http://codefury.net/2008/06/a-stemming-analyzer-for-zends-php-lucene/
+ Zend_Search_Lucene_Analysis_Analyzer::setDefault(new StandardAnalyzer_Analyzer_Standard_English());
+ }
+ else {
+ // set default analyzer to UTF8 with numbers, and case insensitive. Number are useful when searching on e.g. product codes
+ Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive());
+ }
}
-
+
/**
* Query the index
- * @param String $query Lucene query
- * @return Zend_Search_Lucene_Search_QueryHit hits
+ * @param String $query Lucene query
+ * @return Zend_Search_Lucene_Search_QueryHit hits
*/
public function find($query) {
$this->open_index();
@@ -40,18 +81,22 @@ public function find($query) {
/**
* Add an entry
+ *
+ * @param Searchable $item Model implememting Searchable interface
+ * @param bool $create_new whether or not to create new index when adding item - only used when index is rebuilt
+ * @return Search return this instance for method chaining
*/
public function add($item, $create_new = FALSE) {
-
- if(!$create_new) {
- $this->open_index();
- }
-
+
// ensure item implements Searchable interface
if(! is_a($item, "Searchable")) {
throw new Kohana_User_Exception('Invalid Object', 'Object must implement Searchable Interface');
}
+ if(!$create_new) {
+ $this->open_index();
+ }
+
$doc = new Zend_Search_Lucene_Document();
// get indexable fields;
@@ -98,44 +143,56 @@ public function add($item, $create_new = FALSE) {
}
}
$this->index->addDocument($doc);
+
+ // return this so we can have chainable methods
+ return $this;
}
/**
* Update an entry
* We must first remove the entry from the index, then re-add it. To remove, we must find it by unique identifier
+ *
+ * @param Searchable $item Model to update
+ * @return Search return this instance for method chaining
*/
public function update($item) {
$this->remove($item)->add($item);
+
+ // return this so we can have chainable methods
+ return $this;
}
/**
* Remove an entry from the index
+ *
+ * @param Searchable $item Model to remove
+ * @return Search return this instance for method chaining
*/
public function remove($item) {
- $this->open_index();
-
// now we have the identifier, find it
$hits = $this->find('uid:'.$item->get_unique_identifier());
if(sizeof($hits) == 0) {
Kohana::log("error", "No index entry found for id ".$item->get_unique_identifier());
- }
+ }
else if(sizeof($hits) > 1) {
Kohana::log("error", "Non-unique Identifier - More than one record was returned");
}
if(sizeof($hits) > 0) {
- $this->index->delete($hits[0]->id);
+ $this->open_index()->delete($hits[0]->id);
}
-
- // return this so we can have chainable methods - for an update
- return $this;
+
+ // return this so we can have chainable methods
+ return $this;
}
-
+
/**
* Build new site index
+ * @param Array $items array of Models to add
+ * @return Search return this instance for method chaining
*/
public function build_search_index($items) {
// rebuild new index - create, not open
@@ -144,41 +201,77 @@ public function build_search_index($items) {
foreach($items as $item) {
$this->add($item, self::CREATE_NEW);
}
-
+
$this->index->optimize();
+
+ // return this so we can have chainable methods
+ return $this;
}
-
- private function load_search_libs() {
- if ($path = Kohana::find_file('vendor', 'Zend/Loader'))
- {
- ini_set('include_path', ini_get('include_path').PATH_SEPARATOR.dirname(dirname($path)));
- }
+ /**
+ * Return underlying Lucene index to allow use of Zend API
+ * @return Zend_Search_Lucene the index
+ */
+ public function get_index() {
+
+ $this->create_index();
+
+ return $this->index;
+ }
+
+ /**
+ * Load Zend classes. Requires calling externally if get_index() is used,
+ * or if Zend classes need instatiating
+ */
+ public function load_search_libs() {
require_once 'Zend/Loader/Autoloader.php';
-
+
require_once 'StandardAnalyzer/Analyzer/Standard/English.php';
-
+
Zend_Loader_Autoloader::getInstance();
}
-
+
+ /**
+ * Add a Zend document - utility call to underlying Zend method
+ *
+ * @param Zend_Search_Lucene_Document $doc Document to add
+ * @return Search return this instance for method chaining
+ */
+ public function addDocument(Zend_Search_Lucene_Document $doc) {
+
+ $this->open_index()->addDocument($doc);
+
+ // return this so we can have chainable methods
+ return $this;
+ }
+
private function get_index_path() {
return APPPATH.$this->index_path;
}
-
+
private function open_index() {
-
+
if(empty($this->index)) {
- $this->index = $index = Zend_Search_Lucene::open($this->get_index_path()); // Open existing index;
+ try {
+ $this->index = $index = Zend_Search_Lucene::open($this->get_index_path()); // Open existing index;
+ }
+ catch(Zend_Search_Lucene_Exception $e) {
+ $this->index = Zend_Search_Lucene::create($this->get_index_path()); // fall back to creating new one
+ }
}
+
+ // return index for method chaining.
+ return $this->index;
}
private function create_index() {
-
+
if(empty($this->index)) {
- $this->index = Zend_Search_Lucene::create($this->get_index_path(), true);
+ $this->index = Zend_Search_Lucene::create($this->get_index_path());
}
}
-
-
+
+ /* prevent cloning of singleton */
+ private function __clone() {}
}
View
18 libraries/Search_Field.php
@@ -2,12 +2,22 @@
/**
* Represents a Model's search field, describing the type of Index
- */
+ *
+ * filename: Search_Field.php
+ * version: 1.0
+ *
+ * @package kosearch
+ * @author Howie Weiner (howie@microbubble.net)
+ * @copyright (c) 2009, Mirobubble Web Design
+ * @license http://opensource.org/licenses/mit-license.php MIT License
+ * @version 1.0 (12th Nov 2009)
+ * @link http://github.com/microbubble/kosearch
+ **/
class Search_Field {
private $name;
private $type;
private $html_decode;
-
+
/**
* @param String $name attribute name e.g. db table column name
* @param Zend_Search_Lucene_Field constant $type
@@ -18,11 +28,11 @@ function Search_Field($name, $type, $html_decode = FALSE) {
$this->type = $type;
$this->html_decode = $html_decode;
}
-
+
/**
* Accessor for private properties
*/
public function __get($var){
return $this->$var;
- }
+ }
}
View
30 libraries/Searchable.php
@@ -1,10 +1,20 @@
<?php defined('SYSPATH') or die('no direct scrip access');
/**
- * Searchable Interface for use by Search Class.
- */
+ * Searchable Interface for use by Search Class
+ *
+ * filename: Searchable.php
+ * version: 1.0
+ *
+ * @package kosearch
+ * @author Howie Weiner (howie@microbubble.net)
+ * @copyright (c) 2009, Mirobubble Web Design
+ * @license http://opensource.org/licenses/mit-license.php MIT License
+ * @version 1.0 (12th Nov 2009)
+ * @link http://github.com/microbubble/kosearch
+ **/
interface Searchable {
-
+
const KEYWORD = 0;
const UNINDEXED = 1;
const BINARY = 2;
@@ -13,24 +23,24 @@
const DECODE_HTML = TRUE;
const DONT_DECODE_HTML = FALSE;
-
+
/**
* @return array of Search_Field objects
*/
- function get_indexable_fields();
-
+ function get_indexable_fields();
+
/**
* @return mixed identifier for this item
* For ORM Models this would be the PK
*/
- function get_identifier();
-
+ function get_identifier();
+
/**
* @return String type of item
* For ORM Models this would likely be the object name
*/
- function get_type();
-
+ function get_type();
+
/**
* @return mixed unique id of this item
*/
View
19 libraries/Searchable_ORM.php
@@ -1,17 +1,30 @@
<?php defined('SYSPATH') or die('no direct scrip access');
+/**
+ * Implementation of ORM interface for ORM Models
+ *
+ * filename: Searchable_ORM.php
+ * version: 1.0
+ *
+ * @package kosearch
+ * @author Howie Weiner (howie@microbubble.net)
+ * @copyright (c) 2009, Mirobubble Web Design
+ * @license http://opensource.org/licenses/mit-license.php MIT License
+ * @version 1.0 (12th Nov 2009)
+ * @link http://github.com/microbubble/kosearch
+ **/
abstract class Searchable_ORM_Core extends ORM implements Searchable {
public function get_identifier()
{
return $this->__get($this->primary_key);
- }
+ }
public function get_type()
{
return $this->object_name;
- }
-
+ }
+
public function get_unique_identifier()
{
return $this->object_name."_". $this->get_identifier();
View
40 views/search.php
@@ -36,26 +36,26 @@
<ul>
<li>Add the search module to the modules folder</li>
<li>Enable the search module in application/config (<code>MODPATH.'search'</code>)</li>
- <li>Add Zend Search to the application/vendor folder. First, <a href="http://www.zend.com/community/downloads" title="Zend libraries">download</a> the zend libraries.
+ <li>Add Zend Search to the application/vendor folder. First, <a href="http://www.zend.com/community/downloads" title="Zend libraries">download</a> the zend libraries.
The minimal package will suffice. You can copy the whole set of libraries to ther vendor folder, though you only actually need Search, Loader and Exception classes.</li>
- <li>Add the <a href="http://codefury.net/projects/StandardAnalyzer/" title="StandardAnalyzer">StandardAnalyzer</a> to the application/vendor folder. This allows word <a href="http://en.wikipedia.org/wiki/Stemming" title="wikipedia article">stemming</a> - e.g. plurals.
- <u>Note</u>: this analyser is for English only. If you want to use this module for non-English languages, you will need to modify the Search_Core class on line 28.
+ <li>Add the <a href="http://codefury.net/projects/StandardAnalyzer/" title="StandardAnalyzer">StandardAnalyzer</a> to the application/vendor folder. This allows word <a href="http://en.wikipedia.org/wiki/Stemming" title="wikipedia article">stemming</a> - e.g. plurals.
+ <u>Note</u>: this analyser is for English only. If you want to use this module for non-English languages, you don't need this library, but you will need to modify the config file to use the alternate analyser.
</li>
<li>create application/searchindex directory. To change the name/location of this folder, copy the config/search.php file to application/config and modify accordingly.</li>
</ul>
-
+
<p>Your folder structure should be as follows:</p>
<code>
<p>application / searchindex</p>
<p>application / vendor / StandardAnalyzer</p>
<p>application / vendor / Zend / Exception.php</p>
<p>application / vendor / Zend / Loader</p>
- <p>application / vendor / Zend / Loader.php</p>
- <p>application / vendor / Zend / Search</p>
+ <p>application / vendor / Zend / Loader.php</p>
+ <p>application / vendor / Zend / Search</p>
</code>
-
+
<h3>Search example</h3>
-
+
<p><a href="/search/add" title="add some music">Add some music</a> to search against</p>
<p>By selecting the above link, the following will be added to the search index:</p>
<table border="0" cellspacing="0" cellpadding="0">
@@ -91,12 +91,12 @@
<td>Bob Dylan</td>
<td>Like a Rolling Stone</td>
<td>MP3</td>
- </tr>
+ </tr>
</tbody>
</table>
- <p>You should see some files in the <strong>searchindex</strong> folder. These are the index files</p>
- <p id="form">Try the following searches: <a href="/search?q=stone#form">stone</a>, <a href="/search?q=star#form">star</a>,
- <a href="/search?q=star*#form">star*</a>, <a href="/search?q=sugar#form">sugar</a>, <a href="/search?q=title:stone#form">title:stone</a>,
+ <p>You should see some files in the <strong>searchindex</strong> folder. These are the index files</p>
+ <p id="form">Try the following searches: <a href="/search?q=stone#form">stone</a>, <a href="/search?q=star#form">star</a>,
+ <a href="/search?q=star*#form">star*</a>, <a href="/search?q=sugar#form">sugar</a>, <a href="/search?q=title:stone#form">title:stone</a>,
<a href="/search?q=artist:stone#form">artist:stone</a>, <a href="/search?q=type:cd and title:sugar#form">type:cd and title:sugar</a></p>
<hr/>
<form method="get" action="/search#form">
@@ -105,7 +105,7 @@
<input type="submit" />
</form>
<hr/>
-
+
<? if(isset($results)): ?>
<table border="0" cellspacing="0" cellpadding="0">
<thead>
@@ -117,7 +117,7 @@
</thead>
<tbody>
- <? foreach($results as $hit):
+ <? foreach($results as $hit):
$score = round($hit->score, 2) *100;
?>
<tr>
@@ -126,30 +126,30 @@
<td><div class="graph"><div class="bar" style="width:<? echo $score ?>px"></div></div></td>
</tr>
<? endforeach; ?>
-
+
</tbody>
- </table>
+ </table>
<? endif; ?>
-
+
<h3>How to define content to add to the index</h3>
<p>To add a Model to the search index, it must implement the <code>Searchable</code> interface. This interface is defined as follows:</p>
<code><pre>
/**
* @return array of Search_Field objects
*/
-function get_indexable_fields();
+function get_indexable_fields();
/**
* @return mixed identifier for this item
* For ORM Models this would be the PK
*/
-function get_identifier();
+function get_identifier();
/**
* @return String type of item
* For ORM Models this would likely be the object name
*/
-function get_type();
+function get_type();
/**
* @return mixed unique id of this item
View
267 views/search_example.php
@@ -0,0 +1,267 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>Search Example</title>
+ <style type="text/css" media="screen">
+ body {font-family:Arial; color:#555;}
+ a{color:#69c;}
+ td{border-bottom:1px solid #ccc; padding:3px 5px;}
+ th{border-bottom:2px solid #ccc; padding:3px 5px;; text-align:left;}
+ table{margin-bottom:20px; width:600px;}
+ hr {border-top:1px solid #ccc; border-bottom:none; border-left:none; border-right:none; height:1px;}
+ .wrapper {width:960px; margin:2em auto;}
+ li { padding:5px 0;}
+ h1 {font-family:Baskerville,"Times New Roman",Georgia,sans-serif; color:#369;font-size: 3.6em;font-weight: normal;}
+ h3 {margin-top:2em;}
+ .graph {width:100px; height:10px; border:1px solid #aaa; background:#ccc;}
+ .bar{background:#336699; height:10px;}
+ label{font-weight:bold;}
+ </style>
+</head>
+<body>
+ <div class="wrapper">
+ <h1>kosearch</h1>
+ <h3>Introduction to kosearch</h3>
+ <p>So, what is <em>kosearch?</em> It's a Search module for <a href="http://www.kohanaphp.com/" title="Kohana PHP">Kohana PHP</a>.
+ More specifically, it's an implementation of <a href="http://framework.zend.com/manual/en/zend.search.lucene.html" title="Zend (Lucene) Search">Zend (Lucene) Search</a>,
+ a file-based search/index solution. <em>kosearch</em> provides a simple way to index and search Models.
+ It's perfect for a web site that might contain news, products etc. <em>kosearch</em> also exposes the underlying Zend libraries so that other things
+ can be indexed - PDFs, web pages, Word docs etc.</p>
+ <p>The <em>kosearch</em> module has been written for, and tested against Kohana 2.3.4</p>
+ <h3>Why use Zend Lucene Search</h3>
+
+ <p><strong>Q</strong>. Why Use Zend Lucene Search? I can use MySQL Text Search.</p>
+ <p><strong>A</strong>. True, but for text search to work the table structure must be ISAM. ISAM tables don't support Transactions.
+ Plus Zend search is more powerful than text search, and doesn't hit the database. And of course, you might want to index non-database assets such as PDFs, Word docs, images etc.</p>
+
+ <h3>How to setup the Search module</h3>
+ <?
+ if(isset($msg)):
+ echo '<p style="color:#f00">'.$msg.'</p>';
+ endif;
+ ?>
+ <p>To use the search module follow these steps:</p>
+ <ul>
+ <li>Add the search module to the modules folder</li>
+ <li>Enable the search module in application/config (<code>MODPATH.'search'</code>)</li>
+ <li>Add Zend Search to the application/vendor folder. First, <a href="http://www.zend.com/community/downloads" title="Zend libraries">download</a> the zend libraries.
+ The minimal package will suffice. You can copy the whole set of libraries to the vendor folder, though you only actually need Search, Loader and Exception classes.</li>
+ <li>Add the <a href="http://codefury.net/projects/StandardAnalyzer/" title="StandardAnalyzer">StandardAnalyzer</a> to the application/vendor folder. This allows word <a href="http://en.wikipedia.org/wiki/Stemming" title="wikipedia article">stemming</a> - e.g. plurals.
+ <u>Note</u>: this analyser is for English only. If you want to use this module for non-English languages, you don't need this library, but you will need to modify the config file to use the alternate analyser.
+ </li>
+ <li>create application/searchindex directory. To change the name/location of this folder, copy the config/search.php file to application/config and modify accordingly.</li>
+ </ul>
+
+ <p>Your folder structure should be as follows:</p>
+ <code>
+ <p>application / searchindex</p>
+ <p>application / vendor / StandardAnalyzer</p>
+ <p>application / vendor / Zend / Exception.php</p>
+ <p>application / vendor / Zend / Loader</p>
+ <p>application / vendor / Zend / Loader.php</p>
+ <p>application / vendor / Zend / Search</p>
+ </code>
+
+ <h3>Search example</h3>
+
+ <p><a href="/search/add" title="add some music">Add some music</a> to search against</p>
+ <p>By selecting the above link, the following will be added to the search index:</p>
+ <table border="0" cellspacing="0" cellpadding="0">
+ <thead>
+ <tr>
+ <th>Artist</th>
+ <th>Song Title</th>
+ <th>Media (Model class)</th>
+ </tr>
+ <tr>
+ <td>Ian Brown</td>
+ <td>My Star</td>
+ <td>MP3</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Rolling Stones</td>
+ <td>Brown Sugar</td>
+ <td>MP3</td>
+ </tr>
+ <tr>
+ <td>Stone Roses</td>
+ <td>Sugar Spun Sister</td>
+ <td>CD</td>
+ </tr>
+ <tr>
+ <td>David Bowie</td>
+ <td>Starman</td>
+ <td>CD</td>
+ </tr>
+ <tr>
+ <td>Bob Dylan</td>
+ <td>Like a Rolling Stone</td>
+ <td>MP3</td>
+ </tr>
+ </tbody>
+ </table>
+ <p>You should see some files in the <strong>searchindex</strong> folder. These are the index files</p>
+ <p id="form">Try the following searches: <a href="/search?q=stone#form">stone</a>, <a href="/search?q=star#form">star</a>,
+ <a href="/search?q=star*#form">star*</a>, <a href="/search?q=sugar&amp;form=artists#form">sugar</a>, <a href="/search?q=title:stone&amp;form=artists#form">title:stone</a>,
+ <a href="/search?q=artist:stone&amp;form=artists#form">artist:stone</a>, <a href="/search?q=type:cd and title:sugar&amp;form=artists#form">type:cd and title:sugar</a></p>
+ <hr/>
+ <form method="get" action="/search#form">
+ <label for="q">search</label>
+ <input type="hidden" name="form" value="artists" />
+ <input type="text" name="q" value="<? echo $query ?>" />
+ <input type="submit" />
+ </form>
+ <hr/>
+
+ <? if(isset($results)): ?>
+ <table border="0" cellspacing="0" cellpadding="0">
+ <thead>
+ <tr>
+ <th>Artist</th>
+ <th>Song Title</th>
+ <th>Relevance</th>
+ </tr>
+ </thead>
+ <tbody>
+
+ <? foreach($results as $hit):
+ $score = round($hit->score, 2) *100;
+ ?>
+ <tr>
+ <td><? echo $hit->artist ?></td>
+ <td><? echo $hit->title ?></td>
+ <td><div class="graph"><div class="bar" style="width:<? echo $score ?>px"></div></div></td>
+ </tr>
+ <? endforeach; ?>
+
+ </tbody>
+ </table>
+ <? endif; ?>
+
+ <h3>How to define content to add to the index</h3>
+ <p>To add a Model to the search index, it must implement the <code>Searchable</code> interface. This interface is defined as follows:</p>
+ <code><pre>
+/**
+ * @return array of Search_Field objects
+ */
+function get_indexable_fields();
+
+/**
+ * @return mixed identifier for this item
+ * For ORM Models this would be the PK
+ */
+function get_identifier();
+
+ /**
+ * @return String type of item
+ * For ORM Models this would likely be the object name
+ */
+function get_type();
+
+/**
+ * @return mixed unique id of this item
+ */
+function get_unique_identifier();
+}
+ </pre></code>
+
+ <p>The search module includes an abstract ORM implementation of this interface, that implements all methods except <code>get_indexable_fields</code>
+ <p>The <strong>identifier</strong> would likely be the Primary Key for an ORM Model. This is important, as when a record is retrieved from a search, it only contains indexed data,
+ not all attributes. So, to display all Model attributes, it might be necessary to fetch the record by it's PK.</p>
+ <p>The <strong>unique identifier</strong> must be unique to the Lucene index. If you are indexing more than one Model, PK's will not be unique. The ORM implementation uses both the PK and Model name to create a unique ID.
+ The unique ID is required when updating/deleting an entry from the index.</p>
+ <p>The <strong>type</strong> allows for search by Model type. See the example code in this distribution to see how this works. In the example above, the two media types - 'CD', and 'MP3' - are the class types, not attributes of the class.</p>
+ <p>The <strong>get_indexable_fields</strong> method is the only complicated part to this solution. This method defines what fields to index, and what type of index to create.
+ Essentially, there are 5 field types - different types are stored, indexed and tokenised. These field types are defined by Lucene and explained well in the <a href="http://framework.zend.com/manual/en/zend.search.lucene.html#zend.search.lucene.index-creation.understanding-field-types" title="Zend docs"> Zend documentation</a>.</p>
+ <p>The <code>Searchable</code> interface defines constants mapping to these field types, along with another set of constants relating to HTML Decoding.</p>
+ <p>For example, a blog Model might be defined as follows:</p>
+<code><pre>
+class Blog_Model extends Searchable_ORM {
+
+ /**
+ * Searchable interface implementation
+ */
+ public function get_indexable_fields() {
+ $fields = array();
+ $fields[] = new Search_Field('url', Searchable::UNINDEXED);
+ $fields[] = new Search_Field('title', Searchable::TEXT, Searchable::DECODE_HTML);
+ $fields[] = new Search_Field('content', Searchable::UNSTORED, Searchable::DECODE_HTML);
+ $fields[] = new Search_Field('date', Searchable::UNINDEXED);
+ return $fields;
+ }
+}
+</pre></code>
+ <p>Here, we are telling the index to:
+ <ul>
+ <li>Store the url, but not Index it. This is so we can display the blog url;</li>
+ <li>Store, Index and Tokenise the title. This allow s it to be searched against and displayed in the results;</li>
+ <li>Index and Tokenise, but don't Store the content. This allows it to be searched against, but cannot be displayed in results</li>
+ <li>Store, but don't Index the date, so we can display the date in the results</li>
+ </ul>
+ <p>We also tell the module to decode the HTML for the title and content prior to indexing. This allows HTML content to be indexed.
+ If you have a CMS solution which allows HTML content to be stored this will be useful. The default is to not decode.</p>
+
+<h3>How to add/remove content to the index</h3>
+<p>Once you have defined your Models to implement the Searchable interface, adding content is simple, using the <strong>Search</strong> class.</p>
+<p>To add a Model to the index:</p>
+<code><pre>
+$search = new Search;
+$search->add($model);
+</pre></code>
+
+<p>To build a new index, build an array of indexable models. The index will be re-created fresh:</p>
+<code><pre>
+$search = new Search;
+$search->build_search_index($models);
+</pre></code>
+
+<p>To update a Model, it is removed, then re-added to the index:</p>
+<code><pre>
+$search = new Search;
+$search->update($model);
+</pre></code>
+
+<p>To delete a Model:</p>
+<code><pre>
+$search = new Search;
+$search->remove($model);
+</pre></code>
+
+<h3>How to search the index</h3>
+<p>This bit is really tricky ;-)</p>
+<code><pre>
+$search = new Search;
+$search->find($query);
+</pre></code>
+
+<p>The example above gives a few ideas about how to query the index. I suggest reading the
+ <a href="http://framework.zend.com/manual/en/zend.search.lucene.query-language.html" title="Zend docs">documentation</a>
+ about all the possible ways to search using the query language - terms, fields, wildcards, ranges, booleans etc.
+</p>
+<h3>How to index other content</h3>
+<p>Zend Search is capable of indexing web pages, PDFs, Word docs etc. The docs explain in detail how to do this. Here's an example that indexes the Kohana home page</p>
+<p><a href="/search/addurl" title="add the Kohana home page">Add Kohana home page</a> to search against</p>
+<hr/>
+<p id="form2">Now try the following search: <a href="/search?q=kohana&amp;form=kohana#form2">kohana</a></p>
+<hr/>
+<form method="get" action="/search#form2">
+ <label for="q">search</label>
+ <input type="hidden" name="form" value="kohana" />
+ <input type="text" name="q" value="<? echo $query ?>" />
+ <input type="submit" />
+</form>
+<hr/>
+
+<?
+ if(isset($results2)) {
+ echo $results2;
+ echo '<hr/>';
+ }
+?>
+<p><em>kosearch</em> is developed and maintained by <a href="http://www.badlydrawntoy.com" title="visit badlydrawntoy's blog">badlydrawntoy</a>
+</div>
+</body>
+</html>

0 comments on commit 4279b7c

Please sign in to comment.
Something went wrong with that request. Please try again.