Install specific version of Laravel framework with Composer

In some cases, you many need to install a specific version of the Laravel framework.  The typical scenario is that your shared hosting provider uses a version of PHP that is not supported by the latest version of Laravel framework.  For example, many shared hosting providers still use PHP 5.3, even though Laravel 4.3 (current as of this writing) requires PHP 5.4 and PHP 5.3 has reached “end-of-life” status.  The last version of Laravel to support PHP 5.3 (specifically version 5.3.7 or greater, due to requirement for bcrypt algorithm to support password hashing) is version 4.1.

To install version 4.1 of Laravel with Composer, run:

composer create-project laravel/laravel project_name --prefer-dist 4.1.*

By specifying 4.1.* instead of just 4.1, this ensures that we get the latest “minor” version of 4.1, such as 4.1.27, which often includes patches, such as security updates. See Composer documentation for other syntax options for package version numbers.

Create model classes for Laravel e-commerce application

In our last post, we created Laravel migrations to generate our database tables. Now that we have these tables, we need to create corresponding model classes for each of the tables.

Before we get into creating the model classes, let’s look at what models are used for. Models are the ‘M’ in MVC, the model-view-controller design pattern. The fundamental role of the model is to manage how your application interacts with the database. In general, you will have one model for each table in the database. In addition to database interaction, the model will hold the business rules, including relationships between the various entities represented by the various models.

In our e-commerce application, we will be creating four models corresponding to each of the database tables: account (customer), order, order_item, and product. All of our model files will be in the app/model directory. Typically, the model file and class names follow the name of the corresponding table, except they usually don’t use underscores. Furthermore, while not strictly required, I suggest that you specify the corresponding table name explicitly in the $table attribute (variable) of the class.

To begin, we’ll use the Generators package again to help us create our model classes. (We used Generators to generate the framework for our database migrations classes previously.) As you recall, the account table looks like:

mysql> describe account;                                                                  
+-----------------------------+------------------+------+-----+---------+----------------+
| Field                       | Type             | Null | Key | Default | Extra          |
+-----------------------------+------------------+------+-----+---------+----------------+
| account_id                  | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| email_addr                  | varchar(50)      | NO   |     | NULL    |                |
| password                    | varchar(30)      | NO   |     | NULL    |                |
| admin_ind                   | tinyint(1)       | NO   |     | 0       |                |
| address1                    | varchar(50)      | YES  |     | NULL    |                |
| address2                    | varchar(50)      | YES  |     | NULL    |                |
| city                        | varchar(20)      | YES  |     | NULL    |                |
| state                       | varchar(2)       | YES  |     | NULL    |                |
| postal_code                 | varchar(20)      | YES  |     | NULL    |                |
| created                     | datetime         | NO   |     | NULL    |                |
| created_by_account_id       | int(11)          | NO   |     | NULL    |                |
| last_modified               | datetime         | NO   |     | NULL    |                |
| last_modified_by_account_id | int(11)          | NO   |     | NULL    |                |
+-----------------------------+------------------+------+-----+---------+----------------+
13 rows in set (0.01 sec)                                                                 

To generate the model class for the account table, simply run:

php artisan generator:model Account

This will create the “shell” Account model class file in app/models/Account.php. Open this file in your editor, such as Aptana and update the content. Here is the complete Account class that we’ll use:

