What the CRUD? Create, Read, Update and Destroy with Magento2

If you’re used to PHP MVC frameworks like Laravel or Symphony you may have noticed already that performing these relatively simple actions in Magento2 is more complicated than it first appears...

Just a quick note, at the time of writing this article the version of Magento 2 being used is 2.2.2

So you’ve decided to deep dive into the complex and rich world of Magento 2 and want to create an entity and perform basic CRUD actions on it. If you’re used to PHP MVC frameworks like Laravel or Symphony you may have noticed already that performing these relatively simple actions in Magento 2 is more complicated than it first appears. In this post I’ll go though the basic setup of a module in Magento 2 and create a custom database model so we can see how its done.

Assumed knowledge:

  • OO-PHP
  • How to create and register a basic module in Magento 2
  • How to create a basic route, and a new page in Magento 2

What we’ll cover in this tutorial step by step:

  • Create and register a Magento 2 module
  • Create an install script to install a table for the model we want to CRUD
  • Create the Model class (more specifically classes) for Magento 2 to interact with
  • Create a basic Create controller to handle a post from an imaginary front end page

I just want the code!

No worries, here’s a link to the github repo for what I’m about to go through:

Step 1 – Setup our module:

Firstly lets create a basic module in Magento 2 and register it. For the demo we’ll be calling our module CRUD, under a namespace C3. Once done you should have a folder structure that looks like the following:

Basic Module structure

Don’t forget to populate your module.xml file with the relevant config script before proceeding. It should look something like this:

<!--?xml version="1.0"?-->

Step 2 – Install Script to create Database:

Now that we have our module setup let’s start by creating a new entity in our database so we have something to work with. Create a new folder called ‘Setup’ under your module namespace, and create a new file called InstallSchema.php inside it.

We’re going to call our model Notes. So open ‘InstallSchema.php’ and we’ll start building the install script that will setup this new table for us.

