How to create a custom module for crud operations in the step by step in frontend of magento2.3.4?

Repositories give service requestors the ability to perform create, read, update, and delete (CRUD) operations on entities or a list of entities. A repository is an example of a service contract, and its implementation is part of the domain layer.

Service Contracts

Service contracts are agreements between two parties, a service provider and a service customer. The contract defines the services that the consumer will get, along with some additional information.

Magento Service Contracts

In magento service contracts are simply interfaces and classes that protect the data integrity and hide the business logic. This allows the customers to use these contracts freely and at the same time, allows the service to evolve without affecting the users.

The service layer has two different interface types. They are Data interfaces and Service interfaces.

Data Interfaces
Data interfaces are objects that preserve data integrity by using the following patterns:

  • They’re read-only, since they only define constants and getters.
    Getter functions can contain no parameters.
  • A getter function can only return a simple object type (string, integer, Boolean), a simple type array, and another data interface.
  • Mixed types can’t be returned by getter functions.
  • Data entity builders are the only way to populate and modify data interfaces.

Service Interfaces
Service interfaces provide a set of public methods that a client can use. There are three service interfaces subtypes:

  • Repository Interfaces
  • Management Interfaces
  • Metadata Interfaces

Here we only focus on the Repository Interfaces. This interface helps us to write methods to communicate with the database.

Repository Interfaces

Repository interfaces ensure that a user can access persistent data entities. For example, persistent data entities within the Customer Module are Consumer, Address, and Group. This gives us three different interfaces:

  • CustomerRepositoryInterface
  • AddressRepositoryInterface
  • GroupRepositoryInterface

The methods that these interfaces have are:

  • Save – If there’s no ID, creates a new record, and updates what’s existing if there is one.
  • Get – Looks for the IDs in the database and returns a certain data entity interface.
  • GetList – Finds all data entities that correspond with the search criteria, then gives access to the matches by returning the search result interface.
  • Delete – Deletes the selected entity
  • DeleteById – Deletes the entity when you only have its key.

Steps to use Repository Model

  1. Before creating repository for our model, we have to define di.xml at the path VendorNameModuleNameetcdi.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="VendorNameModuleNameApiCarsRepositoryInterface" type="VendorNameModuleNameModelCarsRepository" />
<preference for="VendorNameModuleNameApiDataCarsInterface" type="VendorNameModuleNameModelCars" />
</config>
  1. First we have to create a data interface that holds all the columns in our table. Go on and create VendorNameModuleNameApiDataCarsInterface.php file.
<?php
namespace VendorNameModuleNameApiData;

/**
 * Interface CarsInterface
 *
 * @api
 */
interface CarsInterface
{
    /**
     * @return int
     */
    public function getId();

    /**
     * @param int $car_id
     * @return $this
     */
    public function setId($car_id);

    /**
     * @return string
     */
    public function getTitle();

    /**
     * @param string $title
     * @return $this
     */
    public function setTitle($title);

    /**
     * @return string
     */
    public function getDescription();

    /**
     * @param string $description
     * @return $this
     */
    public function setDescription($description);

    /**
     * @param $updateTime
     * @return $this
     */
    public function setUpdateTime($updateTime);
}
  1. Now that we have created the data interface, we have to create a repository interface which contains all the methods to perform CRUD in magento. Go on and create VendorNameModuleNameApiCarsRepositoryInterface.php file.
  <?php
namespace VendorNameModuleNameApi;

use MagentoFrameworkExceptionLocalizedException;
use MagentoFrameworkExceptionNoSuchEntityException;
use VendorNameModuleNameApiDataCarsInterface;
use MagentoFrameworkApiSearchCriteriaInterface;

/**
 * Interface CarsRepositoryInterface
 *
 * @api
 */
interface CarsRepositoryInterface
{
    /**
     * Create or update a Car.
     *
     * @param CarsInterface $car
     * @return CarsInterface
     */
    public function save(CarsInterface $car);

    /**
     * Get a Car by Id
     *
     * @param int $id
     * @return CarsInterface
     * @throws NoSuchEntityException If Car with the specified ID does not exist.
     * @throws LocalizedException
     */
    public function getById($id);

