The New Myth family: SprintPHP Bonfire Practical CodeIgniter 3

New Myth Media Blog

Serving the New Myth Media Family.

Practical CodeIgniter 3 Released

My new book about making the most of CodeIgniter 3 is out in Early Access!

Continuing on from the previous post, this tutorial will look at taking our basic Entities and making the more flexible, and more capable. Again, this isn’t meant to be a full demonstration of the Repository pattern, but simply examining one particular aspect of it.

What was wrong with the previous example? For starters, all of our class properties had to be public to allow it to work with the Model. While that’s not the worst thing in the world, it is definitely not ideal.

Getters and Setters

The first step is to make the class properties protected instead of public. In order to make those visible to the Model, we’ll use the __get() and __set() magic methods to provide access.

public function __get(string $key)
{
    if (isset($this->$key))
    {
        return $this->$key;
    }
}

public function __set(string $key, $value = null)
{
    if (isset($this->$key))
        
    {

        $this->$key = $value;
      
    }
}

This solves our problem, but simply adds extra code between the value and your code, which is good for encapsulation, but we can do better here. There are going to be numerous times that you want to perform some logic whenever we a value on the Entity. For example, you might want to automatically hash a password whenever it’s set. Or you might want to always keep your dates stored as DateTime instances. So, how do we make this simple?

For that, let’s add some new functionality to the setter that allows us to call any method that had set_ and then property name, like set_password.

public function __set(string $key, $value = null)
{
    // if a set* method exists for this key,
       
    // use that method to insert this value.
       
    $method = 'set_'.$key;
     
    if (method_exists($this, $method))
     
    {
          
        $this->$method($value);
        
    }
      
    // A simple insert on existing keys otherwise.
     
    elseif (isset($this->$key))
        
    {
          
        $this->$key = $value;
      
    }

}

Now, you could solve your business needs with simple little functions like these:

protected function set_password(string $value)
{
    $this->password = password_hash($value, PASSWORD_BCRYPT);
}

protected function set_last_login(string $value)
{
    $this->last_login = new DateTime($value);
}

Whenever you set $user->password = ‘abc’, or $user->last_login = ’10-15-2016 3:42pm’ your custom methods will automatically be called, storing the property as your business needs dictate. Let’s do the same thing for the getters.

public function __get(string $key)
 
{
          
    // if a set* method exists for this key,
       
    // use that method to insert this value.
       
    if (method_exists($this, $key))
        
    {
              
        return $this->$key();
      
    }

         
    if (isset($this->$key))
        
    {
          
        return $this->$key;
        
    }
  
}

In this case, we’re simply checking for a method with the exact name as the class property. You can set these methods as public and then it would work the same, no matter whether it was called as a method or a property, $user->last_login or $user->last_login():

public function last_login($format=‘Y-m-d H:i:s’)
{
    return $this->last_login->format($format);
}

By setting a default value for $format, it works either way, but you can now get the value in the format you need it at that time, instead of being restricted to a single format.

Filler

This has already helped our classes to become more capable and flexible, at the same time helping us to maintain our business rules, and still easily be saved to the database and gotten back out again intact. But wouldn’t it be nice if we could just shove an array of key/value pairs at the class and have it fill the properties out automatically, but only work with existing properties? This makes it simple to grab data from $_POST, create a new Entity class, and shove it there before saving. Even better if we can customize data on the way in the same way we did with setters, right? Welcome to the fill() method:

public function fill(array $data)
  
{
          
    foreach ($data as $key => $var)
        
    {
              
        $method = 'fill_'. $key;
           
        if (method_exists($this, $method))
         
        {
              
            $this->$method($var);
          
        }

         
        elseif (property_exists($this, $key))
          
        {
              
            $this->$key = $var;
            
        }
      
    }
  
}

A quick example should make this one make sense. First, let’s grab the POST data, add it to a new User object, and save it to the database:

$data = $_POST;
$user = new App\Entities\User();
$user->fill($data);
$userModel->save($user);

If this were a registration form we were handling, we might be getting apassword field that we wanted to make sure was hashed. So, a quick fill_ method and we’re good to go. For this example, we’ll just re-use the setter we created earlier:

protected function fill_password(string $value)_
{
    $this->set_password($value);
}

The Entity Class

To make this all simple to re-use, we should create a new Entity class that our Entities can extend and get these features automatically. Here’s one such class, that also takes care of our timestamps, including timezone conversions.

<?php namespace Myth;