<?php
	use Illuminate\Auth\UserInterface;
	use Illuminate\Auth\Reminders\RemindableInterface;
	
	class Account extends Eloquent
		implements UserInterface, RemindableInterface
	{
		/**
		 * The database table used by the model.
		 *
		 * @var string
		 */
		protected $table = "account";
		
		/**
		 * The primary key (PK) column in database table.
		 *
		 * @var string
		 */
		protected $primaryKey = "account_id";
		
		/**
		 * The attributes excluded from the model's JSON form.
		 *
		 * @var array
		 */
		protected $hidden = array("password", "admin_ind",
			"created", "created_by_account_id",
			"last_modified", "last_modified_by_account_id");
		
		/**
		 * The attributes excluded from mass assignment.
		 *
		 * @var array
		 */
		protected $guarded = array("account_id", "admin_ind");
		
		/**
		 * The attributes *explicitly* included for mass assignment.
		 *
		 * @var array
		 */
		protected $fillable = array("email_addr", "address1", "address2",
			"city", "state", "postal_code");
		
		protected $softDelete = true;
		
		/**
		 * Each account (customer) can have many order items.
		 *
		 * @var array
		 */
		public function orders() {
			return $this->hasMany("Order", "account_id");
		}
		
		/**
		 * Get the unique identifier for the account.
		 * Required by Illuminate\Auth\UserInterface.
		 *
		 * @return mixed
		 */
		public function getAuthIdentifier()
		{
		  return $this->getKey();
		}
 
		/**
		 * Get the password for the account.
		 * Required by Illuminate\Auth\UserInterface.
		 *
		 * @return string
		 */
		public function getAuthPassword()
		{
		  return $this->password;
		}
 
		/**
		 * Get the e-mail address where password reminders are sent.
		 * Required by Illuminate\Auth\Reminders\RemindableInterface.
		 *
		 * @return string
		 */
		public function getReminderEmail()
		{
		  return $this->email;
		}
		
		/**
		 * Some "user-friendly" aliases for the interface methods.  🙂
		 */
		public function getAccountId()
		{
			return $this->getAuthIdentifier();
		}
		
		public function getEmailAddr()
		{
			return $this->getReminderEmail();
		}
		
		public function getPassword()
		{
			return $this->getAuthPassword();
		}
	
	}
?>

You can refer to the Eloquent documentation on the Laravel web site for more details, but let’s explain a few of the concepts here.

  • $table – This specifies the name of the database table that this model corresponds to. If the class name matches the table name, it is not strictly required to specify the table, but I find it best to be explicit about this.
  • $primaryKey – The default convention for Laravel is for each table to have a column named id which is the primary key (PK). I prefer to include the table name in the PK column, so we must specify this column explicitly in our class defintion.
  • $hidden – This is a list (array) of the columns in the table that are note returned from the JSON API. In our example, we would not want to expose the password!
  • $guarded – Laravel supports the concept of mass (bulk) assignment of attributes. However, we may want to prevent mass assignment of certain attributes for security reasons. The $guarded array is a list of such columns.
  • $fillable – These are the “opposite” of the $guarded columns in that these are explicitly allowed to be mass assigned.
  • Next, we have the orders() method:
    		public function orders() {
    			return $this->hasMany("Order", "account_id");
    		}	
    	

    This is the paradigm for how Laravel defines relationships between models and tables. In this case, we are indicating that a given account (user) can have multiple (hasMany) orders. You will notice that the reference is actually to the Order model (and not strictly to the order table). Furthermore, we are explicitly noting that the foreign key column to account on order is the account_id column. Also, note that since we are using the hasMany relationship, we specified the method name using the plural form: orders. For an excellent discussion about the various relationships provided by Eloquent, see Visualizing Laravel Relationships.

    In addition, when we get to the Order model, you will see that we have a reciprocal relationship defined back to the account using the (singular) belongsTo method.

  • Finally, we have the three get...() methods. The methods and especially their names may seem a bit awkward. However, you’ll notice that on the class declaration, we have included implements UserInterface, RemindableInterface (and referenced those interface libraries via use directives at the top of the class). An interface is kind of like a template for a class. Essentially, it tells Laravel (or really PHP itself!) that the class that implements the interface must include certain attributes (variables) and/or methods (functions). See the PHP documentation on interfaces for more information. The most common use for interfaces in Laravel is to handle inversion of control (IoC) design pattern, which is useful for application testing.

    You will also notice that I created three additional “helper” methods with more “user-friendly” names, which simply call the methods required by the interfaces. This is just to allow us to use names that are more meaningful when calling the methods ourselves.

This takes care of the Account model. Here are the complete definitions for our other three model classes: Order, OrderItem, and Product. Note that the “headers” for these look a bit different, since none of them implement any interfaces.

Order Model