    /**
     * Retrieve Cars which match a specified criteria.
     *
     * @param SearchCriteriaInterface $criteria
     */
    public function getList(SearchCriteriaInterface $criteria);

    /**
     * Delete a Car
     *
     * @param CarsInterface $car
     * @return CarsInterface
     * @throws NoSuchEntityException If Car with the specified ID does not exist.
     * @throws LocalizedException
     */
    public function delete(CarsInterface $car);

    /**
     * Delete a Car by Id
     *
     * @param int $id
     * @return CarsInterface
     * @throws NoSuchEntityException If car with the specified ID does not exist.
     * @throws LocalizedException
     */
    public function deleteById($id);
}

This concludes the implementation of repository intereface in magento.

Then we have to create corresponding model system to accompany the service contracts by Magento.

Models in Magento are an inherent part of the MVC (Model-View-Controller) architecture. Models are used to do data operations, namely Create, Read, Update and Delete, on a database. Magento’s “Model system” is divided into three parts – models, resource models, and collections.

Model:

Models are like a black box which provides a layer of abstraction on top of the resource models. The fetching, extraction, and manipulation of data occur through models. As a rule of thumb, every entity we create (i.e. every table we create in our database) should have its own model class. Every model extends the MagentoFrameworkModelAbstractModelclass, which inherits the MagentoFrameworkDataObjectclass, hence, we can call the setDataand getData functions(refer magic method __call()) on our model, to get or set the data of a model respectively.

Create a php file named Cars.php in the folder VendorNameModulenameModel.

The contents of the file are:

<?php
namespace VendorNameModulenameModel;

use MagentoFrameworkDataObjectIdentityInterface;
use MagentoFrameworkModelAbstractModel;
use VendorNameModulenameApiDataCarsInterface;
use VendorNameModulenameModelResourceModelCars as ResourceModel;

/**
 * Class Cars
 */
class Cars extends AbstractModel implements
    CarsInterface,
    IdentityInterface
{
    const CACHE_TAG = 'cars_table';

    /**
     * Init
     */
    protected function _construct() // phpcs:ignore PSR2.Methods.MethodDeclaration
    {
        $this->_init(ResourceModel::class);
    }

    /**
     * @inheritDoc
     */
    public function getIdentities()
    {
        return (self::CACHE_TAG . '_' . $this->getId());
    }

    public function getId()
    {
        return $this->getData('cars_id');
    }
    public function setId($car_id)
    {
        return $this->setData('cars_id', $car_id);
    }
    public function getTitle()
    {
        return $this->getData('title');
    }
    public function setTitle($title)
    {
        return $this->setData('title', $title);
    }
    public function getDescription()
    {
        return $this->getData('description');
    }
    public function setDescription($description)
    {
        return $this->setData('description', $description);
    }
    public function setUpdateTime($updateTime)
    {
        return $this->setData('update_time', $updateTime);
    }
}

The Cars class only has one method, _construct(), when we call the _init() method, and pass the resource model’s name as its parameter. Rest of the methods are implemented from the CarsInterface. Now we will see about resource model.

Resource model:

All of the actual database operations are executed by the resource model. Every model must have a resource model, since all of the methods of a resource model expects a model as its first parameter. All resource models must extend the
MagentoFrameworkModelResourceModelDbAbstractDb class.

Create a php file named Cars.php in the folder VendorNameModulenameModelResourceModel

The contents of the file are:

<?php
namespace VendorNameModulenameModelResourceModel;

/**
 * Class Cars
 */
class Cars extends MagentoFrameworkModelResourceModelDbAbstractDb
{
    /**
     * Init
     */
    protected function _construct() // phpcs:ignore PSR2.Methods.MethodDeclaration
    {
        $this->_init('cars_table', 'cars_id');
    }
}

Here we specify the table name and a primary key of the table. Since our table name is cars_table and the primary key is cars_id we pass them both as the parameter.

Collection:

Collections are used when we want to fetch multiple rows from our table. Collections can be used when we want to,
* Fetch multiple rows from a table
* Join tables with our primary table
* Select specific columns
* Apply a WHERE clause to our query
* Use GROUP BY or ORDER BY in our query

Create a php file named Collection.php in the folder
VendorNameModulenameModelResourceModelCars

The contents of the file are.

<?php
namespace VendorNameModulenameModelResourceModelCars;

use VendorNameModulenameModelCars as Model;
use VendorNameModulenameModelResourceModelCars as ResourceModel;

/**
 * Class Collection
 */
class Collection extends MagentoFrameworkModelResourceModelDbCollectionAbstractCollection
{
    /**
     * Init
     */
    protected function _construct() // phpcs:ignore PSR2.Methods.MethodDeclaration
    {
        $this->_init(Model::class, ResourceModel::class);
    }
}

Our collection has a single method _construct() which calls the _init() method that takes our Model and ResourceModel classes as the parameters.

Now that we have created the model system in magento we have one more step to start using this model system in magento. We have to create a class that implements the repsository interface that was created earlier. Go on and create VendorNameModulenameModelCarsRepository.php file.

<?php
namespace VendorNameModulenameModel;

use MagentoFrameworkApiSearchCriteriaInterface;
use MagentoFrameworkApiSearchResultsInterfaceFactory;
use MagentoFrameworkExceptionCouldNotDeleteException;
use MagentoFrameworkExceptionCouldNotSaveException;
use MagentoFrameworkExceptionNoSuchEntityException;

use VendorNameModulenameApiCarsRepositoryInterface;
use VendorNameModulenameApiDataCarsInterface;
use VendorNameModulenameModelCarsFactory;
use VendorNameModulenameModelResourceModelCars as ObjectResourceModel;
use VendorNameModulenameModelResourceModelCarsCollectionFactory;

/**
 * Class CarsRepository
 */
class CarsRepository implements CarsRepositoryInterface
{
    protected $objectFactory;
    protected $objectResourceModel;
    protected $collectionFactory;
    protected $searchResultsFactory;

    /**
     * CarsRepository constructor.
     *
     * @param CarsFactory $objectFactory
     * @param ObjectResourceModel $objectResourceModel
     * @param CollectionFactory $collectionFactory
     * @param SearchResultsInterfaceFactory $searchResultsFactory
     */
    public function __construct(
        CarsFactory $objectFactory,
        ObjectResourceModel $objectResourceModel,
        CollectionFactory $collectionFactory,
        SearchResultsInterfaceFactory $searchResultsFactory
    ) {
        $this->objectFactory        = $objectFactory;
        $this->objectResourceModel  = $objectResourceModel;
        $this->collectionFactory    = $collectionFactory;
        $this->searchResultsFactory = $searchResultsFactory;
    }

    /**
     * @inheritDoc
     *
     * @throws CouldNotSaveException
     */
    public function save(CarsInterface $object)
    {
        try {
            $this->objectResourceModel->save($object);
        } catch (Exception $e) {
            throw new CouldNotSaveException(__($e->getMessage()));
        }
        return $object;
    }

    /**
     * @inheritDoc
     */
    public function getById($id)
    {
        $object = $this->objectFactory->create();
        $this->objectResourceModel->load($object, $id);
        if (!$object->getId()) {
            throw new NoSuchEntityException(__('Object with id "%1" does not exist.', $id));
        }
        return $object;
    }

    /**
     * @inheritDoc
     */
    public function delete(CarsInterface $object)
    {
        try {
            $this->objectResourceModel->delete($object);
        } catch (Exception $exception) {
            throw new CouldNotDeleteException(__($exception->getMessage()));
        }
        return true;
    }

    /**
     * @inheritDoc
     */
    public function deleteById($id)
    {
        return $this->delete($this->getById($id));
    }

