Magento 2.1 Uploading Ico Image and Png
Magento is currently the largest open up-source eCommerce platform in the earth. Due to its feature rich and extensible lawmaking base, merchants with large and small operations all around the world accept been using information technology for a broad diversity of projects.
Magento 1 has been around for eight years, and its successor, Magento 2, was released at the end of 2015, improving weak points of the earlier version such equally:
- Improved performance
- Official automated exam suite
- Ameliorate back-finish UI
- New, more mod front end-terminate codebase
- A more modular way to develop modules, with files contained inside the Magento code instead of being scattered all over the place
- Reduced number of conflicts between modules trying to customize the same functionality
A little over one year down the road, and the improvement is visible, even though not all of the problems mentioned take been totally solved. At present it'southward completely condom to say that Magento 2 is a much more robust slice of software than its predecessor. Some of the improvements nowadays in Magento 2 are:
- Unit and integration tests, including an official and documented mode to create them for custom modules
- Modules that are really modularized, having all of their files placed under one single directory
- A richer templating system, allowing the theme developer to create an due north-level template hierarchy
- A series of useful design patterns adopted throughout the code, improving the lawmaking quality and decreasing probability of errors created past modules—These include automatic dependency injection, service contracts, repositories, and factories, to name a few.
- Native integration to Varnish as a full page caching system, as well equally Redis for session and cache handling
- PHP 7 back up
The learning curve for Magento two, with all of these changes, has become even steeper. In this guide, I intend to show y'all how to develop your first Magento 2 module, and point you in the correct direction to go on your studies. Let'south become to it!
Magento ii Tutorial Prerequisites
It is important that yous have a good understanding of the following technologies/concepts in order to follow the rest of this article:
- Object-oriented Programming (OOP)
- PHP
- Namespaces
- MySQL
- Basic bash usage
From all of the higher up, OOP is probably the most important one. Magento was initially created by a team of experienced Java developers, and their legacy can certainly be seen throughout the codebase. In instance you are not very confident most your OOP skills, it might exist a good idea to review it earlier beginning your work with the platform.
Overview of Magento two's Compages
Magento's compages was designed with the intent of making the source code as modularized and extensible as possible. The finish goal of that approach is to allow it to be easily adapted and customized according to each project's needs.
Customizing unremarkably ways changing the beliefs of the platform's code. In the bulk of systems, this means changing the "core" code. In Magento, if you are following best practices, this is something you can avoid most of the fourth dimension, making it possible for a store to go on up to appointment with the latest security patches and feature releases in a reliable fashion.
Magento two is a Model View ViewModel (MVVM) system. While being closely related to its sibling Model View Controller (MVC), an MVVM compages provides a more robust separation between the Model and the View layers. Below is an explanation of each of the layers of a MVVM organisation:
- The Model holds the business organisation logic of the application, and depends on an associated class—the ResourceModel—for database access. Models rely on service contracts to expose their functionality to the other layers of the application.
- The View is the structure and layout of what a user sees on a screen - the bodily HTML. This is accomplished in the PHTML files distributed with modules. PHTML files are associated to each ViewModel in the Layout XML files, which would be referred to equally binders in the MVVM dialect. The layout files might also assign JavaScript files to be used in the final page.
- The ViewModel interacts with the Model layer, exposing only the necessary information to the View layer. In Magento 2, this is handled past the module's Block classes. Annotation that this was usually function of the Controller role of an MVC system. On MVVM, the controller is but responsible for handling the user menstruum, meaning that it receives requests and either tells the organization to render a view or to redirect the user to some other road.
A Magento 2 module consists of some, if not all, elements of the architecture described above. The overall architecture is described below (source):
A Magento ii module tin can in plow define external dependencies by using Composer, PHP'southward dependency manager. In the diagram above, you see that the Magento two cadre modules depend on the Zend Framework, Symfony too as other third-party libraries.
Below is the structure of Magento/Cms, a Magento 2 core module responsible for handling the creation of pages and static blocks.
Each folder holds one function of the architecture, as follows:
- Api: Service contracts, defining service interfaces and information interfaces
- Cake: The ViewModels of our MVVM architecture
- Controller: Controllers, responsible for treatment the user's menstruation while interacting with the system
- etc: Configuration XML files—The module defines itself and its parts (routes, models, blocks, observers, and cron jobs) inside this folder. The etc files tin also be used by not-core modules to override the functionality of core modules.
- Helper: Helper classes that hold code used in more than than one awarding layer. For instance, in the Cms module, helper classes are responsible for preparing HTML for presentation to the browser.
- i18n: Holds internationalization CSV files, used for translation
- Model: For Models and ResourceModels
- Observer: Holds Observers, or Models which are "observing" system events. Usually, when such an event is fired, the observer instantiates a Model to handle the necessary business logic for such an event.
- Setup: Migration classes, responsible for schema and data creation
- Test: Unit tests
- Ui: UI elements such every bit grids and forms used in the admin application
- view: Layout (XML) files and template (PHTML) files for the forepart-cease and admin application
It is also interesting to notice that, in practice, all of Magento two's inner workings live inside a module. In the epitome higher up, you tin can see, for instance, Magento_Checkout
, responsible for the checkout process, and Magento_Catalog
, responsible for the handling of products and categories. Basically, what this tells united states of america is that learning how to work with modules is the about important part of condign a Magento 2 developer.
All correct, subsequently this relatively brief introduction to the organization architecture and module structure, let'south do something more than physical, shall nosotros? Adjacent, we will go through the traditional Weblog tutorial in gild to get you comfy with Magento 2 and on track to get a Magento 2 Developer. Earlier that, we demand to set up a development surround. Permit'south get to it!
Setting up the Magento 2 Module Development Environment
At the fourth dimension of this writing, we were able to utilize the official Magento 2 DevBox, which is a Magento 2 Docker container. Docker on macOS is something I withal consider to exist unusable, at least with a system which heavily depends on fast deejay I/O such every bit Magento 2. And then, we will practice it the traditional fashion: Install all packages natively on our own motorcar.
Setting upward the Server
Installing everything surely is a bit more tedious, but the end result will be a lightning-fast Magento development environment. Believe me, you will save hours of piece of work by not depending on Docker for your Magento 2 development.
This tutorial assumes an environment on macOS with Brew installed on it. If that'southward not the example for you, the basics volition remain the same, irresolute simply the fashion you install the packages. Permit'southward start with installing all of the packages:
mash install mysql nginxb php70 php70-imagick php70-intl php70-mcrypt
And so start the services:
brew services start mysql brew services start php70 sudo brew services first nginx
Ok, now we'll point a domain to our loopback address. Open the hosts file in whatever editor, but brand sure you accept superuser permissions. Doing that with Vim would be:
sudo vim /etc/hosts
So add the following line:
127.0.0.i magento2.dev
Now we'll create a vhost in Nginx:
vim /usr/local/etc/nginx/sites-available/magento2dev.conf
Add together the following content:
server { listen 80; server_name magento2.dev; fix $MAGE_ROOT /Users/yourusername/www/magento2dev; set $MAGE_MODE developer; # Default magento Nginx config starts below root $MAGE_ROOT/pub; index alphabetize.php; autoindex off; charset off; add_header 'X-Content-Type-Options' 'nosniff'; add_header 'X-XSS-Protection' 'i; mode=cake'; location / { try_files $uri $uri/ /index.php?$args; } location /pub { location ~ ^/pub/media/(downloadable|customer|import|theme_customization/.*\.xml) { deny all; } alias $MAGE_ROOT/pub; add_header 10-Frame-Options "SAMEORIGIN"; } location /static/ { if ($MAGE_MODE = "production") { expires max; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$two concluding; } } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Enshroud-Command "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 final; } } if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } add_header 10-Frame-Options "SAMEORIGIN"; } location /media/ { try_files $uri $uri/ /get.php?$args; location ~ ^/media/theme_customization/.*\.xml { deny all; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; try_files $uri $uri/ /get.php?$args; } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; try_files $uri $uri/ /get.php?$args; } add_header X-Frame-Options "SAMEORIGIN"; } location /media/customer/ { deny all; } location /media/downloadable/ { deny all; } location /media/import/ { deny all; } location ~ /media/theme_customization/.*\.xml$ { deny all; } location /errors/ { try_files $uri =404; } location ~ ^/errors/.*\.(xml|phtml)$ { deny all; } location ~ cron\.php { deny all; } location ~ (index|get|static|report|404|503)\.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_param PHP_FLAG "session.auto_start=off \n suhosin.session.cryptua=off"; fastcgi_param PHP_VALUE "memory_limit=768M \n max_execution_time=threescore"; fastcgi_read_timeout 60s; fastcgi_connect_timeout 60s; fastcgi_param MAGE_MODE $MAGE_MODE; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # Default magento Nginx config finishes below client_max_body_size 20M; }
If you oasis't dealt with Nginx before, this file might scare you, so let us explain the little $.25 here, as it volition also shed some low-cal on some of Magento'due south inner workings. The beginning lines simply tell Nginx that we are using the default HTTP port, and our domain is magento2.dev
:
listen 80; server_name magento2.dev;
Then nosotros set some environment variables. The kickoff one—$MAGE_ROOT
—holds the path to our codebase. Notice you volition need to alter the root path to match your username/binder path, wherever y'all plan to have the source placed on:
set $MAGE_ROOT /Users/yourusername/world wide web/magento2dev;
The second variable—$MAGE_MODE
—sets the runtime mode for our store. As we are developing a module, we will use the developer mode. This allows us to code faster, as we won't have to compile or deploy static files while developing. The other modes are product and default. The real use for the latter is non yet clear.
set $MAGE_MODE developer;
Later on this variables are set, we ascertain the vhost root path. Notice that nosotros suffix the $MAGE_ROOT
variable with the /pub
binder, making only part of our shop available to the web.
root $MAGE_ROOT/pub;
We then define our alphabetize file—the file nginx volition load when the requested file doesn't exist—as alphabetize.php. This script, $MAGE_ROOT/pub/alphabetize.php
, is the primary entry point for customers visiting both the shopping cart and the admin applications. Regardless of the URL requested, index.php will be loaded and the router dispatching procedure started.
alphabetize index.php;
Next, we plough off some Nginx features. Commencement, we turn off autoindex
, which would display a file list when you request a folder, but don't specify a file, and no index is present. 2d, we plow off charset
, which would allow Nginx to automatically add together Charset headers to the response.
autoindex off; charset off;
Side by side, we define a few security headers:
add_header 'X-Content-Type-Options' 'nosniff'; add_header 'X-XSS-Protection' '1; mode=block';
This location, /
, is pointed at our root folder $MAGE_ROOT/pub
, and basically redirects whatever request received to our front end controller index.php, together with the request arguments:
location / { try_files $uri $uri/ /index.php?$args; }
The next portion might be a bit confusing, but it is quite simple. A few lines ago, nosotros defined our root as $MAGE_ROOT/pub
. That is the recommended and more secure setup, equally most of the code is not visible from the web. But it is non the simply way to ready the spider web server. Actually, most shared web servers have 1 default setup, which is to accept your web server pointing at your web folder. For those users, the Magento team has made this file gear up for those cases, when the root is defined equally $MAGE_ROOT
with the following snippet:
location /pub { location ~ ^/pub/media/(downloadable|client|import|theme_customization/.*\.xml) { deny all; } alias $MAGE_ROOT/pub; add_header X-Frame-Options "SAMEORIGIN"; }
Go along in mind that, whenever possible, information technology is best if you have your spider web server pointing at the $MAGE_ROOT/pub
folder. Your shop will be more than secure this way.
Next up, we take the static location $MAGE_ROOT/pub/static
. This folder is initially empty and filled upwards automatically with the modules' and themes' static files, such as image files, CSS, JS, etc. Here, we basically define some cache values for the static files and, when the requested file does not exist, redirect it to $MAGE_ROOT/pub/static.php
. That script will, amongst other things, analyze the request and re-create or symlink the specified file from the correspondent module or theme, depending on the runtime mode divers. This way, your module's static files will reside within our modules' binder, but will be served direct from the vhost public folder:
location /static/ { if ($MAGE_MODE = "production") { expires max; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Command "public"; add_header Ten-Frame-Options "SAMEORIGIN"; expires +1y; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resources=$ii last; } } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Enshroud-Control "no-store"; add_header 10-Frame-Options "SAMEORIGIN"; expires off; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resources=$2 last; } } if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } add_header X-Frame-Options "SAMEORIGIN"; }
Adjacent we deny web admission to some restricted folders and files:
location /media/customer/ { deny all; } location /media/downloadable/ { deny all; } location /media/import/ { deny all; } location ~ /media/theme_customization/.*\.xml$ { deny all; } location /errors/ { try_files $uri =404; } location ~ ^/errors/.*\.(xml|phtml)$ { deny all; } location ~ cron\.php { deny all; }
And the last scrap is where we load upward php-fpm and tell information technology to execute alphabetize.php whenever the user hits it:
location ~ (index|get|static|report|404|503)\.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_param PHP_FLAG "session.auto_start=off \northward suhosin.session.cryptua=off"; fastcgi_param PHP_VALUE "memory_limit=768M \due north max_execution_time=60"; fastcgi_read_timeout 60s; fastcgi_connect_timeout 60s; fastcgi_param MAGE_MODE $MAGE_MODE; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }
With that out of our mode, relieve the file, and then enable it by typing the following commands:
ln -due south /usr/local/etc/nginx/sites-available/magento2dev.conf \ /usr/local/etc/nginx/sites-enabled/magento2dev.conf sudo brew services restart nginx
How to Install Magento 2
Okay, at this point your auto meets Magento 2 requirements, missing only the creature itself. Head over to the Magento website and create an account if yous yet don't take one. After that, go to the download folio and download the latest version (two.1.5, at the time of writing):
Select the .tar.bz2 format and download it. And so proceed to extract it and prepare the correct binder and file permissions for Magento ii to be able to piece of work:
mkdir ~/www/magento2dev cd ~/www/magento2dev tar -xjf ~/Downloads/Magento-CE-2.1.5-2017-02-20-05-39-14.tar.bz2 discover var vendor pub/static pub/media app/etc -type f -exec chmod u+w {} \; find var vendor pub/static pub/media app/etc -type d -exec chmod u+west {} \; chmod u+10 bin/magento
Now, to install the database tables and create the needed configuration files, we volition run this command from the terminal:
./bin/magento setup:install --base-url=http://magento2.dev/ \ --db-host=127.0.0.ane --db-name=magento2 --db-user=root \ --db-password=123 --admin-firstname=Magento --admin-lastname=User \ --admin-e-mail=user@instance.com --admin-user=admin \ --admin-countersign=admin123 --linguistic communication=en_US --currency=USD \ --timezone=America/Chicago --use-rewrites=1 --backend-frontname=admin
Remember to change the database name (db-proper noun
), user (db-user
) and password (db-password
) to match the one you used during MySQL's installation, and that'due south it! This command will install all of Magento 2's modules, creating the required tables and configuration files. Later information technology is finished, open up up your browser and caput to http://magento2.dev/. Y'all should meet a Magento 2 clean install with the default Luma theme:
If you caput to http://magento2.dev/admin, you should come across the Admin awarding login folio:
So use the credentials beneath to login:
User: admin Password: admin123
We're finally gear up to start writing our code!
Creating Our Showtime Magento two Module
To complete our module, nosotros will have to create the following files, and I will guide you through the whole procedure. Nosotros will need:
- A few average registration files, to make Magento enlightened of our Web log module
- One interface file, to ascertain our data contract for the Post
- A Post Model, to represent a Post throughout our code, implementing the Post data interface
- A Mail service Resources Model, to link the Post Model to the database
- A Mail service Collection, to retrieve several posts at once from the database with the help of the Resource Model
- Two migration classes, to set our table schema and content
- Two Actions: 1 to list all posts and another to prove each post individually
- Ii each of Blocks, Views, and Layout files: One of each for the listing activeness, and one of each for the view
Kickoff, allow'southward have a quick look at the core source code binder structure, so nosotros tin define where to place our code. The fashion we installed has all of Magento 2 core code, together with all of its dependencies, living inside the composer'due south vendor
folder.
Registering Our Module
We will keep our lawmaking in a dissever folder, app/lawmaking
. Every module's proper noun is in the form Namespace_ModuleName
, and its location on the filesystem must reverberate that name, app/code/Namespace/ModuleName
for this instance. Following that blueprint, we will name our module Toptal_Blog
and place our files under app/code/Toptal/Blog
. Go ahead and create that binder structure.
Now, we demand to create a few boilerplate files in social club to take our module registered with Magento. Offset, create app/lawmaking/Toptal/Blog/composer.json
:
{}
This file will exist loaded by Composer everytime you run it. Even though we are non actually using Composer with our module, we must create it to proceed Composer happy.
Now we will register our module with Magento. Go ahead and create app/lawmaking/Toptal/Blog/registration.php
:
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Toptal_Blog', __DIR__ );
Here, we are calling the register
method of the ComponentRegistrar
class, sending ii parameters: the string 'module'
, which is the type of component nosotros are registering, and our module'due south name, 'Toptal_Blog'
. With that information, Magento's autoloader will be aware of our namespace and will know where to look for our classes and XML files.
Ane interesting affair to notice here is that we take the type of the component (MODULE
) beingness sent as a parameter to the \Magento\Framework\Component\ComponentRegistrar::annals
function. Non only can we register modules, we can register other kinds of components. For instance, themes, external libraries, and language packs are besides registered using this same method.
Continuing, let united states of america create our terminal registration file, app/code/Toptal/Blog/etc/module.xml
:
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-case" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> <module proper name="Toptal_Blog" setup_version="0.1.0"> <sequence> <module name="Magento_Directory" /> <module name="Magento_Config" /> </sequence> </module> </config>
This file holds some very of import information about our module. They are:
- The module proper name is present again, exposing our module name to the Magento configuration.
- The Magento setup version, which will be used by Magento to decide when to run database migration scripts.
- Our module's dependencies—Equally we are writing a simple module, we depend only on two Magento core modules:
Magento_Directory
andMagento_Config
.
Now, we have a module which should be recognizable by Magento two. Allow'southward check information technology by using the Magento 2 CLI.
First, nosotros need to disable Magento's cache. Magento's cache mechanisms deserve an article dedicated to themselves. For the time existence, as we are developing a module and want our changes to exist recognized by Magento instantly without the demand to clear the cache at all times, we volition only disable it. From the command line, run:
./bin/magento cache:disable
Then let's see if Magento is already enlightened of our modifications by looking at the modules' status. Simply run the following command:
./bin/magento module:status
The event from the last one should be similar to:
Our module is in that location, simply as the output shows, information technology is still disabled. To enable it, run:
./bin/magento module:enable Toptal_Blog
That should have washed it. To be sure, you can call module:status
again and await for our module's proper name in the enabled listing:
Handling Data Storage
Now that nosotros've enabled our module, we need to create the database table which holds our blog posts. This is the schema for the table we want to create:
Field | Type | Nada | Key | Default |
---|---|---|---|---|
post_id | int(10) unsigned | NO | PRI | Zippo |
title | text | NO | Zip | |
content | text | NO | Zip | |
created_at | timestamp | NO | CURRENT_TIMESTAMP |
We achieve this by creating the InstallSchema
form, which is responsible for managing the installation of our schema migration. The file is located at app/code/Toptal/Web log/Setup/InstallSchema.php
and has the post-obit content:
<?php namespace Toptal\Blog\Setup; use \Magento\Framework\Setup\InstallSchemaInterface; use \Magento\Framework\Setup\ModuleContextInterface; use \Magento\Framework\Setup\SchemaSetupInterface; use \Magento\Framework\DB\Ddl\Tabular array; /** * Grade InstallSchema * * @package Toptal\Web log\Setup */ class InstallSchema implements InstallSchemaInterface { /** * Install Blog Posts table * * @param SchemaSetupInterface $setup * @param ModuleContextInterface $context */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); $tableName = $setup->getTable('toptal_blog_post'); if ($setup->getConnection()->isTableExists($tableName) != true) { $table = $setup->getConnection() ->newTable($tableName) ->addColumn( 'post_id', Table::TYPE_INTEGER, null, [ 'identity' => truthful, 'unsigned' => truthful, 'nullable' => false, 'primary' => truthful ], 'ID' ) ->addColumn( 'title', Table::TYPE_TEXT, null, ['nullable' => fake], 'Title' ) ->addColumn( 'content', Table::TYPE_TEXT, null, ['nullable' => fake], 'Content' ) ->addColumn( 'created_at', Table::TYPE_TIMESTAMP, null, ['nullable' => false, 'default' => Table::TIMESTAMP_INIT], 'Created At' ) ->setComment('Toptal Blog - Posts'); $setup->getConnection()->createTable($table); } $setup->endSetup(); } }
If you analyze the install
method, yous will observe it just creates our table and adds its columns one by one.
To decide when to run a schema migration, Magento keeps a tabular array with all of the current setup versions for each module, and whenever a module version changes, its migration classes are initialized. This table is setup_module
, and if you lot accept a expect at that tabular array'south contents, you will see that there is no reference to our module so far. And then, let us change that. From a terminal, fire the following command:
./bin/magento setup:upgrade
That will show y'all a listing of all the modules and its migration scripts that were executed, including ours:
Now, from your MySQL client of preference, yous tin check if the table has really been created:
And at the setup_module
table, at present in that location's a reference to our module, its schema, and data version:
Ok, and what about schema upgrades? Let'south add together some posts to that table through an upgrade to show you lot how to do that. Beginning, bump the setup_version
on our etc/module.xml
file:
At present we create our app/lawmaking/Toptal/Blog/Setup/UpgradeData.php
file, which is responsible for the data (not schema) migrations:
<?php namespace Toptal\Blog\Setup; use \Magento\Framework\Setup\UpgradeDataInterface; utilise \Magento\Framework\Setup\ModuleContextInterface; use \Magento\Framework\Setup\ModuleDataSetupInterface; /** * Form UpgradeData * * @package Toptal\Blog\Setup */ class UpgradeData implements UpgradeDataInterface { /** * Creates sample blog posts * * @param ModuleDataSetupInterface $setup * @param ModuleContextInterface $context * @return void */ public part upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); if ($context->getVersion() && version_compare($context->getVersion(), '0.ane.1') < 0 ) { $tableName = $setup->getTable('toptal_blog_post'); $data = [ [ 'championship' => 'Postal service ane Title', 'content' => 'Content of the showtime post.', ], [ 'title' => 'Post 2 Championship', 'content' => 'Content of the second post.', ], ]; $setup ->getConnection() ->insertMultiple($tableName, $data); } $setup->endSetup(); } }
Y'all can run across that it is very like to our Install grade. The just difference is that it implements an UpgradeDataInterface
instead of InstallSchemaInterface
, and the chief method is chosen upgrade
. With this method, you check for the current module's installed version and, when smaller than yours, burn upwardly the changes you lot need to get done. In our example, we are checking if the current version is smaller than 0.1.1 in the following line using the version_compare
function:
if ($context->getVersion() && version_compare($context->getVersion(), '0.1.1') < 0 ) {
The $context->getVersion()
call will return 0.1.0 when the setup:upgrade
CLI command is called for the showtime time. Then the sample data is loaded to the database, and our version is bumped to 0.ane.1. To go this running, go ahead and burn a setup:upgrade
:
./bin/magento setup:upgrade
And so check the results at the posts table:
And at the setup_module
table:
Observe that, even though we added data to our table using the migration process, it would take been possible to change the schema as well. The process is the same; you would just use the UpgradeSchemaInterface
instead of the UpgradeDataInterface
.
Defining the Model for Posts
Moving on, if you remember our architecture overview, our next building block would exist the blog post ResourceModel. The Resource Model is very simple, and just states the table for which the Model will "connect" to, together with what its primary key is. We volition create our ResourceModel at app/lawmaking/Toptal/Blog/Model/ResourceModel/Mail.php
with the following contents:
<?php namespace Toptal\Blog\Model\ResourceModel; use \Magento\Framework\Model\ResourceModel\Db\AbstractDb; class Post extends AbstractDb { /** * Post Abstract Resource Constructor * @return void */ protected part _construct() { $this->_init('toptal_blog_post', 'post_id'); } }
All of the ResourceModel operations, unless you demand something unlike from the usual CRUD operations, are handled by the AbstractDb
parent class.
We volition besides need some other ResourceModel, a Collection. The Collection will exist responsible for querying the database for multiple posts using our ResourceModel and delivering back a series of Models instantiated and filled upwards with info. Nosotros create the file app/code/Toptal/Blog/Model/ResourceModel/Post/Drove.php
with the following content:
<?php namespace Toptal\Blog\Model\ResourceModel\Post; use \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; class Collection extends AbstractCollection { /** * Remittance File Drove Constructor * @return void */ protected role _construct() { $this->_init('Toptal\Blog\Model\Post', 'Toptal\Blog\Model\ResourceModel\Post'); } }
Notice that in the constructor we simply mention the Model, which volition correspond the post entity throughout our code, and the ResourceModel, which will fetch the info at the database.
The missing slice for this layer is the Post Model itself. The model should hold all the attributes we have divers in our schema, along with any business logic you might need. Following Magento 2's pattern, we demand to create a Data Interface that our model will extend from. We place the interface at app/lawmaking/Toptal/Blog/Api/Information/PostInterface.php
, and information technology should concur the table'southward fields names, along with the methods for accessing them:
<?php namespace Toptal\Weblog\Api\Information; interface PostInterface { /**#@+ * Constants for keys of data assortment. Identical to the proper name of the getter in snake case */ const POST_ID = 'post_id'; const TITLE = 'title'; const CONTENT = 'content'; const CREATED_AT = 'created_at'; /**#@-*/ /** * Get Championship * * @return cord|nada */ public function getTitle(); /** * Become Content * * @render string|zilch */ public office getContent(); /** * Become Created At * * @render cord|nothing */ public function getCreatedAt(); /** * Get ID * * @render int|null */ public function getId(); /** * Prepare Championship * * @param cord $title * @return $this */ public function setTitle($title); /** * Set Content * * @param string $content * @return $this */ public function setContent($content); /** * Set Crated At * * @param int $createdAt * @render $this */ public role setCreatedAt($createdAt); /** * Set ID * * @param int $id * @return $this */ public role setId($id); }
Now to the model's implementation, at app/lawmaking/Toptal/Blog/Model/Mail.php
. Nosotros volition create the methods defined at the interface. We will too specify a cache tag through the CACHE_TAG
abiding and, at the constructor, we volition specify the ResourceModel that will be responsible for the database access for our model.
<?php namespace Toptal\Blog\Model; apply \Magento\Framework\Model\AbstractModel; utilise \Magento\Framework\DataObject\IdentityInterface; utilize \Toptal\Blog\Api\Information\PostInterface; /** * Class File * @package Toptal\Blog\Model * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ grade Postal service extends AbstractModel implements PostInterface, IdentityInterface { /** * Enshroud tag */ const CACHE_TAG = 'toptal_blog_post'; /** * Mail service Initialization * @return void */ protected function _construct() { $this->_init('Toptal\Weblog\Model\ResourceModel\Post'); } /** * Become Title * * @render string|aught */ public function getTitle() { return $this->getData(cocky::Championship); } /** * Get Content * * @return cord|zip */ public function getContent() { return $this->getData(cocky::CONTENT); } /** * Get Created At * * @return string|null */ public function getCreatedAt() { return $this->getData(self::CREATED_AT); } /** * Get ID * * @return int|nada */ public role getId() { return $this->getData(self::POST_ID); } /** * Render identities * @return string[] */ public function getIdentities() { return [self::CACHE_TAG . '_' . $this->getId()]; } /** * Set Title * * @param string $title * @render $this */ public function setTitle($championship) { render $this->setData(self::TITLE, $title); } /** * Set Content * * @param string $content * @return $this */ public function setContent($content) { return $this->setData(self::CONTENT, $content); } /** * Set up Created At * * @param string $createdAt * @return $this */ public role setCreatedAt($createdAt) { render $this->setData(self::CREATED_AT, $createdAt); } /** * Set ID * * @param int $id * @return $this */ public part setId($id) { return $this->setData(cocky::POST_ID, $id); } }
Creating Views
Now we are moving one layer upward, and volition beginning the implementation of our ViewModel and Controller. To ascertain a route in the front-terminate (shopping cart) awarding, we need to create the file app/code/Toptal/Blog/etc/frontend/routes.xml
with the following contents:
<?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="standard"> <route id="weblog" frontName="weblog"> <module name="Toptal_Blog"/> </route> </router> </config>
Listing of Posts at the Index Page
Here, we are basically telling Magento that our module, Toptal_Blog
, will be responsible for responding to routes under http://magento2.dev/blog (notice the frontName aspect of the route). Next upwardly is the action, at app/lawmaking/Toptal/Blog/Controller/Index/Index.php
:
<?php namespace Toptal\Web log\Controller\Alphabetize; use \Magento\Framework\App\Activity\Action; use \Magento\Framework\View\Result\PageFactory; use \Magento\Framework\View\Consequence\Page; use \Magento\Framework\App\Action\Context; utilise \Magento\Framework\Exception\LocalizedException; form Index extends Action { /** * @var PageFactory */ protected $resultPageFactory; /** * @param Context $context * @param PageFactory $resultPageFactory * * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Context $context, PageFactory $resultPageFactory ) { parent::__construct( $context ); $this->resultPageFactory = $resultPageFactory; } /** * Prints the blog from informed order id * @return Page * @throws LocalizedException */ public part execute() { $resultPage = $this->resultPageFactory->create(); return $resultPage; } }
Our action is defining 2 methods. Allow us have a closer look at them:
-
The constructor method simply sends the
$context
parameter to its parent method, and sets the$resultPageFactory
parameter to an attribute for later on utilise. At this point it is useful to know the Dependency Injection blueprint pattern, as that is what is happening here. In Magento 2's case we take automated dependency injection. This means that whenever a grade instantiation occurs, Magento will automatically try to instantiate all of the class constructor parameters (dependencies) and inject it for you as constructor parameters. It identifies which classes to instantiate for each parameter by inspecting the type hints, in this caseContext
andPageFactory
. -
The
execute
method is responsible for the activeness execution itself. In our case, we are simply telling Magento to render its layout past returning aMagento\Framework\View\Result\Page
object. This will trigger the layout rendering procedure, which we will create in a fleck.
Now y'all should encounter a blank page at the url http://magento2.dev/blog/index/alphabetize. Nosotros withal need to define the layout structure for that route, and its corresponding Block (our ViewModel) and the template file which volition present the data to our user.
The layout structure for the front-finish awarding is divers under view/frontend/layout
, and the file proper noun must reverberate our route. As our road is blog/alphabetize/index
, the layout file for that route will be app/lawmaking/Toptal/Blog/view/frontend/layout/blog_index_index.xml
:
<?xml version="ane.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-example" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <torso> <referenceContainer name="content"> <block course="Toptal\Blog\Block\Posts" name="posts.list" template="Toptal_Blog::post/listing.phtml" /> </referenceContainer> </body> </page>
Here, nosotros must define three very important structures in the Magento layout structure: Blocks, Containers, and Templates.
-
Blocks are the ViewModel role of our MVVM compages, which was explained in before sections. They are the building blocks of our template structure.
-
Containers contain and output Blocks. They hold blocks together in nice hierarchical structures, and help in making things make sense when the layout for a page is being processed.
-
Templates are PHMTL (mixed HTML and PHP) files used by a special blazon of block in Magento. You can make calls to methods of a
$block
variable from inside a template. The variable is always defined in the template context. You volition be invoking your Block's methods by doing and so, and thus allowing you to pull information from the ViewModel layer to the actual presentation.
With that extra data at mitt, we tin clarify the XML layout structure in a higher place. This layout structure is basically telling Magento that, when a asking is made to the web log/alphabetize/index
road, a Cake of the type Toptal\Blog\Block\Posts
is to exist added to the content
container, and the template which volition be used to render it is Toptal_blog::post/listing.phtml
.
This leads the states to the creation of our ii remaining files. Our Cake, located at app/code/Toptal/Weblog/Block/Posts.php
:
<?php namespace Toptal\Blog\Block; use \Magento\Framework\View\Element\Template; use \Magento\Framework\View\Chemical element\Template\Context; utilise \Toptal\Blog\Model\ResourceModel\Postal service\Collection as PostCollection; use \Toptal\Weblog\Model\ResourceModel\Post\CollectionFactory equally PostCollectionFactory; apply \Toptal\Weblog\Model\Post; class Posts extends Template { /** * CollectionFactory * @var null|CollectionFactory */ protected $_postCollectionFactory = zip; /** * Constructor * * @param Context $context * @param PostCollectionFactory $postCollectionFactory * @param array $data */ public part __construct( Context $context, PostCollectionFactory $postCollectionFactory, array $data = [] ) { $this->_postCollectionFactory = $postCollectionFactory; parent::__construct($context, $data); } /** * @return Post[] */ public function getPosts() { /** @var PostCollection $postCollection */ $postCollection = $this->_postCollectionFactory->create(); $postCollection->addFieldToSelect('*')->load(); return $postCollection->getItems(); } /** * For a given post, returns its url * @param Post $post * @return string */ public function getPostUrl( Post $post ) { return '/blog/post/view/id/' . $post->getId(); } }
This class is rather simple, and its objective is solely to load the posts to be shown, and provide a getPostUrl
method to the template. There are some things to observe though.
If you recall, nosotros have not defined a Toptal\Blog\Model\ResourceModel\Post\CollectionFactory
class. Nosotros simply divers the Toptal\Blog\Model\ResourceModel\Post\Collection
. And then how is this fifty-fifty working? For every course you define in your module, Magento 2 volition automatically create a Mill for y'all. Factories have two methods: create
, which will return a new instance for each telephone call, and become
, which volition always return the same instance whenever called—used to implement the Singleton pattern.
The third parameter of our Block, $information
, is an optional array. Every bit it is optional and does non have a type hint, it will non be injected by the automatic injection arrangement. Information technology is important to notice that optional constructor parameters must always be positioned last in the parameters. For example, the constructor of the Magento\Framework\View\Chemical element\Template
, our parent class, has these parameters:
public office __construct( Template\Context $context, array $data = [] ) { ...
As we wanted to add our CollectionFactory
to the constructor parameters afterwards extending the Template course, we had to do it before the optional parameter, otherwise the injection would not work:
public part __construct( Context $context, PostCollectionFactory $postCollectionFactory, array $information = [] ) { ...
At the getPosts
method, which will be accessed subsequently by our template, we simply telephone call the create
method from the PostCollectionFactory
, which will return us a fresh PostCollection
and allow us to fetch our posts from the database and send it to our response.
And to finish this route'southward layout, here is our our PHTML template, app/code/Toptal/Web log/view/frontend/templates/post/list.phtml
:
<?php /** @var Toptal\Weblog\Block\Posts $block */ ?> <h1>Toptal Posts</h1> <?php foreach($block->getPosts() as $post): ?> <?php /** @var Toptal\Blog\Model\Post */ ?> <h2><a href="<?php echo $block->getPostUrl($mail);?>"><?php echo $post->getTitle(); ?></a></h2> <p><?php repeat $post->getContent(); ?></p> <?php endforeach; ?>
Notice that here we tin can meet the View layer accessing our ModelView ($block->getPosts()
) which in turn uses a ResourceModel (the drove) to fetch our models (Toptal\Weblog\Model\Mail
) from the database. In every template, whenever you desire to admission its block's methods, in that location will exist a $cake
variable divers and waiting for your calls.
Now yous should be able to see the posts list past simply hitting our road again.
Viewing Individual Posts
Now, if you lot click on a post title, you will get a 404, and then let'southward prepare that. With all of our construction in identify, this becomes quite simple. We will demand but to create the following:
- A new action, responsible for handling requests to the
blog/mail/view
route - A Block to render the post
- A PHTML template, responsible for the view itself
- A layout file for the blog/postal service/view road, putting these last pieces together.
Our new action is quite elementary. It will simply receive the parameter id
from the asking and register it at Magento cadre registry, a central repository for data that is available throughout a single asking cycle. By doing this, we will brand the ID available to the block later on. The file should be located at app/code/Toptal/Weblog/Controller/Postal service/View.php
and these are its contents:
<?php namespace Toptal\Blog\Controller\Post; utilize \Magento\Framework\App\Action\Activeness; use \Magento\Framework\View\Consequence\PageFactory; apply \Magento\Framework\View\Result\Page; use \Magento\Framework\App\Action\Context; apply \Magento\Framework\Exception\LocalizedException; use \Magento\Framework\Registry; course View extends Action { const REGISTRY_KEY_POST_ID = 'toptal_blog_post_id'; /** * Cadre registry * @var Registry */ protected $_coreRegistry; /** * @var PageFactory */ protected $_resultPageFactory; /** * @param Context $context * @param Registry $coreRegistry * @param PageFactory $resultPageFactory * * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Context $context, Registry $coreRegistry, PageFactory $resultPageFactory ) { parent::__construct( $context ); $this->_coreRegistry = $coreRegistry; $this->_resultPageFactory = $resultPageFactory; } /** * Saves the weblog id to the register and renders the page * @return Folio * @throws LocalizedException */ public function execute() { $this->_coreRegistry->register(self::REGISTRY_KEY_POST_ID, (int) $this->_request->getParam('id')); $resultPage = $this->_resultPageFactory->create(); render $resultPage; } }
Notice that we accept added the $coreRegistry
parameter to our __construct
, and saved information technology every bit an attribute for later use. At the execute
method, we retrieve the id
parameter from the request, and annals it. We alse use a form constant, cocky::REGISTRY_KEY_POST_ID
as a key to the register, and we volition use this aforementioned abiding at our cake to refer to the id in the registry.
Permit u.s.a. create the block, at app/code/Toptal/Blog/Block/View.php
with the following contents:
<?php namespace Toptal\Blog\Block; utilize \Magento\Framework\Exception\LocalizedException; utilise \Magento\Framework\View\Chemical element\Template; utilize \Magento\Framework\View\Element\Template\Context; use \Magento\Framework\Registry; use \Toptal\Blog\Model\Mail; use \Toptal\Web log\Model\PostFactory; apply \Toptal\Web log\Controller\Mail\View as ViewAction; form View extends Template { /** * Core registry * @var Registry */ protected $_coreRegistry; /** * Post * @var null|Post */ protected $_post = cipher; /** * PostFactory * @var null|PostFactory */ protected $_postFactory = null; /** * Constructor * @param Context $context * @param Registry $coreRegistry * @param PostFactory $postCollectionFactory * @param array $data */ public part __construct( Context $context, Registry $coreRegistry, PostFactory $postFactory, array $information = [] ) { $this->_postFactory = $postFactory; $this->_coreRegistry = $coreRegistry; parent::__construct($context, $information); } /** * Lazy loads the requested post * @return Post * @throws LocalizedException */ public function getPost() { if ($this->_post === cypher) { /** @var Mail $post */ $postal service = $this->_postFactory->create(); $post->load($this->_getPostId()); if (!$post->getId()) { throw new LocalizedException(__('Mail service not found')); } $this->_post = $post; } return $this->_post; } /** * Retrieves the post id from the registry * @render int */ protected function _getPostId() { return (int) $this->_coreRegistry->registry( ViewAction::REGISTRY_KEY_POST_ID ); } }
At the view block, we define a protected method _getPostId
, which will simply retrieve the mail service ID from the core registry. The public getPost
method will in turn lazy load the post and throw an exception if the post does not exist. Throwing an exception here will brand Magento testify its default fault screen, which might not be the best solution in such a case, but nosotros will continue it this fashion for the sake of simplicity.
On to our PHTML template. Add app/lawmaking/Toptal/Weblog/view/frontend/templates/mail service/view.phtml
with the post-obit contents:
<?php /** @var Toptal\Blog\Block\View $block */ ?> <h1><?php echo $cake->getPost()->getTitle(); ?></h1> <p><?php echo $block->getPost()->getContent(); ?></p>
Nice and simple, just accessing the View cake getPost
method we created earlier.
And, to put information technology all together, we create a layout file for our new route at app/code/Toptal/Blog/view/frontend/layout/blog_post_view.xml
with the post-obit content:
<?xml version="ane.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block course="Toptal\Web log\Cake\View" name="mail.view" template="Toptal_Blog::post/view.phtml" /> </referenceContainer> </body> </page>
This does the same thing nosotros did before. It just adds Toptal\Blog\Cake\View
to the content
container, with Toptal_Blog::mail service/view.phtml
equally the associated template.
To see information technology in action, simply direct your browser to http://magento2.dev/web log/mail/view/id/i to successfully load a post. Y'all should see a screen such equally the ane below:
And as you tin can see, subsequently creating our initial structure, information technology is actually simple to add features to the platform, and well-nigh of our initial lawmaking is reused in the procedure.
In case you desire to chop-chop test the module, here is the total result of our work.
Where to Go from Here
If yous have followed me up until here, congratulations! I am positive yous are quite close to becoming a Magento 2 programmer. Nosotros have developed a pretty advanced Magento ii custom module, and fifty-fifty though it is simple in its features, a lot of ground has been covered.
Some things were left out from this article, for the sake of simplicity. To proper name a few:
- Admin edit forms and grids to manage our web log content
- Blogs categories, tags and comments
- Repositories and a few service contracts we could have established
- Packaging modules up as Magento 2 extensions
In whatsoever case, here are some useful links where you can deepen your knowledge fifty-fifty more:
- Alan Storm Blog on Magento 2 — Alan Storm has probably the about didactic content when it comes to learning Magento.
- Alan Kent's Weblog
- Magento documentation: The Magento two Dev Docs
I provided you with a comprehensive introduction to all relevant aspects of how to create a module in Magento ii, and a few additional resources should you need them. Now it's up to yous to get coding, or head down to the comments if y'all'd similar to weigh in.
Source: https://www.toptal.com/magento/magento-2-tutorial-building-a-complete-module
0 Response to "Magento 2.1 Uploading Ico Image and Png"
Post a Comment