/**
 * Class Entity
 *
 * A base class for entities that provides some convenience methods
 * that make working with entities a little simpler.
 *
 * @package App\Entities
 */
class Entity
{
    /**
     * Given an array of key/value pairs, will fill in the
     * data on this instance with those values. Only works
     * on fields that exist.
     *
     * @param array $data
     */
    public function fill(array $data)
    {
        foreach ($data as $key => $var)
        {
            $method = 'fill_'. $key;
            if (method_exists($this, $method))
            {
                $this->$method($var);
            }

            elseif (property_exists($this, $key))
            {
                $this->$key = $var;
            }
        }
    }

    //--------------------------------------------------------------------

    //--------------------------------------------------------------------
    // Getters
    //--------------------------------------------------------------------

    /**
     * Returns the created_at field value as a string. If $format is
     * a string it will be used to format the result by. If $format
     * is TRUE, the underlying DateTime option will be returned instead.
     *
     * Either way, the timezone is set to the value of $this->timezone,
     * if set, or to the app's default timezone.
     *
     * @param string $format
     *
     * @return string
     */
    public function created_at($format = 'Y-m-d H:i:s'): string
    {
        $timezone = isset($this->timezone)
            ? $this->timezone
            : app_timezone();

        $this->created_at->setTimezone($timezone);

        return $format === true
            ? $this->created_at
            : $this->created_at->format($format);
    }

    //--------------------------------------------------------------------

    /**
     * Returns the updated_at field value as a string. If $format is
     * a string it will be used to format the result by. If $format
     * is TRUE, the underlying DateTime option will be returned instead.
     *
     * Either way, the timezone is set to the value of $this->timezone,
     * if set, or to the app's default timezone.
     *
     * @param string $format
     *
     * @return string
     */
    public function updated_at($format = 'Y-m-d H:i:s'): string
    {
        $timezone = isset($this->timezone)
            ? $this->timezone
            : app_timezone();

        $this->updated_at->setTimezone($timezone);

        return $format === true
            ? $this->updated_at
            : $this->updated_at->format($format);
    }

    //--------------------------------------------------------------------

    //--------------------------------------------------------------------
    // Setters
    //--------------------------------------------------------------------

    /**
     * Custom value for the `created_at` field used with timestamps.
     *
     * @param string $datetime
     *
     * @return $this
     */
    public function set_created_at(string $datetime)
    {
        $this->created_at = new \DateTime($datetime, new \DateTimeZone('UTC'));

        return $this;
    }

    //--------------------------------------------------------------------

    /**
     * Custom value for the `updated_at` field used with timestamps.
     *
     * @param string $datetime
     *
     * @return $this
     */
    public function set_updated_at(string $datetime)
    {
        $this->updated_at = new \DateTime($datetime, new \DateTimeZone('UTC'));

        return $this;
    }

    //--------------------------------------------------------------------

    //--------------------------------------------------------------------
    // Magic
    //--------------------------------------------------------------------

    /**
     * Allows Models to be able to get any class properties that are
     * stored on this class.
     *
     * For flexibility, child classes can create `get*()` methods
     * that will be used in place of getting the value directly.
     * For example, a `created_at` property would have a `created_at`
     * method.
     *
     * @param string $key
     *
     * @return mixed
     */
    public function __get(string $key)
    {
        // if a set* method exists for this key,
        // use that method to insert this value.
        if (method_exists($this, $key))
        {
            return $this->$key();
        }

        if (isset($this->$key))
        {
            return $this->$key;
        }
    }

    //--------------------------------------------------------------------

    /**
     * Allows Models to be able to set any class properties
     * from the result set.
     *
     * For flexibility, child classes can create `set*()` methods
     * to provide custom setters for keys. For example, a field
     * named `created_at` would have a `set_created_at` method.
     *
     * @param string $key
     * @param null   $value
     */
    public function __set(string $key, $value = null)
    {
        // if a set* method exists for this key,
        // use that method to insert this value.
        $method = 'set_'.$key;
        if (method_exists($this, $method))
        {
            $this->$method($value);
        }

        // A simple insert on existing keys otherwise.
        elseif (isset($this->$key))
        {
            $this->$key = $value;
        }
    }

    //--------------------------------------------------------------------
}

The Repository Design Pattern is very useful in application design. At the core of this, though, is the Entity, a class that simply represents a single object of whatever data you are modeling. This could be a single User, a Comment, an Article, or anything else in your app. The trick is to ensure that’s all it is. It can (and should!) handle some of the business logic, and can include convenience methods to combine data in various ways, or work with other Repositories to get related data. But it shouldn’t have a care in the world about how to persist itself. That’s the Repository’s job.