<!--?php namespace C3\CRUD\Setup; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; class InstallSchema implements InstallSchemaInterface { /** * Installs DB schema for a module * * @param SchemaSetupInterface $setup * @param ModuleContextInterface $context * @return void * @throws \Zend_Db_Exception */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $setup-&gt;startSetup();
        $dbConnection            = $setup-&gt;getConnection();
        $validatedNotesTableName = $setup-&gt;getTable('notes');
        $notesTable = $dbConnection-&gt;newTable($validatedNotesTableName)
                                    // ID
                                   -&gt;addColumn('note_id', Table::TYPE_INTEGER, null, [
                                       'identity' =&gt; true,
                                       'unsigned' =&gt; true,
                                       'nullable' =&gt; false,
                                       'primary'  =&gt; true
                                   ], 'Notes ID')
                                    // Notes title
                                   -&gt;addColumn('title', Table::TYPE_TEXT, 250, ['nullable' =&gt; false], 'Note Title')
                                    // Notes Content
                                   -&gt;addColumn('content', Table::TYPE_TEXT, 1000, ['nullable' =&gt; false], 'Note Content')
                                    // Created at
                                   -&gt;addColumn('created_at', Table::TYPE_TIMESTAMP, null, [
                                       'default'  =&gt; Table::TIMESTAMP_INIT,
                                       'nullable' =&gt; false
                                   ], 'Created At')
                                    // Updated At
                                   -&gt;addColumn('updated_at', Table::TYPE_TIMESTAMP, null, [
                                       'default'  =&gt; Table::TIMESTAMP_INIT_UPDATE,
                                       'nullable' =&gt; false
                                   ], 'Updated At');
&lt;p&gt;&lt;span style="font-weight: 400;"&gt;As you can see here, we're creating a table called' notes' where we can store a notes title, and content. O&lt;/span&gt;&lt;span style="font-weight: 400;"&gt;nce we're happy with how our table will look,  run '&lt;em&gt;php/bin magento setup:upgrade&lt;/em&gt;' in terminal under your project root to register your module and install our table. &lt;/span&gt;&lt;span style="font-weight: 400;"&gt;Magento2 will run any scripts contained within the Setup directory in your module and build our table for us.&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;&lt;span style="font-weight: 400;"&gt;Step 3 - Build our Model (All of the Abstraction):&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style="font-weight: 400;"&gt;Now it's time to set up our database model. A model entity in Magento is broken up into three separate classes.&lt;/span&gt;&lt;/p&gt;
&lt;li&gt;&lt;span style="font-weight: 400;"&gt;Firstly, a Resource Model which interfaces directly with the database, allowing for CRUD operations.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Secondly, a basic business logic Model (which extends Magento\Framework\Model\AbstractModel) it is in this model that we place our main business logic.&lt;/li&gt;
&lt;li&gt;&lt;span style="font-weight: 400;"&gt;Finally, a Collection Model, which is responsible for handling a collection of Resource models. Whilst this model isn’t strictly necessary, as we can retrieve database results or our Notes model via the Resource Model, we do however get to treat each result as an object with the use of the Collection model instead.&lt;/span&gt;&lt;/li&gt;
&lt;p&gt;&lt;span style="font-weight: 400;"&gt;Let's start by creating a' Model' directory in our module, then a' ResourceModel' subdirectory, and declare our first model class, the resource model. As you'll see this model is where we specify which database table to link to, and which column to use for its id.&lt;/span&gt;&lt;/p&gt;
&lt;pre lang='php'&gt;
&lt;?php namespace C3\CRUD\Model\ResourceModel; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; class Note extends AbstractDb { /** * Link our model to the notes table, and which column is the id reference. */ protected function _construct() { $this-&gt;_init('notes', 'note_id');
&lt;p&gt;&lt;span style="font-weight: 400;"&gt;Now we move on to our Collection Model, create yet another subdirectory under the' ResourceModel' directory and call it' Notes.&lt;/span&gt;&lt;br ?-->
<span style="font-weight: 400;">In here we're going to place our Collection.php class. You may have noticed we did not call this Notes, or NotesCollection. </span><span style="font-weight: 400;">T</span><span style="font-weight: 400;">his is because Magento expects to find the relevant collection class from the namespace for the resource model it links to, and expects to find a folder under the resource model name, and the collection class within. </span><span style="font-weight: 400;"> As you'll see this model is where we specify which database table to link to, and which column to use for its id.</span>

(Its not immediately obvious by the screenshot, but the bottom Note.php file is located under the Model directory, and the upper is located under the ResourceModel directory).

Why expand our database model into three separate classes?

Magento2 has opted to extract database models into three separate classes to allow for the greatest flexibility from within your application. It allows for you to apply only database specific logic to say the resource model, and business specific logic to your abstract model, and collection behaviour to (you guessed it) your collection model.

Step 4 – Set Up A Route: Telling Magento How to Find Your Controllers

Time to create a controller to handle CRUD operations on our module. We’ll start with setting up a basic route in our module. Create a ‘frontend’ directory under your modules ‘etc’ directory. In here create your ‘routes.xml’ file.

<!--?xml version="1.0" encoding="UTF-8"?-->

Here we’ve basically set our route path to start with

Step 5 – CRUD via Controllers:

Now we’ll create our controller directory in the root directory of our module with a subdirectory of ‘Notes’: C3\CRUD\Controller\Note. Now we need to create controller classes for each CRUD action we want. For the purposes of length, I will show a simple ‘Create’ class only, however once you see how its done it will be pretty simple to build your own read, update and destroy classes.

One note about Magento2 controllers you must create a whole new controller class per action you want to perform. So for instance, if we were to go ahead and build out our Read, Update and Destroy classes too our Controller directory should look like something like this.

Ok, so let’s make the Create.php class:

&lt;?php namespace C3\CRUD\Controller\Note; use C3\CRUD\Model\NoteFactory; // Factory classes are dynamically generated classes by Magento use C3\CRUD\Model\ResourceModel\Note; use Magento\Framework\App\Action\Action; use Magento\Framework\App\Action\Context; use Magento\Framework\App\Request\Http; use Magento\Framework\Exception\AlreadyExistsException; class Create extends Action { protected $helper; protected $request; protected $note; protected $noteResource; protected $noteFactory; public function __construct( Context $context, Note $noteResource, Http $request, NoteFactory $noteFactory ) { $this-&gt;request      = $request;
        $this-&gt;noteFactory  = $noteFactory;
        $this-&gt;noteResource = $noteResource;
     * Execute action for our controller.
    public function execute()
        $postData = $this-&gt;request-&gt;getPost();
        if (!empty($postData)) {
            //Probably best do some form of validation here but for tutorial purposes, we wont for now
            $title = $postData['title'];
            $issue = $postData['content'];
            $newNote = $this-&gt;note
                  'title' =&gt; $title,
                  'issue' =&gt; $issue,
            //Try to save the new note
            try {
                $this-&gt;messageManager-&gt;addSuccessMessage("New Ticket: $title Created");
            } catch (\Exception $e) {
                //Add a error message if we cant save the new note from some reason
                $this-&gt;messageManager-&gt;addErrorMessage('Unable to save this ticket, please call your service provider');
        } else {
            // Add a helpful error message
            $this-&gt;messageManager-&gt;addErrorMessage("No data was posted");
        // Return to where we came from, here we're assuming the read notes page.
        return $this-&gt;_redirect("*/*/read");

As you’ll notice we’ve referenced dependency in called   ‘C3\CRUD\Model\NoteFactory’. This is a dynamically generated class from Magento for all Abstract Model classes. It allows for the instantiation of new models without the need for you to declare their dependencies as well. Don’t worry if your IDE is upset with you initially when declaring the factory dependency; at the time Magento fires the request for that class, it will automatically generate it for you.

Note: Whilst the use of a Factory class is not strictly required when dealing with Model instantiation , we recommend using it as it provides more control over object creation, and in turn helps avoid object reuse.

Ok But, Where’s the Frontend?

I leave that to you dear reader, to figure out how to post a form to this controller. Here is a helpful link to Alan Storms page in creating a basic module in your Magento2 application to get you started.

The reason why I haven’t included this is this particular tutorial is due to frontend development in Magento 2 is deserving of its own blog post. Also stated at the beginning of this post, we expect you to know how to create a new page before attempting CRUD.

Step 6 – Profit

There you have it, we created our database model Magento 2 style, made a database script to install our model table in your stores DB, and finally created a quick route, and controller action to demonstrate how to connect all the pieces. A quick recap of the steps are as follows:

  • Create and register your Module
  • Create an install script to make your db table/s
  • Create your three model classes in your modules ‘Model’ directory
  • Use model factories to instantiate and work with your models

I hope this short tutorial was helpful in wrapping your head around how Magento 2 expects your database models to be instantiated and why. I’ll be posting more of these articles in the future, and any constructive feedback is welcome.

A quick note on why we provide these tutorial-style blog posts – they help us and others get on with the interesting part of development. The point is to help grasp what layouts are required quickly, so you can get on with actual coding, rather than piecing together how things are to work in the first place.

About the author

Team C3