PHPainfree is a relatively young PHP framework written by Eric Ryan Harrison.
The README for the project is in first person, so I’ll let PHPainfree explain what it is:
I am an ultra-lightweight PHP framework. I am inspired by the MVC concept, but I’m too artsy to let myself be defined by labels like that. I basically do what I want.
To try the framework out I cloned the git repository. This should be roughly equivalent to version 0.6.3, in case you want to follow along at home.
My setup is a bit unique, as a trial usage in a sub-directory of my local machine, but you should be able to adjust your install to suit.
It essentially boiled down to these steps:
Shell Transcript
jmhobbs@katya:/var/www/localhost/PHPainfree$ ls CHANGELOG.md htdocs includes LICENSE README.md templates jmhobbs@katya:/var/www/localhost/PHPainfree$ cd includes/ jmhobbs@katya:/var/www/localhost/PHPainfree/includes$ cp PainfreeConfig-GENERIC.php PainfreeConfig.php jmhobbs@katya:/var/www/localhost/PHPainfree/includes$ vim PainfreeConfig.php jmhobbs@katya:/var/www/localhost/PHPainfree/includes$ cd .. jmhobbs@katya:/var/www/localhost/PHPainfree$ cd htdocs/ jmhobbs@katya:/var/www/localhost/PHPainfree/htdocs$ ln -s ../includes/ . jmhobbs@katya:/var/www/localhost/PHPainfree/htdocs$ ln -s ../templates/ . jmhobbs@katya:/var/www/localhost/PHPainfree/htdocs$ ls -a . .. css .htaccess images includes index.php js templates jmhobbs@katya:/var/www/localhost/PHPainfree/htdocs$ vim .htaccess
Listing: htdocs/.htaccess
RewriteEngine On
RewriteBase /PHPainfree/htdocs/
RewriteRule ^js/(.+)$ js/$1 [L]
RewriteRule ^css/(.+)$ css/$1 [L]
RewriteRule ^images/(.+)$ images/$1 [L]
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule ^(.+)$ index.php?route=$1&%{QUERY_STIRNG} [L]After that is all done, it should happily serve up it’s welcome page.
When PHPainfree claim’s to be ultra-lightweight, they mean it. Many of the bits and pieces you would expect on a framework just don’t exist. Many.
But more on that later. For now, let’s take apart the default files and build something out of them. What we’ll attempt to assemble is that paragon of beginner programs, the todo list.
Looking at the provided example files it really seems to me that this is a very view driven framework. The “logic” part runs first, but really just sets up things for the “view” part. Model and controller seem smashed together into the “logic” files, but this is just my interpretation of the design.
This is how the provided example files flow:
Logic Setup => Template(s) Run => Calls Logic Methods
According to includes/PainfreeConfig.php the BaseView “is the name of your base template inside of the templates folder. This base view generally provides the overall framework of output for your application”.
To feel out how the framework handles I created a very small stub BaseView in templates/layout.tpl
Listing: templates/layout.tpl
1 2 3 4 5 6 7 8 9 10 | <!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" xml:lang="en">
<head>
<title><?php echo $Controller->title(); ?></title>
</head>
<body>
<h1><?php echo $Controller->title(); ?></h1>
</body>
</html> |
According to includes/PainfreeConfig.php the ApplicationController is “…the primary controller for your application”.
I created a new controller for my test, taken almost entirely from the provided includes/Generic.php.
Listing: includes/ToDoList.php
1 2 3 4 5 6 7 8 9 10 | <?php $Controller = new ToDoList(); class ToDoList { public function title () { return "ToDo List"; } } |
The last step for this first exploratory version is to change some variables in our configuration file.
Listing: includes/PainfreeConfig.php
26 27 28 29 30 31 32 33 34 35 36 | $PainfreeConfig = array( // ApplicationController is the primary controller for your // application. Generic.php is provided, but doesn't do anything // except look for the view in the templates/ folder 'ApplicationController' => 'ToDoList.php', // BaseView is the name of your base template inside of the templates // folder. This base view generally provides the overall framework // of output for your application 'BaseView' => 'layout.tpl', |
Once that is done, it should now be serving my new files.
That’s great and all, and we learned about the processing pipeline and stuff, but really, we didn’t do anything.
So let’s get down to it. I like a nice MVC pattern, with convention over configuration, so here is how I’m laying out my application. Note that you do not have to do it this way, PHPainfree is written to encourage you to do it, well, just about any way you want.
.
+-- includes
| +-- Autoload
| | `-- BaseController.php
| +-- controllers
| | +-- list.php
| | `-- todo.php
| +-- main.php
| `-- PainfreeConfig.php
`-- templates
+-- layout.tpl
+-- list
| `-- index.tpl
`-- todo
`-- index.tplTo make my structure work, I had to create my own routing system. I couldn’t find anything built into PHPainfree that would do this for me, but that’s okay because it’s pretty simple. I set my ApplicationController option to “main.php” and placed this in there.
Listing: includes/main.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php $controller = 'list'; $method = 'index'; $arguments = array(); // When the path is empty it routes to $PainfreeConfig['DefaultRoute'] $path = explode( '/', $Painfree->route ); if( 1 <= count( $path ) ) $controller = preg_replace( '/[^a-z0-9_-]/', '', strtolower( $path[0] ) ); if( 2 <= count( $path ) ) $method = preg_replace( '/[^a-z0-9_-]/', '', strtolower( $path[1] ) ); if( 3 <= count( $path ) ) $arguments = array_slice( $path, 2 ); $Controller = new Base_Controller(); // Will be replaced by a real controller require_once( 'controllers/' . $controller . '.php' ); |
Note that on line 18 I instantiate a class called BaseController. This is a stub class that I created for all of my controllers to inherit from, that way I have a consistent interface to call in my templates.
My BaseController.php file will be placed into includes/Autoload to take advantage of the loading feature of PHPainfree. Any file placed into the includes/Autoload folder will be automatically included at runtime, just after the configuration file is loaded and just before the logic file is ran. This is useful for loading libraries, or to do some request pre-processing.
Listing: includes/Autoload/BaseController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php class Base_Controller { protected $data = array(); public function title () { return "ToDo List"; } public function render () { global $PainfreeConfig, $method; // Make data available to view $data =& $this->data; // Make the controller available to the view $Controller =& $this; require_once( $PainfreeConfig['TemplateFolder'] . '/' . // My controller classes all end in "_controller", so I cut that off here strtolower( substr( get_called_class(), 0, -11 ) ) . '/' . $method . '.tpl' ); } } |
Up to this point I haven’t touched a database, which PHPainfree has some support for. Real quick I’ll set up a MySQL database for our ToDo application.
MySQL Transcript
mysql> create database todolist; Query OK, 1 row affected (0.02 sec) mysql> grant all on todolist.* to painfree@localhost identified by 'password'; Query OK, 0 rows affected (0.05 sec) mysql> flush privileges; Query OK, 0 rows affected (0.03 sec) mysql> use todolist; Database changed mysql> CREATE TABLE lists ( id INT(6) UNSIGNED AUTO_INCREMENT, name VARCHAR(255), PRIMARY KEY (id) ); Query OK, 0 rows affected (0.01 sec) mysql> CREATE TABLE todos ( id INT(6) UNSIGNED AUTO_INCREMENT, list_id INT(6) UNSIGNED, title VARCHAR(255), created DATETIME, completed DATETIME, PRIMARY KEY(id) ); Query OK, 0 rows affected (0.01 sec) mysql> INSERT INTO lists ( name ) VALUES ( 'Yard Work' ); Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO todos ( list_id, title, created ) VALUES ( 1, 'Mow Grass', NOW() ), ( 1, 'Weed Garden', NOW() ); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql>
Configuring the database connection in PHPainfree is relatively straightforward. Just open up includes/PainfreeConfig.php and find the Database key. This is an array of MySQL connections, which cascade if they fail.
For instance, if you have a development environment and a production environment, you could place your dev configuration after the production configuration.
In development, the production connection would fail and then load the development configuration. Nothing to change, no environment variables to set, it just works.
Listing: includes/PainfreeConfig.php
45 46 47 48 49 50 51 52 53 54 | 'Database' => array( 'Primary' => array( 'type' => 'mysql', 'host' => 'localhost', 'user' => 'painfree', 'pass' => 'password', 'schema' => 'todolist', 'port' => 3306 ) ), |
Using the database is easy too. The $Painfree global variable has a member called db which provides access to our configured database. But what is $Painfree->db? Well, a little bit of digging into the PHPainfree core and we find out it is just a normal MySQLi link object. Nothing fancy, no database abstractions.
Listing: includes/core/DBD/mysql.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?php class mysql { static function connect($host,$user,$pass,$schema,$port) { $db = @new mysqli($host,$user,$pass,$schema,$port); if ( ! mysqli_connect_errno() ) { return $db; } else { return false; //mysqli_connect_errno() . ']: ' . mysqli_connect_error(); } } } |
Applying all this knowledge and configuration, let’s start our first controller, the List_Controller. This first version will simply fetch all the active lists from the database and get them ready for the template.
Listing: includes/controllers/list.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php class List_Controller extends Base_Controller { public function title () { return "List"; } public function lists () { $lists = array(); $stmt = $this->db->prepare( "SELECT id, name FROM lists" ); $stmt->execute(); $stmt->bind_result( $id, $name ); while( $stmt->fetch() ) $lists[] = array( 'id' => $id, 'name' => $name ); $stmt->close(); return $lists; } } $Controller = new List_Controller(); |
Now we need to make our template to use this controller. Again, very basic.
Listing: templates/list/index.tpl
1 2 3 4 5 6 7 | <h1>ToDo Lists</h1> <ul> <?php foreach( $Controller->lists() as $list ): ?> <li><a href="list/view/<?php echo $list['id']; ?>"><?php echo $list['name']; ?></a></li> <?php endforeach; ?> </ul> |
At this point, we should be able to render this view:
From here it is a short work to finish the application with another controller and a few more actions. Rather than post a bunch of reptitive code snippets I’ll provide my completed source, here.
So, there is my first PHPainfree application. As with any new tool, my usage is probably flawed until I learn more about it. So take this review with a grain of salt.
PHPainfree is a young framework, and it’s a thin one. Coming from a heavier framework background, it feels too thin to me. I missed having an ORM, and built in helpers (think Form, Link, Validation). Also, there is no real exception stack that I could find, just $Painfree->debug() for you to use.
MySQL is the only option right now, though it is easily extended. For example, I wrote this in just a few seconds to add SQLite3 support.
Listing: includes/core/DBD/sqlite.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?php class sqlite { static function connect($host,$user,$pass,$schema,$port) { try { $db = @new SQLite3( $host, SQLITE3_OPEN_READWRITE ); return $db; } catch( Exception $e ) { return false; } } } |
However, having multiple drivers is shallow when there is no abstraction element. Since it uses native driver objects, I can’t just switch from MySQL to SQLite3, because I would then have to switch all of my method calls. Using PDO would be a good option for PHPainfree, IMHO.
My other qualm is the rendering stream. I’m used to the standard MVC pattern, where the controller fetches data with models and publishes it via views. There may be a way to work like that in PHPainfree, but it’s not readily apparent.
It’s light, at the cost of including minimal features. And it’s fairly easy to understand. According to SLOCCount there are only 101 lines of source (after removing the config file and default controller). You can read the whole framework in a few minutes.
Really, I think this is a framework to build your own framework. The core idea of PHPainfree is to stay out of your way. If I intended to use PHPainfree on a regular basis, I would set it up the way I like it, dumping libraries into includes/Autoload and then keep a tracking version in git with all my addons.
I think that where you can draw the most value from this framework is building something you love on top of this common core code. So give it a try at http://github.com/februaryfalling/PHPainfree
Posted June 7th, 2010 - PermalinkFirst off, I do not mean dictionary in the Python sense of the word. I mean dictionary in the glossary sense, like Merriam-Webster. This collision of terminology makes Googling for this functionality particularly difficult and frustrating.
I came across three useful Python solutions, and I’m going to detail usage of two of them in this post.
First up is accessing Wordnet.
“Wordnet is a large lexical database of English…”
The only Python way of accessing this (that I came across) is NLTK, a set of
“Open source Python modules, linguistic data and documentation for research and development in natural language processing…”
For various reasons, NLTK is not packaged by Debian, so I had to install it by hand. Even if your distro does package NLTK, you might want to read this bit anyway. Installing was a cinch with easy_install nltk. However, this does not install the corpus (where wordnet is stored). As shown below:
>>> from nltk.corpus import wordnet >>> wordnet.synsets( 'cake' ) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.5/site-packages/nltk-2.0b8-py2.5.egg/nltk/corpus/util.py", line 68, in __getattr__ self.__load() File "/usr/lib/python2.5/site-packages/nltk-2.0b8-py2.5.egg/nltk/corpus/util.py", line 56, in __load except LookupError: raise e LookupError: ********************************************************************** Resource 'corpora/wordnet' not found. Please use the NLTK Downloader to obtain the resource: >>> nltk.download(). Searched in: - '/home/jmhobbs/nltk_data' - '/usr/share/nltk_data' - '/usr/local/share/nltk_data' - '/usr/lib/nltk_data' - '/usr/local/lib/nltk_data' **********************************************************************
So what we need to do is run the NLTK installer, as shown here:
>>> import nltk >>> nltk.download() NLTK Downloader --------------------------------------------------------------------------- d) Download l) List c) Config h) Help q) Quit --------------------------------------------------------------------------- Downloader> d Download which package (l=list; x=cancel)? Identifier> wordnet Downloading package 'wordnet' to /home/jmhobbs/nltk_data... Unzipping corpora/wordnet.zip. --------------------------------------------------------------------------- d) Download l) List c) Config h) Help q) Quit --------------------------------------------------------------------------- Downloader> q True >>>
Now that we have everything installed, using wordnet from Python is straight forward.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # Load the wordnet corpus from nltk.corpus import wordnet # Get a collection of synsets (synonym sets) for a word synsets = wordnet.synsets( 'cake' ) # Print the information for synset in synsets: print "-" * 10 print "Name:", synset.name print "Lexical Type:", synset.lexname print "Lemmas:", synset.lemma_names print "Definition:", synset.definition for example in synset.examples: print "Example:", example |
The output of that is:
---------- Name: cake.n.01 Lexical Type: noun.artifact Lemmas: ['cake', 'bar'] Definition: a block of solid substance (such as soap or wax) Example: a bar of chocolate ---------- Name: patty.n.01 Lexical Type: noun.food Lemmas: ['patty', 'cake'] Definition: small flat mass of chopped food ---------- Name: cake.n.03 Lexical Type: noun.food Lemmas: ['cake'] Definition: baked goods made from or based on a mixture of flour, sugar, eggs, and fat ---------- Name: coat.v.03 Lexical Type: verb.contact Lemmas: ['coat', 'cake'] Definition: form a coat over Example: Dirt had coated her face
Perfect!
There are some caveats to using WordNet with NLTK. First is that the definitions aren’t always ordered in the way you would expect. For instance, look at the “cake” results above. Cake, as in the confection, is the third definition, which feels wrong. You can of course order and filter on the synset name to correct this to some degree.
Second, there is a major load time for getting WordNet ready to use. Your first call to wordnet.sysnsets will take considerably longer than the next ones. On my machine the difference was 3.5 seconds versus 0.0003 seconds.
Last, you are constrained to the English language, as analyzed by Pinceton. I’ll address this issue in the next section.
As I said above, using WordNet is simple, but restrictive. What if I want to use a foreign language dictionary or something? WordNet is only in English. This is where the SDict format comes in. It has lots of free resource files available at http://sdict.com/en/. The best existing parser I found was SDict Viewer which is a dead project, but remarkably complete.
SDict Viewer is an application, so it’s not an easy to install library. However, it is very well written and extracting what you need is simple. You can get my “library” version from http://github.com/jmhobbs/sdictviewer-lib.
Here is an example when it’s all finished:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import sys import sdictviewer.formats.dct.sdict as sdict import sdictviewer.dictutil dictionary = sdict.SDictionary( 'webster_1913.dct' ) dictionary.load() start_word = sys.argv[1] found = False for item in dictionary.get_word_list_iter( start_word ): try: if start_word == str( item ): instance, definition = item.read_articles()[0] print "%s: %s" % ( item, definition ) found = True break except: continue if not found: print "No definition for '%s'." % start_word dictionary.close() |
Here is a sample run:
jmhobbs@katya:~$ python okay.py Cat Cat: (n.) An animal of various species of the genera Felis and Lynx. The domestic cat is Felis domestica. The European wild cat (Felis catus) is much larger than the domestic cat. In the United States the name wild cat is commonly applied to the bay lynx (Lynx rufus) See Wild cat, and Tiger cat. wrote /home/jmhobbs/.sdictviewer/index_cache/webster_1913.dct-1.0.index
As you can see, it gives a nice definition (thank you Webster 1913) and then it has a little junk on the end. This is the index cache, a lookup table for finding words faster. You can avoid saving it by calling dictionary.close(False) instead.
In option 2 I said that SDict Viewer was a dead project, this is because the development has been moved to the Aard Dictionary project. I chose not to pursue this format, as most of the existing resources are stored in HTML formats and I needed plain text. This might be ideal for you though, as they also provide access to Wikipedia archives.
So there you have it. Two viable ways of extracting a plain text definition for a word in Python. Best of luck to you!
Posted March 1st, 2010 - Permalink