In today’s tutorial, we’ll skip all of the steps of using a Repository and just look at how to make working with Entity classes as simple as possible, while still being very flexible.

Getting Entities from the Model

The first thing to look at is how do we get this data from the model itself? Lucky for us, CodeIgniter’s Database layer has been able to convert the results from database queries into custom classes since at least version 2. I didn’t know about this until version 3 was about ready to be released, as it wasn’t documented at the time. Doing this is as easy as passing fully-qualified class name as the only parameter to the getResult() method:

$rows = $db->table(‘users’)->limit(20)->get();
$user = $rows->getResult(‘App\Entities\User’);

This would give you an array of User instances. As long as the Entity class has properties that a) match the names of the columns in the database, and b) the model can access those parameters, their values will be filled in and you’ll be able to work with the classes straight-away. Let’s take a look at some very basic versions to see this in action.

The UserModel might look something like this:

<?php namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    protected $table = ‘users’;
    protected $returnType = ‘App\Entities\User’;
    protected $useSoftDeletes = false;
    protected $allowedFields = [
        ‘username’, ‘email’, ‘password_hash’
    ];
    protected $useTimestamps = true;
}

Notice the $returnType property? By setting this to the Entity class, all of our results will be returned as instances of App\Entities\User by default. We can always change that at runtime if we need to.

Also, notice the $allowedFields property. This is especially important for a couple of reasons. The first is that the Model class forces you to fill this in if you want to pass it an array of key/value pairs during a save(), insert(), or update() call to help protect against Mass Assignment attacks, or simple human error. But it will also come in handy when we want to save the object back to the database. More on that a little later.

The Entity Class

Now lets look at the simplest version of the Entity class that we can make. In the next blog post we’ll explore a much more powerful version. We would create a new file at /application/Entities/User.php, creating the new Entities folder since it’s not there by default. It might look something like this:

<?php namespace App\Entities;

class User 
{
    public $id;
    public $username;
    public $email;
    public $password_hash;
    public $created_at;
    public $updated_at;

    public function setPassword(string $password)
    {
        $this->password_hash = password_hash($password, PASSWORD_DEFAULT);

        return $this;
    }

    public function createdOn($format = ‘Y-m-d’)
    {
        $date = new DateTime($this->created_at);
        return $date->format($format);
    }
}

As I said, this is an extremely basic version. But it works to demonstrate the usefulness. It contains properties for all of the columns in the database, not just the ones listed in $allowedFields. Since they are all public, they can be accessed from outside the class whenever you need them, whether in a controller, view, or in the model itself when saving. In real apps, we would likely want to make those protected properties to keep things a little safer. We’ll look at that in the next post, and combine it with some powerful convenience methods to really make working with Entities simple and, dare I say it, fun.

This small example, also includes two convenience methods. The first helps ensure a business rule by make a single place that determines how our password is hashed. The second allows you to retrieve a formatted version of the created_at date. Neither of these are ground-breaking. They’re only there to give you some ideas of basic methods you might find helpful.

Saving the Entity

CodeIgniter’s Model class is smart enough to recognize Entity classes whenever you perform a save(), update(), or insert() call, and convert that class to an array of key/value pairs that can be used to create or update the record in the database.

But, how does it know which fields should be allowed to update the database? Remember the $allowedFields array the model has? That’s the key. It uses that list of fields and grabs the values from the Entity class. In our example, it would create an array that looks something like:

[
    ‘username’ => ‘blackjack’,
    ‘email’ => ‘jack.black@example.com’,
    ‘password_hash’ => ‘. . .’
]

Notice that it did not grab the id, created_at, or update_at fields. That’s because the id field is automatically assigned by the database and we shouldn’t be able to change it, and the date fields are managed by the Model class itself, and we don’t want outside classes mucking with the dates.

So, when it comes to saving your data, there’s nothing special to do. Just pass your Entity to the save(), update(), or insert() method, and the Model takes care of the rest.

// Grab a user
$user = $userModel->find(15);

// Update their email
$user->email = ‘blackjack@example.com’;

// Save the user
$userModel->save($user);

Up Next

Hopefully, this gets you interested in exploring this type of pattern with your applications. Even if you don’t do a full-repository pattern, this simple change makes your code much more manageable and can be very powerful.