    /**
     * @inheritDoc
     */
    public function getList(SearchCriteriaInterface $criteria)
    {
        $searchResults = $this->searchResultsFactory->create();
        $searchResults->setSearchCriteria($criteria);
        $collection = $this->collectionFactory->create();
        foreach ($criteria->getFilterGroups() as $filterGroup) {
            $fields = ();
            $conditions = ();
            foreach ($filterGroup->getFilters() as $filter) {
                $condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq';
                $fields() = $filter->getField();
                $conditions() = ($condition => $filter->getValue());
            }
            if ($fields) {
                $collection->addFieldToFilter($fields, $conditions);
            }
        }
        $searchResults->setTotalCount($collection->getSize());
        $sortOrders = $criteria->getSortOrders();
        if ($sortOrders) {
            /** @var SortOrder $sortOrder */
            foreach ($sortOrders as $sortOrder) {
                $collection->addOrder(
                    $sortOrder->getField(),
                    ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC'
                );
            }
        }
        $collection->setCurPage($criteria->getCurrentPage());
        $collection->setPageSize($criteria->getPageSize());
        $objects = ();
        foreach ($collection as $objectModel) {
            $objects() = $objectModel;
        }
        $searchResults->setItems($objects);
        return $searchResults;
    }
}

Now all we have to do is use this repository class in our module front-end and perform CRUD operation.

Bellow is the code for controller for the CRUD operation.

<?php
namespace VendorNameModulenameControllerIndex;

    use MagentoFrameworkAppActionAction;
    use MagentoFrameworkAppActionContext;
    use MagentoFrameworkExceptionCouldNotSaveException;
    use MagentoFrameworkExceptionLocalizedException;
    use MagentoFrameworkExceptionNoSuchEntityException;
    use MagentoFrameworkViewResultPageFactory;

    use VendorNameModulenameApiCarsRepositoryInterface;
    use VendorNameModulenameApiDataCarsInterface

    class Index extends Action
    {
        protected $_pageFactory;

        protected $_carsRepository;
        protected $_carsModel;


        public function __construct(
            Context $context,
            PageFactory $pageFactory,
            CarsRepositoryInterface $carsRepository,
            CarsInterface $carsInterface
        ) {
            $this->_pageFactory = $pageFactory;
            $this->_carsRepository=$carsRepository;
            $this->_carsModel = $carsInterface;
            return parent::__construct($context);
        }

        public function execute()
        {
            echo "Hello World!!!";
            //Create a record.
        $this->_carsModel->setTitle("Title");
        $this->_carsModel->setDescriptions("Description");

            try {
                $this->_carsRepository->save($this->_carsModel);
            } catch (CouldNotSaveException $e) {
                echo $e->getMessage();
            }
            //Read a record
            try {
                $car = $this->_carsRepository->getById("19");
                echo "Car id = " . $car->getId() . "<br>";
                echo "Car Title = " . $car->getTitle();
            } catch (NoSuchEntityException $e) {
                echo "No such entity exception - " . $e->getMessage();
            } catch (LocalizedException $e) {
                echo "Localized Exception" . $e->getMessage();
            }
            //Update a record
            try {
                $car = $this->_carsRepository->getById("21");
                echo "Car id = " . $car->getId() . "<br>";
                echo "Car Title = " . $car->getTitle();
                $car->setTitle("This is the updated title");
                $car->setDescription("This is the updated Description");
                $this->_carsRepository->save($car);
            } catch (NoSuchEntityException $e) {
                echo "No such entity exception - " . $e->getMessage();
            } catch (LocalizedException $e) {
                echo "Localized Exception" . $e->getMessage();
            }
            //Delete a record
            try {
                $this->_carsRepository->deleteById("20");
                echo "Deleted the record with id = 20" . "<br>" . "Go to database and check.";
            } catch (NoSuchEntityException $e) {
                echo "No such entity exception - " . $e->getMessage();
            } catch (LocalizedException $e) {
                echo "Localized Exception" . $e->getMessage();
            }
            exit;
        }
    }

You should inject the VendorNameModulenameApiCarsRepositoryInterface and VendorNameModulenameApiDataCarsInterface in the constructor of the block and use the CRUD operations in the block.

If you want to know how to use the getList() method, just mention that in the comment.

Hope this helps.

Comment if there is any spelling mistakes or continuity error in the documentation.