<?php
	
	class Order extends Eloquent
	{
		/**
		 * The database table used by the model.
		 *
		 * @var string
		 */
		protected $table = "order";
		
		/**
		 * The primary key (PK) column in database table.
		 *
		 * @var string
		 */
		protected $primaryKey = "order_id";
		
		/**
		 * The attributes excluded from the model's JSON form.
		 *
		 * @var array
		 */
		protected $hidden = array("created", "created_by_account_id",
			"last_modified", "last_modified_by_account_id");
		
		/**
		 * The attributes excluded from mass assignment.
		 *
		 * @var array
		 */
		protected $guarded = array("account_id", "order_id");
		
		/**
		 * The attributes *explicitly* included for mass assignment.
		 *
		 * @var array
		 */
		protected $fillable = array("order_status", "order_subtotal", "order_net",
			"order_gross", "shipping_fee", "shipping_method", "order_notes");
		
		protected $softDelete = true;
		
		
		/**
		 * Each order belongs to EXACTLY one customer (account).
		 *
		 * @var array
		 */
		public function account() {
			return $this->belongsTo("Account", "account_id");
		}		

		/**
		 * Each order contains one or more order items.
		 *
		 * @var array
		 */		
		public function orderItems() {
			return $this->hasMany("OrderItem", "order_id");
		}
		
		public function getAccountId()
		{
		  return $this->account_id;
		}
		
		public getOrderId()
		{
		  return $this->getKey();
		}
	
	}
?>

OrderItem Model

<?php
	
	class OrderItem extends Eloquent
	{
		/**
		 * The database table used by the model.
		 *
		 * @var string
		 */
		protected $table = "order_item";
		
		/**
		 * The primary key (PK) column in database table.
		 *
		 * @var string
		 */
		protected $primaryKey = "order_item_id";
		
		/**
		 * The attributes excluded from the model's JSON form.
		 *
		 * @var array
		 */
		protected $hidden = array("created", "created_by_account_id",
			"last_modified", "last_modified_by_account_id");
		
		/**
		 * The attributes excluded from mass assignment.
		 *
		 * @var array
		 */
		protected $guarded = array("product_id", "order_id");
		
		/**
		 * The attributes *explicitly* included for mass assignment.
		 *
		 * @var array
		 */
		protected $fillable = array("qty", "extended_price");
		
		protected $softDelete = true;
		
		/**
		 * Each order item belongs to EXACTLY one order.
		 *
		 * @var array
		 */
		public function order() {
			return $this->belongsTo("Order", "order_id");
		}
		
		/**
		 * Each order item references EXACTLY one product.
		 *
		 * @var array
		 */
		public function product() {
			return $this->hasOne("Product", "product_id");
		}
		
		public function getProductId()
		{
			return $this->product_id;
		}
		
		public function getOrderId()
		{
			return $this->order_id;
		
		public getOrderItemId()
		{
			return $this->getKey();
		}
	
	}
?>

Product Model

<?php
	
	class Product extends Eloquent
	{
		/**
		 * The database table used by the model.
		 *
		 * @var string
		 */
		protected $table = "product";
		
		/**
		 * The primary key (PK) column in database table.
		 *
		 * @var string
		 */
		protected $primaryKey = "product_id";
		
		/**
		 * The attributes excluded from the model's JSON form.
		 *
		 * @var array
		 */
		protected $hidden = array("created", "created_by_account_id",
			"last_modified", "last_modified_by_account_id");
		
		/**
		 * The attributes excluded from mass assignment.
		 *
		 * @var array
		 */
		protected $guarded = array("product_id");
		
		/**
		 * The attributes *explicitly* included for mass assignment.
		 *
		 * @var array
		 */
		protected $fillable = array("product_code", "product_name", "product_description");
		
		protected $softDelete = true;
		
		/**
		 * Each product can be used (referenced) by many order items.
		 *
		 * @var array
		 */
		public function orderItems() {
			return $this->belongsToMany("OrderItem", "product_id");
		}
		
		public getProductId()
		{
			return $this->getKey();
		}
	
	}
?>

Next time, we’ll look at adding some test data to the database, which is called “seeding” the database in Laravel. See you then!

References

Laravel 4 Generators package on Github
Setting up your first Laravel 4 Model