In the next post, we’ll take it a little farther by hiding those class properties, but still ensuring they’re accessible to the Model during creation and saving. Then we’ll craft a small Entity class that we can base all of our Entities from that provides some magic that allows us to manipulate the data on the way in and out, and even provide a new fill() method that takes an array and populates/changes the class properties. All of this allows for much more freedom, power, and flexibility in your Entity classes.

Now that the ribbon has been taken off of the first semi-release of CodeIgniter 4, people are wondering how they get started with it. A couple of things have changed that make a big impact on getting started when you're expecting it to be just like CI3. This article aims to help you out there.

Download It

There are two different ways you can download it currently, but they're both basically the same thing.

  1. From the terminal type: git clone git@github.com:bcit-ci/CodeIgniter4.git which will pull down the latest version of the develop branch into a new directory called CodeIgniter4.
  2. From GitHub do a straight download by clicking Clone or Download and then selecting Download Zip. Unzip into a new folder when you're done.

Look Around

If you start taking a look around the new code you'll see a slightly different folder layout than you're used to, though the changes are minimal:

/application        - Your app's files go here
/public             - This is the "web root" of your application. index.php is here
/system         - CodeIgniter lives here
/tests              - Holds tests for CodeIgniter and your application
/user_guide_src - The source files for the user guide. Instructions how to build them.
/writable           - A place for any folder that needs to be writable

The two most important right now are the public and the user_guide_src folders. public holds the new web root of the application. Best practices tell us that your application should live outside of the web and not be accessible from a browser. With this layout, only the files in public are available to end-users through the browser. This is in line with every other major framework out there, but is a change from the way that CodeIgniter has always done it.

The user_guide_src folder contains all of the current documentation for the framework. To the best of our knowledge it is completely up to date with the current release, and we plan on keeping it in sync as we go. This will be your best friend, as you explore over the coming days or weeks. While this isn't the generated HTML, all of the files in the source folder inside it are human-readable and laid out similarly to what you're used to in CI3 docs. Take time to read through the new things in here as most of your questions should be answered, and you'll hopefully find some nice surprises lurking in places.

The following pages are good reads to get started with:

Start Playing

In order to start playing around with the new code, you'll need to get it running in a browser. There's a number of ways to do it, but we'll cover two here.

PHP Server

PHP has a built-in web server now. This is the simplest way to get running. Jump to the command line, navigate to the public directory, and type the following:

$ php -S localhost:8000

That's it. Back to your browser, navigate to http://localhost:8000 and you should see the shiny new CI4 welcome page.

Virtual Host

A more permenant solution is to have another web server running locally, like Apache or NGINX, and create a new virtual host for it. This allows you to create a name for the site, like ci4.dev, and use that in your browser. This is especially helpful so that you don't have to worry about RewriteBase commands for Apache config, or any other tricky ways to get past the public folder. When you setup the virtual host, make sure it is pointing to the public folder or it won't work.

Here are some helper guides for those of you using popular AMP stacks locally:

Note that most of these are essentially the same thing, since you're editing raw Apache config files.

Laravel Homestead is another excellent solution for a PHP7 virtual machine running under NGINX.


Lastly, have fun!

It used to be that the majority of the websites were silos, not communicating with other websites much at all. That has changed a lot in the last few years, though, to the point where many sites consume information from external APIs, or communicate with third-party services, like payment gateways, social networks, and more, all on a day-to-day basis. For PHP developers, this typically involves the use of curl to make this happen. That means that any full-stack framework should provide some form of capabilities to help you out there. In the upcoming CodeIgniter 4, we've provided a lightweight, yet still very flexible, HTTP client built-in.

The CURLRequest Class

The CURLRequest class extends our more generic Request class to provide most of the features that you'd need on a day-to-day basis. It is a synchronous-only HTTP client. Its syntax is modeled very closely after the excellent Guzzle HTTP Client. This way if you find that the built-in client doesn't do everything you need it to, it is very easy to switch over to using Guzzle and take advantage of some of its more advanced features, including asynchronous requests, non-reliance on curl, and more.

Why build our own solution? For the last decade, many developers have looked to CodeIgniter as the framework that you can download and have 90% or more of the features you need to build your site at your fingertips. Bundling something like Guzzle into the framework doesn't make sense, when Guzzle provides its own HTTP layer, duplicating a large part of the core system code. If we wanted to provide a solution, we had to build our own, based around our own HTTP layer. Being a lightweight solution, it is primarily a wrapper around curl and so the only real trick was ensuring syntax compatibility with Guzzle to make your transitions, if you need to do them, as simple as possible.

NOTE: This does require that the curl library be installed and usable.

