Codeigniter4foundations Sample
Codeigniter4foundations Sample
Lonnie Ezell
This book is for sale at http://leanpub.com/codeigniter4foundations
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Primer: Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
The Simple Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Going Deeper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Primer: Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
What Are They? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
How to Use Them . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
When to Use Them . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Creating Your Own . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Extending the Core Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
The first beta of CodeIgniter was released to the world on February 25, 2006 by pMachine. It was
described on its website as “an Open Source Web Application Framework that makes writing kick-
ass PHP programs simple as apple pie”. Inspired by Ruby on Rails, it was built by Rick Ellis as the
foundation for ExpressionEngine, a CMS that Rick and Paul Burdick had built for their own client
work. Well, built for is probably the wrong description. It was ripped out of the CMS and cleaned
up so it could be released on its own.
It rapidly built a large following of PHP developers that loved its speed, flexibility, and simplicity.
It came with a collection of 20 different libraries to help you get things done, and not spend time
re-building solutions. For many years it was one of–if not the–most widely used PHP framework.
Times have changed a lot since then, though. PHP itself, has grown up a lot, adding many
sophisticated features previously reserved for “real” languages, like Java. It’s gotten more powerful,
faster, and better able to create well-designed applications. CodeIgniter, however, did not keep up
with the technological advances in the language.
While I don’t know the specific reasons, I imagine that EllisLabs, the company that pMachine
morphed into and continued building ExpressionEngine until recently, didn’t think it was feasible
to rewrite the core of their primary business every time a new feature was added to PHP. Since the
framework grew up in parallel to ExpressionEngine, and was largely kept in sync with the needs
of ExpressionEngine developers, new features were typically few and far between. In time, the gap
between the common practices used in the wider PHP community, and the programming styles used
within CodeIgniter widened. New frameworks came along that did cater to the current practices, and
CodeIgniter started losing popularity.
The situation was not helped when the framework became seemingly abandoned by EllisLab as
they went through their own growing pains and didn’t have the resources they could provide to
Introduction 2
keep up with the community. A while later, they announced they were looking for a new home for
the framework, where it would have the resources it needed to grow again. In July 2014, nearly a
year later, it found its new home at the British Columbia Institute of Technology, one of Canada’s
top technology schools.
James Parry, one of the professors at BCIT, championed the project and led its revitalization. He
brought others on board to help guide the project.
Eventually, they decided that the PHP world had moved on so far beyond the traditions and practices
that past versions of CodeIgniter had found success in. A rewrite of the entire framework was needed.
One that would keep the spirit and feel of CodeIgniter 3 as much as possible, while bringing in
current PHP technologies and best practices so that it could fit in with today’s needs. The goal was a
modern, more secure, more flexible version of the PHP framework that many developers learned to
program with. It would not try to be everything to everyone. It never has, but it was believed there
was still a need for a framework that was lighter, more explicit, and more flexible than many of the
top frameworks were.
As the rewrite came close to completion, James Parry passed away due to lung cancer.
After 4+ years in development, CodeIgniter 4 was released to the world on his birthday, February
24, 2020.
Primer: Model-View-Controller (MVC)
What is MVC?
Model-View-Controller, or MVC, is a way of organizing your code into separate concerns. It helps
separate code to make it more flexible, more resilient, and more organized. This is the basic pattern
that CodeIgniter, and many other frameworks, use to structure your code.
With the exception of Controllers, none of this is enforced by the framework and you can build
classes in whatever manner best suits your application. Base classes for Models, and standard ways
of working with views, do provide a lot of built-in power and convenience at your disposal, though,
so you are encouraged to follow the pattern. That is, after all, why you chose to use a framework
instead of raw PHP, right?
For many developers just starting out with namespaces, they can seem a bit of a mysterious thing.
Don’t let that scare you off, though. Whether you are brand new to PHP or have been using it for
years and have never needed to use them, you’ll pick it up quick.
Given a class name of NewsModel, the fully-qualified class name would be App\Models\NewsModel.
As far as PHP is concerned that’s all it needs to know about it. It now knows how to dif-
ferentiate between this class and one with a similar name, but a different namespace, like
Acme\Models\NewsModel. Combined with an autoloader, though, it now also knows exactly where
on the disk to find that class. Let’s look at that from a default CodeIgniter application structure, and
see how that plays out.
A default CodeIgniter application is structured like this:
²https://getcomposer.org
Primer: Namespaces 6
1 app/
2 Config/
3 Controllers/
4 Database/
5 Filters/
6 Helpers/
7 Language/
8 Libraries/
9 Models/
10 ThirdParty/
11 Views/
12 system/
With the exception of the Config directory, everything under the application folder lives within the
App namespace. So files in the Controllers directory would have a namespace of App\Controllers.
Anything within the Models directory is namespaced under App\Models. For technical reasons we
won’t go into right here, the Config folder has its own namespace so any files in that directory are
simply namespaced Config.
If you want to get an instance of a class App\Models\NewsModel, you can do that very easily:
One thing you might be thinking is that this gets pretty wordy, requiring lots of typing. And you’re
absolutely correct. To minimize that you can specify at the top of the file what classes you are going
to use. Once you’ve done that you can simply give the main class name throughout the rest of your
code.
If you have a couple of classes with the same base name that you need to use within the same file
you can assign an alias that can be used in that file only. This is done by typing as at the end of the
initial use statement followed by the name you want to refer to it as:
Primer: Namespaces 7
1 <?php
2 namespace App\Controllers;
3
4 use CodeIgniter\Controller;
5 use App\News\Manager as NewsManager;
6 use App\Blog\Manager as BlogManager;
7
8 class NewsControllerextends Controller
9 {
10 public function index()
11 {
12 $news = new NewsManager();
13 $blog = new BlogManager();
14 }
15 }
Case-sensitivity
One thing that you need to be aware of is that namespaces within CodeIgniter 4 are case-sensitive.
Any files or directories that the namespace maps to MUST match the case of the file or directory.
Looking at a NewModel again, it has the namespace of App\Models\NewsModel. This means the file
must be located at application\Models\NewsModel.php. Neither application\Models\newsModel.php
or application\models\newsmodel.php would be located by the framework. Conversely, a file at
application\Models\NewsModel.php could not use the namespace App\models since the case does
not match.
And that’s all you really need to know to start using namespaces in your CodeIgniter 4 application.
To recap:
• Namespaces help keep class names from colliding with others of the same name.
• Namespaces help CodeIgniter 4 find the classes on the disk so you don’t have to explicitly
require/include the files.
• Namespaces are case-sensitive and must match the case of the files and directories it maps to.
Going Deeper
Now let’s dig a little deeper into namespaces and how we can use them in our applications. We’ll
look at adding our own namespaces to CodeIgniter, integrating Composer - a third-party autoloader,
and more.
Primer: Namespaces 8
Additional Directories
The easiest thing to do is to add a new directory to the application folder. You might do this to
keep all of your Entity classes together, similar to how Models are stored together by default. This
works out of the box. Simply create the directory, then any new files within that directory should
be namespaced to include the new directory name.
After creating a new directory at app/Entities to store our Entity classes, we would use the
following namespace for any Entities that you created: App\Entities. Whenever we need to get
an instance of that class we would use:
Domain Mapping
Imagine a project that uses multiple classes for each domain of the application. If we are creating
a forum application, we might have a PostModel, a Post entity class, a PostManager (that acts as a
Repository class for Posts). We could completely change the structure of our application directory
to reflect this, organizing our business logic classes by the application domain, instead of the class
type. The application directory now might look more like:
1 app/
2 Config/
3 Controllers/
4 Database/
5 Domains/
6 Forums/
7 Forum.php
8 ForumManager.php
9 ForumModel.php
10 Posts/
11 Post.php
12 PostManager.php
13 PostModel.php
14 Threads/
15 Users/
Primer: Namespaces 9
16 Filters/
17 Helpers/
18 Language/
19 ThirdParty/
20 Views/
This keeps all classes related to the business logic or data together. In this example, the controllers
and views are kept in their default locations, but they could have been moved also. However, that
makes things a little more complicated since we are dealing with modules at that point, and will be
discussed in a later chapter.
Of course, all of the classes need to be correctly namespaced. So Forum.php would use the namespace
App\Domains\Forumswhile Post.php would use the namespace App\Domains\Posts. Notice that the
namespaces match the directory names and structure exactly.
New Namespaces
To organize any code outside of the app directory, the framework has to be told where to find the
new files so it can map the namespace correctly. This could be used to keep your business logic code
outside of the application directory, or to make a central place to store modules, for example. This
is simple and requires adding a single line to app/Config/Autoload.php, in the $psr4 block of the
constructor:
1 $psr4 = [
2 'Config' => APPPATH.'Config',
3 APP_NAMESPACE => APPPATH,
4 'App' => APPPATH,
5 'Tests\Support' => TESTPATH.'_support',
6 'Modules' => ROOTPATH.'modules', <- The new namespace
7 ];
In this example, a new namespace, Modules, has been defined. It can be found in a new directory
modules that sits on the same level as the application and system directories. The constant ROOTPATH
used here specifies the main directory that the project sits in. Other constants exist that can be used,
also, though ROOTPATH and APPPATH will be the two most likely to be used:
• ROOTPATH is the directory that holds the app and system directories
• APPPATH is the app directory
• TESTPATH is the tests directory
• BASEPATH is the system directory
• WRITEPATH is the writable directory
Primer: Namespaces 10
CodeIgniter can use 2 different autoloaders. The first is the one built into the framework,
which is what we will discuss here. The second is Composer. CodeIgniter has its own for
a couple of reasons. The first is to remove the dependence on Composer, something the
community was very adamant about when work on version 4 was getting started. The
second reason is that it allows us to do some tricks throughout the system that help provide
additional flexibility and allow non-class files to be “namespaced”, like views, helpers, etc.
Primer: Dependency Injection
Even more so than namespaces, Dependency Injection seems to have gotten a massive cloud of
confusion based around it. In this chapter I hope to clear up the confusion, and show you what it is,
how it works, and how I recommend using it within your own applications.
What is it?
Dependency Injection (a.k.a. DI), at its core, simply means that any dependencies for your class
should be passed into it, instead of creating the dependency within the class itself.
First an example of a non-DI way to do it:
1 class UserManager {
2 public function list() {
3 $model = new UserModel();
4
5 return $model->findAll();
6 }
7 }
In this example, we grab a new instance of the UserModel class within our UserManager class. This
is fine for simple code, but breaks down when we start getting more advanced.
This is especially true when you start testing your code. If you were trying to test all of your
UserManager methods, you would end up needing to setup the testing database, create the test tables,
add in any sample data, all before running each test. While that type of testing has things going for
it, it can be slow. Instead, you might want to create a mock of the class where you can define how
the class reacts and never hit the database, making your tests run much faster. However, if you are
creating a new instance of the class within your UserManager you cannot easily do that.
How to do it
To simplify your life while testing, you should instead inject the dependencies for UserManager, in
this case the UserModel class, into the UserManager. This allows you to create a mocked-up version
of the UserModel and pass that mock into the UserManager within all of your tests. This is most often
done within the constructor, like:
Primer: Dependency Injection 12
1 class UserManager {
2 protected $userModel;
3
4 public function __construct($userModel) {
5 $this->userModel = $userModel;
6 }
7
8 public function list() {
9 return $this->userModel->findAll();
10 }
11 }
How to use it
Now that you understand that basics, let’s look at how to use this in your day to day.
Let’s say you are building a classifieds ad app. Odds are you would need to collect some functionality
together in a single class related to the ads. Functions that make it easier or more semantic to work
with the AdsModel.
We now have a skeleton class that we can expand for anything that needs to do more than a model
does by itself. When we need this, we pass the model in with it. Within our constructor it might
look like this:
Primer: Dependency Injection 13
1 use App\Libraries\AdsManager;
2 use App\Models\AdsModel;
3
4 class AdsController
5 {
6 public function localAds(string $region) {
7 $adsMan = new AdsManager(model(AdsModel::class));
8
9 return view('ads/local', [
10 'ads' => $adsMan->localAds($region),
11 ]);
12 }
13 }
Here we create a new instance of the AdsManager. We create an instance of the AdsModel with
the model helper and pass it in through the constructor. While this example is not too bad to type
every time, it can get cumbersome as the number of dependencies grow. There’s a couple of ways
to handle it, but CodeIgniter provides a concept called Services that you’ll read all about in the next
chapter that will make this simple to handle in your application.
The model() helper will return an instance of the model class. It scans across all namespaces
in Config\Autoload.php. By default, it will always return the same instance so you can use
it anywhere in your app and always get the same instance of the class. Very handy stuff.
injecting dependencies, returning shared or single instances. And it does it all in plain code that
anyone can easily read and understand. If your app changes frameworks down the road, or you need
to customize the core of the framework, you only change a couple of lines of code. It’s simply. But
it’s a slight amount of work more than easy. But not much. The prize, though, is that it’s transparent
and explicit.
Getting back to Controllers, though. If you wade through discussion/arguments online about DI,
Ioc, Service pattern, etc. there’s a lot of rhetoric on all sides on whether it’s right or wrong to create
classes within any of your classes, which includes controller. Here’s the way I look at it. You may
have a different viewpoint and that’s great - it’s what brings on change and keeps the world moving.
Looking back at Chapter 2, we saw that “controllers are responsible for taking user input, ensuring
it gets processed correctly, and returning the output to the user.” In order to do that they must
orchestrate all the pieces. They load the proper models, libraries, etc. They collect user input, pass
it into those models/libraries, and collect the results. Then they organize that result into the format
the original client wants to see it in, whether that’s a view, a JSON object, a chunk of XML like an
RSS feed, or anything else.
While gathering the tools they need to perform these actions, they can either do it in a way that
is explicit and makes it simple for the developer to understand and trace what’s going on, or they
can do it in a way that is nicer to read, but much more complex and obtuse. I prefer the code to be
simpler to read, and easier for the next developer to understand what is going on. I prefer that the
Controller creates the classes in plain site instead of having it injected through the constructor or a
method’s argument list.
Either option is fine, and works in the long run. I’m not here to say one was is better than others.
Just that this is why CodeIgniter 4 is the way it is.
Primer: Services
Services are a core part of CodeIgniter 4, serving as convenient shortcuts to setting up a class and
its dependencies as well as providing a simple method for overriding most of the core framework
classes. They were mentioned in the previous chapter, but we will dive deeper into them in this
chapter.
1 $cache = service('cache');
2 $value = $cache->get('key');
You can chain the call together with the functionality you need if you only need to use the class
once:
1 service('cache')->set('key', $value);
Many of the services take optional parameters. For example, the cache service can take a configura-
tion class instance as its first argument, allowing you alter how the class works. Remember, though,
that by default services return a shared instance.
Primer: Services 16
1 $cache1 = service('cache');
2
3 $config = new Config\Cache();
4 $cache2 = service('cache', $config);
In this example, you might expect that $cache1 and $cache2 are different, but they are not. when
$cache1 is created its instance is saved. Whenever another call to service()‘ is called,
that original instance is returned. It does not cache multiple versions based on their arguments. It is
possible to get a new instance using the new parameters, though:
1 $cache1 = service('cache');
2
3 $config = new Config\Cache();
4 $cache2 = service('cache', $config, true);
5 // or
6 $cache2 = single_service('cache', $config);
The last argument in service() determines whether it is a shared instance or not. If true, it returns a
new instance. If false, it returns a shared one. You can also use the helper method single_service()
which functions just like service() with the exception that it always returns a new instance.
This is cleaner and more explicit, allowing any future developers (or yourself after having worked
on another project for 6 months) to easily follow what is happening and where the pieces come
from.
In general, there are two reasons to use your own services:
1. You have a class that needs some fairly complex setup that would be too much pain to recreate
every time.
2. An instance needs to be used in multiple places where it’s not easy to pass the dependences in
directly.
That is not to say those are the only reasons you might want to use them, but they are the starting
place to ask yourself when considering using a service. If either of those cases matches then go for
it. If they don’t exactly match up, you might try another way first.
Primer: Services 17
Hopefully, most of that is pretty clear by now. If $getShared then it runs it through the base class’
getSharedInstance() method which checks if we’ve already cached an instance of this class. If we
have it returns the cached instance. If a cached instance does not exist, then it calls this method
again, with $getShared set to false. That’s when the instance creation code you write does its thing
and returns a new instance. The one thing to watch out for is to ensure you pass in the name of the
method as the first argument to getSharedInstance(). That’s how it knows which method to call.
If your service needs the ability to have arguments passed in it when created, then you add those
arguments first in the method. The $getShared argument must always be the last argument or things
will not work correctly.
Finally, make sure to pass in each of the new arguments in $getSharedInstance() call, as shown
above.
Primer: Services 18
Once done, your class will always be used in place of the system class. In many cases the system
will expect the class returned to implement a specific interface so be sure to check out the class that
you’re replacing to see if it implements an interface that you’ll need to follow.
Project 1 - Brochure Site Take 1
We are going to start simple, and get the basics under our belt with this first project. In future
chapters we’ll expand on the concepts presented in the earlier ones. The projects themselves will
change in most chapters. In the first two, though, we will look at two different ways to build the
same simple, one page brochure site.
You can find all sample projects described in this book over at GitHub⁴ if you want to view
the full codebase.
Routes
Routes define what controller is used when a user visits a specific URL. They are configured within
the file, app/Config/Routes.php. For each URL in your site, you define a single line of code.
1 $routes->get('/', 'Home::index');
We will get into more details on that in the next chapter, as there are two ways that you can do
routing. The first is the same that previous versions of CodeIgniter have used, where it automatically
routes a URL to a controller and method, based upon matching the names. This can be handy, but I
recommend staying away from it. But having to write out each route is too tedious, you say? It only
takes a few seconds to create a route, and if your site needs hundreds, you can likely restructure
something to work with only a few dozen routes, which is manageable.
For our examples, we’re going to turn the auto-routing feature off, and do it manually. So edit
app/Config/Routes.php and change setAutoRoute to false:
1 $routes->setAutoRoute(false);
1 $routes->get('/', function() {
2 echo view('home');
3 });
With the exception of the actual content to display, this is all that is needed to display our site. It
states that if someone visits /, then it will run the given function, which simply displays our content.
The view() helper method is one of a number of helper functions⁵ that are always available. They
make it simple to access common functionality without loading up instances of the required classes
and setting them up.
The first argument it takes is the name of the file to grab and render out. By default, it expects to
find the files in the app/Views folder. So, this command would look for the file app/Views/home.php.
The view file itself can be any combination of PHP and HTML needed to display your content. At
it’s simplest, the view might look something like:
1 <!doctype html>
2 <html>
3 <head>
4 <title>Acme, Inc</title>
5 </head>
6 <body>
7 <h1>Welcome to Acme, Inc</h1>
8 <p>Purveyor of fine rockets, magnets, and tools.</p>
9 </body>
10 </html>
Even on simple brochure sites, though, we might need to display dynamic data within the view itself.
For this example, we’ll pass some simple, faked data into the view to demonstrate how the data gets
there and can be used. We’ll cover more realistic details a little later.
1 $routes->get('/', function() {
2 echo view('home', [
3 'siteName' => 'Acme, Inc',
4 'categories' => ['rockets', 'magnets', 'tools']
5 ]);
6 });
As the second argument to the view() command, we can pass in an array of data that should be
available within the view for us to work with. The keys are the names the data will be accessed as
within the view. The updated view would look like this:
⁵https://codeigniter.com/user_guide/general/common_functions.html
Project 1 - Brochure Site Take 1 21
1 <!doctype html>
2 <html>
3 <head>
4 <title><?= $siteName ?></title>
5 </head>
6 <body>
7 <h1>Welcome to <?= $siteName ?></h1>
8 <p>Purveyor of fine
9 <?php foreach($categories as $cat) : ?>
10 <?= $cat .', ' ?>
11 <?php endforeach ?>, and more.</p>
12 </body>
13 </html>
Here we use the php shorthand syntax (<?=) to echo the content out using the key of the array we
passed in before. For the categories we simply loop over the content array as we would loop over
any other array.
This has provided an overview of how to take a visitor and display a page. With this type of a
setup you could do any brochure site that doesn’t need any special management abilities. Hopefully,
you’re site would look better than this one, though. :)