Chapter 1: Magento 2 Front Banner Module – Complete Project Structure
In this comprehensive tutorial series, we will build a complete Magento 2 module called FrontBanner.
This module provides full banner management functionality including admin grid, form, image upload,
and frontend slider display. The module follows Magento 2 best practices and coding standards.
Complete Module Features
- Complete admin grid with banner listing
- Add/Edit banner functionality with image upload
- Image preview in admin grid
- Delete banner functionality
- AJAX image upload
- Frontend banner slider
- ACL permissions for admin access
- Database schema with declarative setup
Final Project Structure
Below is the complete folder structure of the FrontBanner module as implemented in Acesoftech/Frontendbanner.
C:.
│ registration.php
│
├───Block
│ │ BannerSlider.php
│ │
│ └───Adminhtml
│ │ Banner.php
│ │
│ └───Banner
│ │ Edit.php
│ │ Grid.php
│ │
│ ├───Edit
│ │ Form.php
│ │
│ └───Renderer
│ Image.php
│
├───Controller
│ └───Adminhtml
│ ├───Banner
│ │ Delete.php
│ │ Edit.php
│ │ Index.php
│ │ NewAction.php
│ │ Save.php
│ │ Upload.php
│ │
│ ├───Hello
│ │ World.php
│ │
│ ├───Index
│ │ Index.php
│ │
│ ├───Ping
│ │ Index.php
│ │
│ └───Test
│ Index.php
│
├───etc
│ │ acl.xml
│ │ db_schema.xml
│ │ module.xml
│ │
│ └───adminhtml
│ menu.xml
│ routes.xml
│
├───Model
│ │ Banner.php
│ │ ImageUploader.php
│ │
│ └───ResourceModel
│ │ Banner.php
│ │
│ └───Banner
│ Collection.php
│
├───Setup
│ InstallData.php
│
└───view
├───adminhtml
│ └───layout
│ frontbanner_banner_edit.xml
│ frontbanner_banner_index.xml
│
└───frontend
├───layout
│ cms_index_index.xml
│
└───templates
slider.phtml
Chapter 2: Module Registration and Configuration
2.1 registration.php
Location: app/code/FrontBanner/FrontBanner/registration.php
<?php
/**
* Copyright © Acesoftech Pvt. Ltd. All rights reserved.
*/
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'FrontBanner_FrontBanner',
__DIR__
);
2.2 etc/module.xml
Location: app/code/FrontBanner/FrontBanner/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="FrontBanner_FrontBanner" setup_version="1.0.0">
<sequence>
<module name="Magento_Backend"/>
<module name="Magento_Cms"/>
</sequence>
</module>
</config>
Chapter 3: Admin Routes and Menu Configuration
3.1 etc/adminhtml/routes.xml
Location: app/code/FrontBanner/FrontBanner/etc/adminhtml/routes.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="admin">
<route id="frontbanner" frontName="frontbanner">
<module name="FrontBanner_FrontBanner" />
</route>
</router>
</config>
3.2 etc/adminhtml/menu.xml
Location: app/code/FrontBanner/FrontBanner/etc/adminhtml/menu.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
<menu>
<add id="FrontBanner_FrontBanner::banner"
title="Banner Manager"
module="FrontBanner_FrontBanner"
sortOrder="10"
parent="Magento_Backend::content"
action="frontbanner/banner/index"
resource="FrontBanner_FrontBanner::banner"/>
</menu>
</config>
3.3 etc/acl.xml
Location: app/code/FrontBanner/FrontBanner/etc/acl.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
<acl>
<resources>
<resource id="Magento_Backend::admin">
<resource id="FrontBanner_FrontBanner::banner"
title="Banner Manager"
sortOrder="10" />
</resource>
</resources>
</acl>
</config>
Chapter 4: Database Schema
4.1 etc/db_schema.xml
Location: app/code/FrontBanner/FrontBanner/etc/db_schema.xml
<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
<table name="frontbanner_banner" resource="default" engine="innodb"
comment="Front Banner Table">
<column xsi:type="int" name="banner_id" padding="10" unsigned="true"
nullable="false" identity="true" comment="Banner ID"/>
<column xsi:type="varchar" name="title" nullable="false" length="255"
comment="Banner Title"/>
<column xsi:type="varchar" name="image" nullable="true" length="255"
comment="Banner Image"/>
<column xsi:type="text" name="description" nullable="true"
comment="Banner Description"/>
<column xsi:type="varchar" name="link" nullable="true" length="255"
comment="Banner Link"/>
<column xsi:type="smallint" name="status" nullable="false" default="1"
comment="Banner Status"/>
<column xsi:type="int" name="sort_order" nullable="false" default="0"
comment="Sort Order"/>
<column xsi:type="timestamp" name="created_at" nullable="false"
default="CURRENT_TIMESTAMP" comment="Created At"/>
<column xsi:type="timestamp" name="updated_at" nullable="false"
default="CURRENT_TIMESTAMP" on_update="true" comment="Updated At"/>
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="banner_id"/>
</constraint>
<index referenceId="FRONTBANNER_BANNER_STATUS">
<column name="status"/>
</index>
<index referenceId="FRONTBANNER_BANNER_SORT_ORDER">
<column name="sort_order"/>
</index>
</table>
</schema>
Chapter 5: Model Layer
5.1 Model/Banner.php
Location: app/code/FrontBanner/FrontBanner/Model/Banner.php
<?php
namespace FrontBanner\FrontBanner\Model;
use Magento\Framework\Model\AbstractModel;
use FrontBanner\FrontBanner\Model\ResourceModel\Banner as ResourceModel;
class Banner extends AbstractModel
{
const STATUS_ENABLED = 1;
const STATUS_DISABLED = 0;
/**
* @var string
*/
protected $_eventPrefix = 'frontbanner_banner_model';
/**
* @var string
*/
protected $_eventObject = 'banner';
/**
* Initialize resource model
*
* @return void
*/
protected function _construct()
{
$this->_init(ResourceModel::class);
}
/**
* Get banner statuses
*
* @return array
*/
public function getAvailableStatuses()
{
return [
self::STATUS_ENABLED => __('Enabled'),
self::STATUS_DISABLED => __('Disabled')
];
}
/**
* Get image URL
*
* @return string
*/
public function getImageUrl()
{
$image = $this->getImage();
if ($image) {
return $this->_getUrl()->getBaseUrl(['_type' => \Magento\Framework\UrlInterface::URL_TYPE_MEDIA])
. 'frontbanner/banner/' . $image;
}
return '';
}
/**
* Get URL instance
*
* @return \Magento\Framework\UrlInterface
*/
protected function _getUrl()
{
return \Magento\Framework\App\ObjectManager::getInstance()
->get(\Magento\Framework\UrlInterface::class);
}
}
5.2 Model/ImageUploader.php
Location: app/code/FrontBanner/FrontBanner/Model/ImageUploader.php
<?php
namespace FrontBanner\FrontBanner\Model;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem;
use Magento\MediaStorage\Model\File\UploaderFactory;
use Magento\Framework\App\Filesystem\DirectoryList;
use Psr\Log\LoggerInterface;
class ImageUploader
{
/**
* @var string
*/
const BASE_TMP_PATH = 'frontbanner/banner/tmp';
/**
* @var string
*/
const BASE_PATH = 'frontbanner/banner';
/**
* @var string[]
*/
protected $allowedExtensions = ['jpg', 'jpeg', 'gif', 'png'];
/**
* @var UploaderFactory
*/
protected $uploaderFactory;
/**
* @var Filesystem\Directory\WriteInterface
*/
protected $mediaDirectory;
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @param Filesystem $filesystem
* @param UploaderFactory $uploaderFactory
* @param LoggerInterface $logger
* @throws \Magento\Framework\Exception\FileSystemException
*/
public function __construct(
Filesystem $filesystem,
UploaderFactory $uploaderFactory,
LoggerInterface $logger
) {
$this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
$this->uploaderFactory = $uploaderFactory;
$this->logger = $logger;
}
/**
* Save file to temp media directory
*
* @param string $fileId
* @return array
* @throws LocalizedException
*/
public function saveFileToTmpDir($fileId)
{
try {
$this->createDirectoryIfNotExists(self::BASE_TMP_PATH);
/** @var \Magento\MediaStorage\Model\File\Uploader $uploader */
$uploader = $this->uploaderFactory->create(['fileId' => $fileId]);
$uploader->setAllowedExtensions($this->allowedExtensions);
$uploader->setAllowRenameFiles(true);
$result = $uploader->save($this->mediaDirectory->getAbsolutePath(self::BASE_TMP_PATH));
if (!$result) {
throw new LocalizedException(__('File can not be saved to the destination folder.'));
}
unset($result['path']);
return $result;
} catch (\Exception $e) {
$this->logger->critical($e);
throw new LocalizedException(__('Something went wrong while saving the file.'));
}
}
/**
* Move file from tmp to permanent directory
*
* @param string $filename
* @return string
*/
public function moveFileFromTmp($filename)
{
$this->createDirectoryIfNotExists(self::BASE_PATH);
$tmpFile = $this->getFilePath(self::BASE_TMP_PATH, $filename);
$newFile = $this->getFilePath(self::BASE_PATH, $filename);
if ($this->mediaDirectory->isExist($tmpFile)) {
$this->mediaDirectory->renameFile($tmpFile, $newFile);
}
return $filename;
}
/**
* Create directory if not exists
*
* @param string $path
* @return void
*/
protected function createDirectoryIfNotExists($path)
{
if (!$this->mediaDirectory->isExist($path)) {
$this->mediaDirectory->create($path);
}
}
/**
* Get file path
*
* @param string $path
* @param string $filename
* @return string
*/
protected function getFilePath($path, $filename)
{
return rtrim($path, '/') . '/' . ltrim($filename, '/');
}
}
Chapter 6: Resource Model Layer
6.1 Model/ResourceModel/Banner.php
Location: app/code/FrontBanner/FrontBanner/Model/ResourceModel/Banner.php
<?php
namespace FrontBanner\FrontBanner\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class Banner extends AbstractDb
{
/**
* @var string
*/
protected $_eventPrefix = 'frontbanner_banner_resource_model';
/**
* Initialize resource model
*
* @return void
*/
protected function _construct()
{
$this->_init('frontbanner_banner', 'banner_id');
}
/**
* Process banner data before saving
*
* @param \Magento\Framework\Model\AbstractModel $object
* @return $this
*/
protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object)
{
if ($object->isObjectNew()) {
$object->setCreatedAt(date('Y-m-d H:i:s'));
}
$object->setUpdatedAt(date('Y-m-d H:i:s'));
return parent::_beforeSave($object);
}
}
6.2 Model/ResourceModel/Banner/Collection.php
Location: app/code/FrontBanner/FrontBanner/Model/ResourceModel/Banner/Collection.php
<?php
namespace FrontBanner\FrontBanner\Model\ResourceModel\Banner;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use FrontBanner\FrontBanner\Model\Banner as BannerModel;
use FrontBanner\FrontBanner\Model\ResourceModel\Banner as BannerResource;
class Collection extends AbstractCollection
{
/**
* @var string
*/
protected $_idFieldName = 'banner_id';
/**
* @var string
*/
protected $_eventPrefix = 'frontbanner_banner_collection';
/**
* @var string
*/
protected $_eventObject = 'banner_collection';
/**
* Define resource model
*
* @return void
*/
protected function _construct()
{
$this->_init(BannerModel::class, BannerResource::class);
}
/**
* Add enabled filter
*
* @return $this
*/
public function addEnabledFilter()
{
$this->addFieldToFilter('status', BannerModel::STATUS_ENABLED);
return $this;
}
/**
* Add sort order
*
* @param string $direction
* @return $this
*/
public function addSortOrder($direction = 'ASC')
{
$this->setOrder('sort_order', $direction);
return $this;
}
}
Chapter 7: Admin Controllers
7.1 Controller/Adminhtml/Banner/Index.php
Location: app/code/FrontBanner/FrontBanner/Controller/Adminhtml/Banner/Index.php
<?php
namespace FrontBanner\FrontBanner\Controller\Adminhtml\Banner;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
class Index extends Action
{
/**
* @var PageFactory
*/
protected $resultPageFactory;
/**
* @param Context $context
* @param PageFactory $resultPageFactory
*/
public function __construct(
Context $context,
PageFactory $resultPageFactory
) {
parent::__construct($context);
$this->resultPageFactory = $resultPageFactory;
}
/**
* Check the permission
*
* @return bool
*/
protected function _isAllowed()
{
return $this->_authorization->isAllowed('FrontBanner_FrontBanner::banner');
}
/**
* Index action
*
* @return \Magento\Framework\View\Result\Page
*/
public function execute()
{
/** @var \Magento\Backend\Model\View\Result\Page $resultPage */
$resultPage = $this->resultPageFactory->create();
$resultPage->setActiveMenu('FrontBanner_FrontBanner::banner');
$resultPage->addBreadcrumb(__('CMS'), __('CMS'));
$resultPage->addBreadcrumb(__('Banner Manager'), __('Banner Manager'));
$resultPage->getConfig()->getTitle()->prepend(__('Banner Manager'));
return $resultPage;
}
}
7.2 Controller/Adminhtml/Banner/NewAction.php
Location: app/code/FrontBanner/FrontBanner/Controller/Adminhtml/Banner/NewAction.php
<?php
namespace FrontBanner\FrontBanner\Controller\Adminhtml\Banner;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
class NewAction extends Action
{
/**
* @var PageFactory
*/
protected $resultPageFactory;
/**
* @param Context $context
* @param PageFactory $resultPageFactory
*/
public function __construct(
Context $context,
PageFactory $resultPageFactory
) {
parent::__construct($context);
$this->resultPageFactory = $resultPageFactory;
}
/**
* Check the permission
*
* @return bool
*/
protected function _isAllowed()
{
return $this->_authorization->isAllowed('FrontBanner_FrontBanner::banner');
}
/**
* New action
*
* @return \Magento\Framework\View\Result\Page
*/
public function execute()
{
$resultPage = $this->resultPageFactory->create();
$resultPage->setActiveMenu('FrontBanner_FrontBanner::banner');
$resultPage->addBreadcrumb(__('CMS'), __('CMS'));
$resultPage->addBreadcrumb(__('Banner Manager'), __('Banner Manager'));
$resultPage->getConfig()->getTitle()->prepend(__('New Banner'));
return $resultPage;
}
}
7.3 Controller/Adminhtml/Banner/Edit.php
Location: app/code/FrontBanner/FrontBanner/Controller/Adminhtml/Banner/Edit.php
<?php
namespace FrontBanner\FrontBanner\Controller\Adminhtml\Banner;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
use FrontBanner\FrontBanner\Model\BannerFactory;
use Magento\Framework\Registry;
class Edit extends Action
{
/**
* @var PageFactory
*/
protected $resultPageFactory;
/**
* @var BannerFactory
*/
protected $bannerFactory;
/**
* @var Registry
*/
protected $coreRegistry;
/**
* @param Context $context
* @param PageFactory $resultPageFactory
* @param BannerFactory $bannerFactory
* @param Registry $coreRegistry
*/
public function __construct(
Context $context,
PageFactory $resultPageFactory,
BannerFactory $bannerFactory,
Registry $coreRegistry
) {
parent::__construct($context);
$this->resultPageFactory = $resultPageFactory;
$this->bannerFactory = $bannerFactory;
$this->coreRegistry = $coreRegistry;
}
/**
* Check the permission
*
* @return bool
*/
protected function _isAllowed()
{
return $this->_authorization->isAllowed('FrontBanner_FrontBanner::banner');
}
/**
* Edit action
*
* @return \Magento\Framework\View\Result\Page
*/
public function execute()
{
$id = $this->getRequest()->getParam('banner_id');
$model = $this->bannerFactory->create();
if ($id) {
$model->load($id);
if (!$model->getId()) {
$this->messageManager->addErrorMessage(__('This banner no longer exists.'));
$resultRedirect = $this->resultRedirectFactory->create();
return $resultRedirect->setPath('*/*/');
}
}
$this->coreRegistry->register('frontbanner_banner', $model);
$resultPage = $this->resultPageFactory->create();
$resultPage->setActiveMenu('FrontBanner_FrontBanner::banner');
$resultPage->addBreadcrumb(__('CMS'), __('CMS'));
$resultPage->addBreadcrumb(__('Banner Manager'), __('Banner Manager'));
$resultPage->getConfig()->getTitle()->prepend(
$model->getId() ? __('Edit Banner: %1', $model->getTitle()) : __('New Banner')
);
return $resultPage;
}
}
7.4 Controller/Adminhtml/Banner/Save.php
Location: app/code/FrontBanner/FrontBanner/Controller/Adminhtml/Banner/Save.php
<?php
namespace FrontBanner\FrontBanner\Controller\Adminhtml\Banner;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use FrontBanner\FrontBanner\Model\BannerFactory;
use FrontBanner\FrontBanner\Model\ImageUploader;
use Magento\Framework\Exception\LocalizedException;
class Save extends Action
{
/**
* @var BannerFactory
*/
protected $bannerFactory;
/**
* @var ImageUploader
*/
protected $imageUploader;
/**
* @param Context $context
* @param BannerFactory $bannerFactory
* @param ImageUploader $imageUploader
*/
public function __construct(
Context $context,
BannerFactory $bannerFactory,
ImageUploader $imageUploader
) {
parent::__construct($context);
$this->bannerFactory = $bannerFactory;
$this->imageUploader = $imageUploader;
}
/**
* Check the permission
*
* @return bool
*/
protected function _isAllowed()
{
return $this->_authorization->isAllowed('FrontBanner_FrontBanner::banner');
}
/**
* Save action
*
* @return \Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
$data = $this->getRequest()->getPostValue();
$resultRedirect = $this->resultRedirectFactory->create();
if ($data) {
$id = $this->getRequest()->getParam('banner_id');
$model = $this->bannerFactory->create();
if ($id) {
$model->load($id);
}
// Handle image upload
if (isset($data['image']) && isset($data['image']['value'])) {
$data['image'] = $data['image']['value'];
} elseif (isset($data['image']) && is_array($data['image'])) {
if (!empty($data['image'][0]['name']) && !empty($data['image'][0]['tmp_name'])) {
$data['image'] = $this->imageUploader->moveFileFromTmp($data['image'][0]['name']);
} else {
unset($data['image']);
}
} else {
if (isset($data['image']) && !$data['image'][0]['name']) {
$data['image'] = '';
}
}
$model->setData($data);
try {
$model->save();
$this->messageManager->addSuccessMessage(__('You saved the banner.'));
if ($this->getRequest()->getParam('back')) {
return $resultRedirect->setPath('*/*/edit', ['banner_id' => $model->getId()]);
}
return $resultRedirect->setPath('*/*/');
} catch (LocalizedException $e) {
$this->messageManager->addErrorMessage($e->getMessage());
} catch (\Exception $e) {
$this->messageManager->addErrorMessage(__('Something went wrong while saving the banner.'));
}
return $resultRedirect->setPath('*/*/edit', ['banner_id' => $id]);
}
return $resultRedirect->setPath('*/*/');
}
}
7.5 Controller/Adminhtml/Banner/Delete.php
Location: app/code/FrontBanner/FrontBanner/Controller/Adminhtml/Banner/Delete.php
<?php
namespace FrontBanner\FrontBanner\Controller\Adminhtml\Banner;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use FrontBanner\FrontBanner\Model\BannerFactory;
class Delete extends Action
{
/**
* @var BannerFactory
*/
protected $bannerFactory;
/**
* @param Context $context
* @param BannerFactory $bannerFactory
*/
public function __construct(
Context $context,
BannerFactory $bannerFactory
) {
parent::__construct($context);
$this->bannerFactory = $bannerFactory;
}
/**
* Check the permission
*
* @return bool
*/
protected function _isAllowed()
{
return $this->_authorization->isAllowed('FrontBanner_FrontBanner::banner');
}
/**
* Delete action
*
* @return \Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
$id = $this->getRequest()->getParam('banner_id');
$resultRedirect = $this->resultRedirectFactory->create();
if ($id) {
try {
$model = $this->bannerFactory->create();
$model->load($id);
$model->delete();
$this->messageManager->addSuccessMessage(__('You deleted the banner.'));
return $resultRedirect->setPath('*/*/');
} catch (\Exception $e) {
$this->messageManager->addErrorMessage($e->getMessage());
return $resultRedirect->setPath('*/*/edit', ['banner_id' => $id]);
}
}
$this->messageManager->addErrorMessage(__('We can\'t find a banner to delete.'));
return $resultRedirect->setPath('*/*/');
}
}
7.6 Controller/Adminhtml/Banner/Upload.php
Location: app/code/FrontBanner/FrontBanner/Controller/Adminhtml/Banner/Upload.php
<?php
namespace FrontBanner\FrontBanner\Controller\Adminhtml\Banner;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use FrontBanner\FrontBanner\Model\ImageUploader;
use Magento\Framework\Controller\ResultFactory;
class Upload extends Action
{
/**
* @var ImageUploader
*/
protected $imageUploader;
/**
* @param Context $context
* @param ImageUploader $imageUploader
*/
public function __construct(
Context $context,
ImageUploader $imageUploader
) {
parent::__construct($context);
$this->imageUploader = $imageUploader;
}
/**
* Check the permission
*
* @return bool
*/
protected function _isAllowed()
{
return $this->_authorization->isAllowed('FrontBanner_FrontBanner::banner');
}
/**
* Upload action
*
* @return \Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
try {
$result = $this->imageUploader->saveFileToTmpDir('image');
$result['cookie'] = [
'name' => $this->_getSession()->getName(),
'value' => $this->_getSession()->getSessionId(),
'lifetime' => $this->_getSession()->getCookieLifetime(),
'path' => $this->_getSession()->getCookiePath(),
'domain' => $this->_getSession()->getCookieDomain(),
];
} catch (\Exception $e) {
$result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
}
return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setData($result);
}
}
Chapter 8: Admin Blocks
8.1 Block/Adminhtml/Banner.php
Location: app/code/FrontBanner/FrontBanner/Block/Adminhtml/Banner.php
<?php
namespace FrontBanner\FrontBanner\Block\Adminhtml;
use Magento\Backend\Block\Widget\Grid\Container;
class Banner extends Container
{
/**
* Constructor
*
* @return void
*/
protected function _construct()
{
$this->_controller = 'adminhtml_banner';
$this->_blockGroup = 'FrontBanner_FrontBanner';
$this->_headerText = __('Banner Manager');
$this->_addButtonLabel = __('Add New Banner');
parent::_construct();
}
}
8.2 Block/Adminhtml/Banner/Grid.php
Location: app/code/FrontBanner/FrontBanner/Block/Adminhtml/Banner/Grid.php
<?php
namespace FrontBanner\FrontBanner\Block\Adminhtml\Banner;
use Magento\Backend\Block\Widget\Grid\Extended;
use Magento\Backend\Block\Template\Context;
use Magento\Backend\Helper\Data;
use FrontBanner\FrontBanner\Model\ResourceModel\Banner\CollectionFactory;
use FrontBanner\FrontBanner\Model\Banner;
class Grid extends Extended
{
/**
* @var CollectionFactory
*/
protected $collectionFactory;
/**
* @param Context $context
* @param Data $backendHelper
* @param CollectionFactory $collectionFactory
* @param array $data
*/
public function __construct(
Context $context,
Data $backendHelper,
CollectionFactory $collectionFactory,
array $data = []
) {
$this->collectionFactory = $collectionFactory;
parent::__construct($context, $backendHelper, $data);
}
/**
* Initialize grid
*
* @return void
*/
protected function _construct()
{
parent::_construct();
$this->setId('bannerGrid');
$this->setDefaultSort('banner_id');
$this->setDefaultDir('DESC');
$this->setSaveParametersInSession(true);
$this->setUseAjax(true);
}
/**
* Prepare collection
*
* @return \Magento\Backend\Block\Widget\Grid\Extended
*/
protected function _prepareCollection()
{
$collection = $this->collectionFactory->create();
$this->setCollection($collection);
return parent::_prepareCollection();
}
/**
* Prepare columns
*
* @return \Magento\Backend\Block\Widget\Grid\Extended
*/
protected function _prepareColumns()
{
$this->addColumn('banner_id', [
'header' => __('ID'),
'index' => 'banner_id',
'type' => 'number',
'width' => '50px'
]);
$this->addColumn('title', [
'header' => __('Title'),
'index' => 'title',
'class' => 'xxx',
'width' => '200px'
]);
$this->addColumn('image', [
'header' => __('Image'),
'class' => 'xxx',
'width' => '150px',
'filter' => false,
'renderer' => \FrontBanner\FrontBanner\Block\Adminhtml\Banner\Renderer\Image::class
]);
$this->addColumn('description', [
'header' => __('Description'),
'index' => 'description',
'class' => 'xxx',
'width' => '300px'
]);
$this->addColumn('link', [
'header' => __('Link'),
'index' => 'link',
'class' => 'xxx',
'width' => '200px'
]);
$this->addColumn('sort_order', [
'header' => __('Sort Order'),
'index' => 'sort_order',
'type' => 'number',
'width' => '80px'
]);
$this->addColumn('status', [
'header' => __('Status'),
'index' => 'status',
'type' => 'options',
'options' => [
Banner::STATUS_ENABLED => __('Enabled'),
Banner::STATUS_DISABLED => __('Disabled')
],
'width' => '100px'
]);
$this->addColumn('created_at', [
'header' => __('Created At'),
'index' => 'created_at',
'type' => 'datetime',
'width' => '170px'
]);
$this->addColumn('updated_at', [
'header' => __('Updated At'),
'index' => 'updated_at',
'type' => 'datetime',
'width' => '170px'
]);
$this->addColumn('action', [
'header' => __('Action'),
'width' => '100px',
'type' => 'action',
'getter' => 'getId',
'actions' => [
[
'caption' => __('Edit'),
'url' => [
'base' => '*/*/edit'
],
'field' => 'banner_id'
]
],
'filter' => false,
'sortable' => false,
'index' => 'stores',
'is_system' => true
]);
return parent::_prepareColumns();
}
/**
* Get row url
*
* @param \Magento\Framework\DataObject $row
* @return string
*/
public function getRowUrl($row)
{
return $this->getUrl('*/*/edit', ['banner_id' => $row->getId()]);
}
/**
* Get grid url
*
* @return string
*/
public function getGridUrl()
{
return $this->getUrl('*/*/grid', ['_current' => true]);
}
}
8.3 Block/Adminhtml/Banner/Renderer/Image.php
Location: app/code/FrontBanner/FrontBanner/Block/Adminhtml/Banner/Renderer/Image.php
<?php
namespace FrontBanner\FrontBanner\Block\Adminhtml\Banner\Renderer;
use Magento\Framework\DataObject;
use Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer;
class Image extends AbstractRenderer
{
/**
* @var \Magento\Framework\UrlInterface
*/
protected $urlBuilder;
/**
* @param \Magento\Backend\Block\Context $context
* @param array $data
*/
public function __construct(
\Magento\Backend\Block\Context $context,
array $data = []
) {
$this->urlBuilder = $context->getUrlBuilder();
parent::__construct($context, $data);
}
/**
* Render image
*
* @param DataObject $row
* @return string
*/
public function render(DataObject $row)
{
$image = $row->getData($this->getColumn()->getIndex());
if ($image) {
$imageUrl = $this->urlBuilder->getBaseUrl(['_type' => \Magento\Framework\UrlInterface::URL_TYPE_MEDIA])
. 'frontbanner/banner/' . $image;
return '<img src="' . $imageUrl . '" width="100" height="50" />';
}
return '';
}
}
8.4 Block/Adminhtml/Banner/Edit.php
Location: app/code/FrontBanner/FrontBanner/Block/Adminhtml/Banner/Edit.php
<?php
namespace FrontBanner\FrontBanner\Block\Adminhtml\Banner;
use Magento\Backend\Block\Widget\Form\Container;
use Magento\Backend\Block\Widget\Context;
use Magento\Framework\Registry;
class Edit extends Container
{
/**
* @var Registry
*/
protected $coreRegistry;
/**
* @param Context $context
* @param Registry $registry
* @param array $data
*/
public function __construct(
Context $context,
Registry $registry,
array $data = []
) {
$this->coreRegistry = $registry;
parent::__construct($context, $data);
}
/**
* Initialize banner edit block
*
* @return void
*/
protected function _construct()
{
$this->_objectId = 'banner_id';
$this->_controller = 'adminhtml_banner';
$this->_blockGroup = 'FrontBanner_FrontBanner';
parent::_construct();
$this->buttonList->update('save', 'label', __('Save Banner'));
$this->buttonList->update('delete', 'label', __('Delete Banner'));
$this->buttonList->add(
'saveandcontinue',
[
'label' => __('Save and Continue Edit'),
'class' => 'save',
'data_attribute' => [
'mage-init' => [
'button' => [
'event' => 'saveAndContinueEdit',
'target' => '#edit_form'
]
]
]
],
-100
);
}
/**
* Get header text
*
* @return \Magento\Framework\Phrase
*/
public function getHeaderText()
{
$banner = $this->coreRegistry->registry('frontbanner_banner');
if ($banner && $banner->getId()) {
return __("Edit Banner '%1'", $this->escapeHtml($banner->getTitle()));
}
return __('New Banner');
}
}
8.5 Block/Adminhtml/Banner/Edit/Form.php
Location: app/code/FrontBanner/FrontBanner/Block/Adminhtml/Banner/Edit/Form.php
<?php
namespace FrontBanner\FrontBanner\Block\Adminhtml\Banner\Edit;
use Magento\Backend\Block\Widget\Form\Generic;
use Magento\Backend\Block\Template\Context;
use Magento\Framework\Registry;
use Magento\Framework\Data\FormFactory;
use FrontBanner\FrontBanner\Model\Banner;
class Form extends Generic
{
/**
* @var Banner
*/
protected $bannerModel;
/**
* @param Context $context
* @param Registry $registry
* @param FormFactory $formFactory
* @param Banner $bannerModel
* @param array $data
*/
public function __construct(
Context $context,
Registry $registry,
FormFactory $formFactory,
Banner $bannerModel,
array $data = []
) {
$this->bannerModel = $bannerModel;
parent::__construct($context, $registry, $formFactory, $data);
}
/**
* Prepare form
*
* @return $this
*/
protected function _prepareForm()
{
/** @var \FrontBanner\FrontBanner\Model\Banner $banner */
$banner = $this->_coreRegistry->registry('frontbanner_banner');
/** @var \Magento\Framework\Data\Form $form */
$form = $this->_formFactory->create(
[
'data' => [
'id' => 'edit_form',
'action' => $this->getData('action'),
'method' => 'post',
'enctype' => 'multipart/form-data'
]
]
);
$fieldset = $form->addFieldset(
'base_fieldset',
['legend' => __('Banner Information'), 'class' => 'fieldset-wide']
);
if ($banner && $banner->getId()) {
$fieldset->addField(
'banner_id',
'hidden',
['name' => 'banner_id']
);
}
$fieldset->addField(
'title',
'text',
[
'name' => 'title',
'label' => __('Title'),
'title' => __('Title'),
'required' => true
]
);
$fieldset->addField(
'description',
'textarea',
[
'name' => 'description',
'label' => __('Description'),
'title' => __('Description'),
'required' => false
]
);
$fieldset->addField(
'link',
'text',
[
'name' => 'link',
'label' => __('Link URL'),
'title' => __('Link URL'),
'required' => false
]
);
$fieldset->addField(
'sort_order',
'text',
[
'name' => 'sort_order',
'label' => __('Sort Order'),
'title' => __('Sort Order'),
'required' => false,
'class' => 'validate-number'
]
);
$fieldset->addField(
'status',
'select',
[
'name' => 'status',
'label' => __('Status'),
'title' => __('Status'),
'values' => $this->bannerModel->getAvailableStatuses(),
'required' => true
]
);
$fieldset->addField(
'image',
'file',
[
'name' => 'image',
'label' => __('Banner Image'),
'title' => __('Banner Image'),
'required' => false,
'after_element_html' => $this->getImageHtml($banner)
]
);
$form->setValues($banner ? $banner->getData() : []);
$form->setUseContainer(true);
$this->setForm($form);
return parent::_prepareForm();
}
/**
* Get image HTML
*
* @param \FrontBanner\FrontBanner\Model\Banner $banner
* @return string
*/
protected function getImageHtml($banner)
{
if ($banner && $banner->getImage()) {
$imageUrl = $this->_urlBuilder->getBaseUrl(['_type' => \Magento\Framework\UrlInterface::URL_TYPE_MEDIA])
. 'frontbanner/banner/' . $banner->getImage();
return '<br/><img src="' . $imageUrl . '" width="200" height="100" />';
}
return '';
}
}
Chapter 9: Frontend Blocks
9.1 Block/BannerSlider.php
Location: app/code/FrontBanner/FrontBanner/Block/BannerSlider.php
<?php
namespace FrontBanner\FrontBanner\Block;
use Magento\Framework\View\Element\Template;
use Magento\Framework\View\Element\Template\Context;
use FrontBanner\FrontBanner\Model\ResourceModel\Banner\CollectionFactory;
use FrontBanner\FrontBanner\Model\Banner;
class BannerSlider extends Template
{
/**
* @var CollectionFactory
*/
protected $collectionFactory;
/**
* @var \Magento\Framework\UrlInterface
*/
protected $urlBuilder;
/**
* @param Context $context
* @param CollectionFactory $collectionFactory
* @param array $data
*/
public function __construct(
Context $context,
CollectionFactory $collectionFactory,
array $data = []
) {
$this->collectionFactory = $collectionFactory;
$this->urlBuilder = $context->getUrlBuilder();
parent::__construct($context, $data);
}
/**
* Get banner collection
*
* @return \FrontBanner\FrontBanner\Model\ResourceModel\Banner\Collection
*/
public function getBanners()
{
$collection = $this->collectionFactory->create();
$collection->addEnabledFilter()
->addSortOrder()
->setPageSize(10)
->setCurPage(1);
return $collection;
}
/**
* Get banner image URL
*
* @param \FrontBanner\FrontBanner\Model\Banner $banner
* @return string
*/
public function getBannerImageUrl($banner)
{
if ($banner && $banner->getImage()) {
return $this->urlBuilder->getBaseUrl(['_type' => \Magento\Framework\UrlInterface::URL_TYPE_MEDIA])
. 'frontbanner/banner/' . $banner->getImage();
}
return '';
}
/**
* Get slider configuration
*
* @return string
*/
public function getSliderConfig()
{
return json_encode([
'autoplay' => true,
'autoplaySpeed' => 5000,
'dots' => true,
'arrows' => true,
'infinite' => true,
'speed' => 500,
'slidesToShow' => 1,
'slidesToScroll' => 1
]);
}
}
Chapter 10: Setup Scripts
10.1 Setup/InstallData.php
Location: app/code/FrontBanner/FrontBanner/Setup/InstallData.php
<?php
namespace FrontBanner\FrontBanner\Setup;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
class InstallData implements InstallDataInterface
{
/**
* @var EavSetupFactory
*/
protected $eavSetupFactory;
/**
* @param EavSetupFactory $eavSetupFactory
*/
public function __construct(EavSetupFactory $eavSetupFactory)
{
$this->eavSetupFactory = $eavSetupFactory;
}
/**
* Install data
*
* @param ModuleDataSetupInterface $setup
* @param ModuleContextInterface $context
* @return void
*/
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
// Add sample data or additional setup here if needed
$setup->endSetup();
}
}
Chapter 11: Layout Files
11.1 view/adminhtml/layout/frontbanner_banner_index.xml
Location: app/code/FrontBanner/FrontBanner/view/adminhtml/layout/frontbanner_banner_index.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="FrontBanner\FrontBanner\Block\Adminhtml\Banner" name="frontbanner_banner_grid"/>
</referenceContainer>
</body>
</page>
11.2 view/adminhtml/layout/frontbanner_banner_edit.xml
Location: app/code/FrontBanner/FrontBanner/view/adminhtml/layout/frontbanner_banner_edit.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="FrontBanner\FrontBanner\Block\Adminhtml\Banner\Edit" name="frontbanner_banner_edit"/>
</referenceContainer>
</body>
</page>
11.3 view/frontend/layout/cms_index_index.xml
Location: app/code/FrontBanner/FrontBanner/view/frontend/layout/cms_index_index.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="FrontBanner\FrontBanner\Block\BannerSlider"
name="frontbanner.slider"
template="FrontBanner_FrontBanner::slider.phtml"
before="-" />
</referenceContainer>
</body>
</page>
Chapter 12: Template Files
12.1 view/frontend/templates/slider.phtml
Location: app/code/FrontBanner/FrontBanner/view/frontend/templates/slider.phtml
<?php
/**
* @var \FrontBanner\FrontBanner\Block\BannerSlider $block
*/
$banners = $block->getBanners();
$sliderConfig = $block->getSliderConfig();
?>
<?php if ($banners->count() > 0): ?>
<div class="frontbanner-slider" data-mage-init='{"frontbannerSlider": }'>
<div class="banner-slider">
<?php foreach ($banners as $banner): ?>
<div class="banner-item">
<?php if ($banner->getLink()): ?>
<a href="<?= $block->escapeUrl($banner->getLink()) ?>"
title="<?= $block->escapeHtmlAttr($banner->getTitle()) ?>">
</?php endif; ?>
<img src="<?= $block->escapeUrl($block->getBannerImageUrl($banner)) ?>"
alt="<?= $block->escapeHtmlAttr($banner->getTitle()) ?>"
title="<?= $block->escapeHtmlAttr($banner->getTitle()) ?>"/>
<?php if ($banner->getDescription()): ?>
<div class="banner-description">
<?= $block->escapeHtml($banner->getDescription()) ?>
</div>
<?php endif; ?>
<?php if ($banner->getLink()): ?>
</a>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<style>
.frontbanner-slider {
margin: 20px 0;
position: relative;
}
.banner-item {
position: relative;
text-align: center;
}
.banner-item img {
max-width: 100%;
height: auto;
}
.banner-description {
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
background: rgba(0,0,0,0.7);
color: #fff;
padding: 10px;
border-radius: 5px;
}
.slick-prev, .slick-next {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 10;
background: rgba(0,0,0,0.5);
color: #fff;
border: none;
padding: 10px 15px;
cursor: pointer;
}
.slick-prev {
left: 10px;
}
.slick-next {
right: 10px;
}
.slick-dots {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
.slick-dots li {
margin: 0 5px;
}
.slick-dots button {
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(255,255,255,0.5);
border: none;
text-indent: -9999px;
cursor: pointer;
}
.slick-dots li.slick-active button {
background: #fff;
}
</style>
<script>
require(['jquery', 'slick'], function($) {
$(document).ready(function() {
$('.banner-slider').slick({
autoplay: true,
autoplaySpeed: 5000,
dots: true,
arrows: true,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1
});
});
});
</script>
<?php endif; ?>
Chapter 13: Test Controllers
13.1 Controller/Adminhtml/Hello/World.php
Location: app/code/FrontBanner/FrontBanner/Controller/Adminhtml/Hello/World.php
<?php
namespace FrontBanner\FrontBanner\Controller\Adminhtml\Hello;
use Magento\Backend\App\Action;
use Magento\Framework\Controller\ResultFactory;
class World extends Action
{
/**
* Execute action
*
* @return \Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
$result = $this->resultFactory->create(ResultFactory::TYPE_RAW);
$result->setContents('Hello World from Admin');
return $result;
}
}
13.2 Controller/Adminhtml/Index/Index.php
Location: app/code/FrontBanner/FrontBanner/Controller/Adminhtml/Index/Index.php
<?php
namespace FrontBanner\FrontBanner\Controller\Adminhtml\Index;
use Magento\Backend\App\Action;
use Magento\Framework\Controller\ResultFactory;
class Index extends Action
{
/**
* Execute action
*
* @return \Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
$result = $this->resultFactory->create(ResultFactory::TYPE_RAW);
$result->setContents('Index Controller Test');
return $result;
}
}
13.3 Controller/Adminhtml/Ping/Index.php
Location: app/code/FrontBanner/FrontBanner/Controller/Adminhtml/Ping/Index.php
<?php
namespace FrontBanner\FrontBanner\Controller\Adminhtml\Ping;
use Magento\Backend\App\Action;
use Magento\Framework\Controller\ResultFactory;
class Index extends Action
{
/**
* Execute action
*
* @return \Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
$result = $this->resultFactory->create(ResultFactory::TYPE_JSON);
$result->setData(['status' => 'pong', 'timestamp' => time()]);
return $result;
}
}
13.4 Controller/Adminhtml/Test/Index.php
Location: app/code/FrontBanner/FrontBanner/Controller/Adminhtml/Test/Index.php
<?php
namespace FrontBanner\FrontBanner\Controller\Adminhtml\Test;
use Magento\Backend\App\Action;
use Magento\Framework\Controller\ResultFactory;
class Index extends Action
{
/**
* Execute action
*
* @return \Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
$result = $this->resultFactory->create(ResultFactory::TYPE_RAW);
$result->setContents('Test Controller - Module is working!');
return $result;
}
}
Chapter 14: Module Installation Guide
Step 1: Create Module Directory Structure
mkdir -p app/code/FrontBanner/FrontBanner/{etc,Controller,Model,Block,Setup,view}
Step 2: Copy All Files
Copy all the files provided in this tutorial to their respective locations in the module directory.
Step 3: Run Magento Commands
php bin/magento module:enable FrontBanner_FrontBanner
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento cache:flush
Step 4: Verify Installation
- Go to Admin Panel → Content → Banner Manager
- Create a new banner with image
- Check the frontend homepage to see the banner slider
Step 5: Troubleshooting
# Check if module is enabled
php bin/magento module:status | grep FrontBanner
# Check for errors in log
tail -f var/log/system.log
tail -f var/log/exception.log
# Re-run setup upgrade if needed
php bin/magento setup:upgrade
# Recompile if using production mode
php bin/magento deploy:mode:set developer
Chapter 15: Module Features Summary
Admin Features:
- ✓ Banner grid with sortable columns
- ✓ Add new banner functionality
- ✓ Edit existing banners
- ✓ Delete banners with confirmation
- ✓ AJAX image upload with preview
- ✓ Image preview in grid
- ✓ Status management (Enable/Disable)
- ✓ Sort order management
- ✓ ACL permissions for security
Frontend Features:
- ✓ Automatic banner slider on homepage
- ✓ Responsive design
- ✓ Slick slider integration
- ✓ Banner links/URLs
- ✓ Banner descriptions
- ✓ Sort order respected
- ✓ Only enabled banners shown
Technical Features:
- ✓ Magento 2 coding standards
- ✓ Declarative schema setup
- ✓ Dependency injection ready
- ✓ Event observers ready
- ✓ Plugin/interceptor support
- ✓ Translation ready
- ✓ PSR-4 autoloading
Conclusion
This comprehensive Magento 2 FrontBanner module provides a complete banner management system
with both admin and frontend functionality. The module follows Magento 2 best practices and
includes all the necessary components for a production-ready extension.
The module structure demonstrates proper separation of concerns with:
- Controllers – Handle HTTP requests and responses
- Models – Business logic and data abstraction
- Blocks – Presentation logic and data preparation
- Templates – HTML markup with PHP
- Layouts – Page structure definition
- etc – Configuration files
This module can be extended further with additional features such as:
- Widget support for placing banners anywhere
- Scheduled banner display
- Multiple banner positions
- Banner statistics and tracking
- Responsive image optimization
- Multi-store support
Happy Coding!