A Few Quick Examples

Let's walk through a few quick examples to see how easy and convenient it is to have a curl wrapper in our bundle of tricks. These examples are obviously very bare-bones and don't provide all of the details you would need in a finished application.

A Single Call

Let's say you have another site that you need to communicate with, and that you only need to grab some information from it once, not as part of a larger conversation with the site. You can very simply grab the information you need something like this:

$client = new \Config\Services::curlrequest();
$issues = $client->request('get', 'https://api.github.com/repos/bcit-ci/CodeIgniter/issues');

The request() method takes the HTTP verb and URI and returns an instance of CodeIgniter\HTTP\Response ready for you to use.

if ($issues->getStatusCode() == 200)
{
    echo $issues->getBody();
}

Consuming an API

If you're working with an API for more than a single call, you can pass the base_uri in as one of a number of available options. Then, all following request URIs are appended to that base_uri.

$client = new \Config\Services::curlrequest(['base_uri' => 'https://example.com/api/v1/']);

// GET http://example.com/api/v1/photos
$client->get('photos');

// GET http://example.com/api/v1/photos/13
$client->delete('photos/13');

Submitting A Form

Often, you will need to submit a form to an external site, sometimes even with file attachments. This is easily handled with the class.

$client =  new \Config\Servies::curlrequest();
$response = $client->request('POST', 'http://example.com/form', [
    'multipart' => [
        'foo' => 'bar',
        'fuz' => ['bar', 'baz'],
        'userfile' => new CURLFile('/path/to/file.txt')
    ]
]);

Multitude of Options

The library supports a wide array of options, allowing you to work with nearly any situation you might find yourself up against, including:

  • setting auth values for HTTP Basic or Digest authentication
  • setting arbitrary header values for more complex authentication (or any other needs)
  • specifying the location of PEM-formatted certificates,
  • restricting execution time
  • allowing redirects to be followed,
  • return content even when it encounters an error
  • easily upload JSON content
  • send query string or POST variables with the request
  • specify whether SSL certificates should be validated or not (helpful for development servers who don't have full SSL certificates in place)
  • and more.

Hopefully, this new addition to the framework will come in handy during development and help make using curl much more pleasant.

While work on the database layer is still under heavy construction, I think we're far enough along to be able to give you a glimpse into how it works, how it's the same, and how it's different from what you're accustomed to in previous versions.

First things first: how far along is it? At the moment we can connect to a MySQL database and run both raw queries, and use the Query Builder to run queries. I just wrapped up tests on the existing Query Builder features, I believe, so it should be fairly solid at the moment. What's left? The Query Caching layer needs built, as does the Forge, and the utility methods, as well as getting the drivers in place and in shape.

What's the Same?

While the underlying structure of the database engine has been changed a fair amount, what you'll see while using it will be fairly familiar. The biggest cosmetic difference is in method names using CamelCasing instead of snake_casing. The query builder still largely works like you're used to, so there won't be much to relearn. You should be able to dive right in and use your years of experience with just the tiniest amount of time getting accustomed to it.

What's different?

I won't go into all of the details here, just the big items. Instead of a boring little list, let's take a look at a few examples of it in action.

Configuration

The config files are still mostly like the old ones. There was no need to reinvent the wheel here since it worked great already. They have been changed to be a simple class, like the rest of them but the fields are the same.

class Database extends \CodeIgniter\Database\Config
{
    /**
     * Lets you choose which connection group to
     * use if no other is specified.
     *
     * @var string
     */
    public $defaultGroup = 'default';

    /**
     * The default database connection.
     *
     * @var array
     */
    public $default = [
        'dsn'          => '',
        'hostname'     => 'localhost',
        'username'     => '',
        'password'     => '',
        'database'     => '',
        'dbdriver'     => 'MySQLi',
        'dbprefix'     => '',
        'pconnect'     => false,
        'db_debug'     => (ENVIRONMENT !== 'production'),
        'cache_on'     => false,
        'cachedir'     => '',
        'charset'      => 'utf8',
        'dbcollat'     => 'utf8_general_ci',
        'swapPre'      => '',
        'encrypt'      => false,
        'compress'     => false,
        'stricton'     => false,
        'failover'     => [],
        'saveQueries' => true,
    ];

    //--------------------------------------------------------------------

}

Raw Queries

Making queries without using the Query Builder is simple. Get a database instance, run the query() method and get back a result object.

// Connects to the default connection, 'default' in this example.
$db = Config\Database::connect();

$query = $db->query('SELECT * FROM users');

// Get results as objects.
$results = $query->getResultArray();
// Get results as arrays
$results = $query->getResultObject();
// Get result as custom class instances
$result = $query->getResult('\My\Class');

The first thing to note is that num_rows() has been removed. For the last few years it's use has been discouraged, and written out of examples, since some drivers have horrible performance and/or memory issues when using it. Instead, all result*() methods return empty arrays if no results, while all row*() methods return null.

Parameter binding still exists:

$query = $db->query('SELECT * FROM users WHERE id > ? AND role = ?', [3, 'Admin']);

Parameter binding has gotten a new trick, though, with named parameters, for more readable (and flexible) queries:

$query = $db->query('SELECT * FROM users WHERE id > :id AND role = :role',
    [ 'id'   => 3,
      'role' => Admin'
    ]
);

All values are automatically escaped, of course, to keep your queries safe.

Saved Queries

One of the big changes in the database layer is that all queries are saved in history as Query objects, instead of strings in an array. This is partially to clean up the code and remove some resposibilities from other classes. But it will also allow for more flexibility in the Query Caching layer, and other places. Just be aware that if you need to get $db->getLastQuery() you're getting a Query object back, not a string.

The query objects hold the query string, which can be retrieved with and without the parameters bound to it, as well as any error information that query might have, and performance data (when it started, how long it took).

Query Builder

The Query builder operates mostly as you're used to, with one big change. The Query Builder is now it's own class, not part of the driver files. This helps keep the code cleaner, and works nicely with the new Query objects and named paramater binding, which is used throughout the builder.

One of the biggest benefits of having it as a completely separate class is that it allows us to keep each query completely seperate. There is no more "query bleeding" where you're in the middle of building a query and make a call out to another method to retrieve some values, only to have the other method build it's own query, and incorrectly using the values from the original query. That's a thing of the past.

The primary visible change in the Query Builder is how you access the builder object. Since it's no longer part of the driver, we need a way to get an instance of it that is setup to work with the current driver. That's where the new table() method comes into play.

$db = Config\Database::connect();

$result = $db->table('users')
             ->select('id, role')
             ->where('active', 1)
             ->get();

Basically, the main table is now specified in the table() method instead of the get() method. Everything else works just like you're used to.

What's Still Coming?

Aside from the previously mentioned parts that need implementing, there are some nice additions potentially coming down the pike. There's no guarantee all of these items will make it in, but these are a handful of the ideas I'd currently like to see make it in the database layer.

Native Read/Write Connection Support is likely to make it in the configuration of your database. Once the connections have been defined, using them is automatic. The Connection will determine if your query is a write query or read query and choose the correct connection to use based on that. So, if you have a master/slave setup, this should make things a breeze.

New QueryBuilder Methods will likely be added. I'm going to scout out the other frameworks a little more, to see if there's features that are useful enough to warrant looking into. The following are my short list, though:

  • first() is a convenience method to retrieve the first result itself.
  • increment() and decrement() methods would be nice to have.
  • chunk() would loop over all matching results in chunks of 100 (or whatever). This allows you to process potentially thousands or even millions of rows without completely killing your server or running out of memory.

Enhanced Model The only reason the CI_Model class exists in v3 is to provide easy access to the rest of the framework by using magic methods to access the loaded libraries, etc. That's not really necessary anymore, since there is no singleton object. So, it only makes sense to take this opportunity to actually create a Model class that is useful. The details of this haven't been discussed much in the Council, yet, so I can't say what will make it in. Over the years, though, creating base MY_Model classes with a a fair amount of convenience features has become fairly common. Time to build it into the core, I think.

Simpler Pagination This idea is ripped straight from Laravel, but the first project I worked on in Laravel, it was the pagination that blew me away. This would work hand-in-hand with the Enhanced Model, allowing you to simply call $model->paginate(20) and it would be smart enough to figure out the rest of the details. Obviously, there's more involved than that, but if you've ever used Laravel's version, you'll know how much of a breath of fresh air it is compared to CodeIgniter's. Now, there's is built into their ORM, so it might turn out to be not very feasible for our system, but it's definitely something I want to look into.


I hope that gets you excited about the future of the framework, and maybe calms down some fears that things are going to change too much. One of my big goals while rewriting is to keep the system familiar, while bringing more modern code practices and flexibility into the system.

Are there any features from other systems that you love and miss when you work in CodeIgniter that you'd like us to consider? I won't say that everything (or even any of it) will make its way into the system, but we'll definitely consider it.