Comparison of the capabilities of the OpenCart and Laravel frameworks.

Sorry for my English! English is not my native language. One of the reasons to create this blog is to improve my English writing. So I will be highly obliged if you will help me with this. If you find a grammar error on this page, please select it with your mouse and press Ctrl+Enter.

The name of the topic is very ambiguous, as many may say that these are completely different things, which make no sense to compare. But many OpenCart developers consider OpenCart to be a framework, some even write that it is not just a framework, but the best framework on which you can make a website of any complexity. Therefore, I decided to compare the functionality of frameworks.

Why Laravel?

Today it is probably the most popular PHP framework. It is many times (and even dozens) more popular than OpenCart itself.
For example: https://trends.google.com.ua/trends/explore?date=today%205-y&q=%2Fm%2F0jwy148,%2Fg%2F11b5m9hl85&hl=uk
over the past 5 years, if the popularity of OpenCart has fallen by more than 2 times, then the popularity of Laravel, on the contrary, has grown by 30%.

This is a framework on which everything is written - from all kinds of APIs, serverless solutions, SPA, backend for mobile applications, bots for messengers to large and complex sites, online stores and marketplaces.
It is simple, fast, functional, safe and what is very important - it is very actively supported by the community and is being developed, a new, 10 version of the framework has just been released, just a year ago - 9. There are a huge number of free extensions, there is already a ready-made package for almost any task.
It works great with both regular css libraries like bootstrap and reactive frameworks like React and Vue.

I recently began to study this framework more seriously, rather as a sporting interest and broadening my horizons, since lately I have been constantly hearing about it from very different sources.
And I was very pleasantly surprised by its beauty, convenience and functionality. And also how radically it differs from opencart in terms of code and work with it for programmers.

Almost everything that can be automated is automated here, many actions are done in general in 1-2 lines of code.

Therefore, I decided to write this post, and at the same time compare the functionality with OpenCart a bit and even show code examples.

And so, some features of Laravel that I liked the most:

Here, all PHP features are fully revealed - classes, interfaces, inheritance, traits, Enums, SOLID, design patterns (facades are everywhere, DI, Factory ..), etc.
Working with the framework, you really begin to understand the beauty of the right approach, when in one line of code you can change the logic of the entire application if everything is designed correctly. And not like in opencart, when in order to change some little thing, you need to change hundreds of lines of code in dozens of files, which is done all the time, just look at how opencart is made, for example. pagination or form validation or link generation or database manipulation..
To not be unfounded. If you read the wording of the 5th principle of SOLID - dependency inversion, then there will be this:

  • A. Upper level modules should not depend on lower level modules. Both types of modules must depend on abstractions.
  • B. Abstractions should not depend on details. Details should depend on abstractions.

Most likely, if you are reading this for the first time, then the only thing you will understand from this is that you did not understand anything))
At the same time, when you start working with Laravel, then quite often methods receive in parameters not some specific objects and therefore depend on specific implementations of these objects, but interfaces, that is, these methods depend on abstractions. What does it give? In the Laravel settings, you can specify which specific class to use for a given interface, you can even do it through a condition, for example, in development mode, use one class, which, for example. does not send data anywhere, but only writes to the log, and use another one in production.

At the same time, we do not change the logic of the method itself in any way, it does not matter at all what object was passed to it, the main thing is that it be an object of the desired interface. And the application turns out to be very flexible and without a bunch of unnecessary conditions, checks and code duplication. Changes are made by changing one line of code, and we already have the whole application working according to a completely different logic.

Console Commands

php artisan - a script with which you can create a huge amount of code and run typical actions through 1 command.
You can create controllers, models, migrations, events, rules, resources, requests, work with the database, queues, scheduler, views, environments and much more.
You can also create your own custom console commands or change the logic of the existing ones.
That is, you need to create something, you do not write all the code manually, instead enter 1 command in the console, for example.
php artisan make:model ProductModel (+ various options if needed)
and immediately create a new model for the product along with a factory, migration, seed, resource controllers (in which all the necessary methods for CRUD will already be created), as well as separate request classes.
How much time does it save? It is especially worth considering that if you write so much code manually, then you will make a mistake somewhere and then spend a lot of time to understand why it does not work.
Plus, we also get the same type of structure for all projects, which greatly simplifies the work.

Very cool work with the base

There is a separate builder for creating queries to the database and you do not need to manually write a bunch of SQL queries for a bunch of the same actions - getting, changing, deleting records in the database.
There is, of course, an ORM.
You just create a new model
php artisan make:model ProductModel
It will be almost an empty class without a single line of SQL code, but in the controller you can already fully work with the database, for example

$products = Product::all();

will receive all the goods, you can also add various conditions, limits, sorting, links, etc.
Not only do you save a lot of time, but also all queries to the database are automatically protected from all kinds of SQL injections and various errors.

Tables in the database are created and modified using migrations - a special class for changing a table, with which you can both add changes and roll them back.
for example migration for post table:

 Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content');
            $table->unsignedBigInteger('category_id')->nullable();
            $table->timestamps();
 
            $table->index('category_id', 'post_category_idx');
            $table->foreign('category_id', 'post_category_fk')->on('categories')->references('id');
 
        });

You can use seeds - classes to fill tables with some data.
Seeds can be implemented through factories - a class for automatically creating a record, there is also a Faker class for creating fake data for fields - text, numbers, various dates, codes, pictures, phone numbers,
emails, etc. you can very easily fill the site with test data for testing, in a couple of seconds create at least 1000 posts with a title, description, picture, etc. Or a bunch of users with emails, passwords, etc.

That is, how the standard work happens - you copy some project on Laravel,
With one command you create all the necessary tables for work
With another command, fill in these tables with the data necessary for work.

The most simple controllers (which, in fact, should be according to the MVC standard, and not what we now see in opencart)

example controller method to get post

   public function show(Post $post)
    {
        return view('post.show', compact('post'));
    }

Pay attention, a very cool feature of Laravel - if the page address contains a post id (for example, /posts/123), then we can get not this identifier as an argument of the controller method so that we can manually create this post later, and the framework will create it for us this post and we immediately receive the object of this post as an argument, it remains to pass it to the view and that's it. If the post is not found, the framework will return the 404 page. It seems like a trifle, but there are dozens of such trifles that greatly speed up development.

or controller to create posts

   public function store(StoreRequest $request)
    {
        $data = $request->validated();
 
        $this->service->store($data);
 
        return redirect()->route('admin.post.index');
    }

StoreRequest - a separate request for this method, again we get a ready-made object as an argument and we do not need to create it manually ourselves, it contains the rules for request validation

    public function rules()
    {
        return [
            'title' => 'required|string',
            'content' => 'required|string',
            'image' => 'required|file', // можно также указать что это также картинка, определенного типа и размеров.
            'category_id' => 'required|integer|exists:categories,id', // тут сразу проверяется наличие этого идентификатора базе в таблице категорий, чтобы не создать пост с несуществующей категорией.
            'tag_ids' => 'nullable|array',
            'tag_ids.*' => 'nullable|integer|exists:tags,id',
        ];
    }


that is, you do not need to manually write a bunch of rules for each field that you receive through a bunch of if structures, process these rules, manually create and return validation errors if these rules do not work, or even write sql queries if you need to check unique entries in the database No data. An array is simply created with all the rules (dozens of validation rules with error texts are already available out of the box, but you can create your own) and that's it.

further into

$data = $request->validated();

will get an array of all already checked data.
If the validation fails, then the framework itself returns the user to the old page and returns all validation errors
all you need to do is write it in the template below the field

@error('title')
  <div class="text-danger">{{ $message }}</div>
@enderror

and the framework itself will display the necessary errors for all the rules for each field, errors can also be configured and redefined.

further down the code

$this->service->store($data);

all the logic from the controller is moved to a separate service to create all the data, to improve the readability of the code and not to bloat the controllers.
the method itself is something like this:

   public function store($data) {
        try {
            // старт транзакции, так как есть работа с несколькими таблицами
            DB::beginTransaction();
 
            $data['image'] = Storage::put('/images', $data['image']);
 
            $post = Post::сreate($data);
 
            if (isset($tagIds)) {
                $post->tags()->attach($tagIds);
            }
 
            DB::commit();
        } catch (\Throwable $th) {
            // откат если произошла какая-то ошибка
            DB::rollBack();
        }
    }

Here you can see the work with transactions, which are not available at all in OpenCart and in vain, because when saving, for example. product, not one product table is involved, but a dozen others - options, attributes, promotions, discounts, pictures, links, etc.

And if an error occurs somewhere, then invalid data will be written to the database, which will then be extremely difficult to find. I quite often encounter a situation where there is invalid data in the opencart database, especially after using various import modules.

It's very easy to work with files.

You can specify several file storages in the settings (local, ftp, cloud storages) and easily switch between them or upload some data locally, some to the cloud, etc.

Post::сreate($data);

will create a new post

through the attach method, new tags will be added to this post, because the link of this post with tags is registered in the post model

    public function tags() {
        return $this->belongsToMany(Tag::class, 'post_tags', 'post_id', 'tag_id');
    }

This relationship gives us the ability to both automatically get post tags (via $post->tags) and change them.

In doing so, we do not write a single line of SQL code! All code with all connections, checks, etc. wrote the framework for us. And this not only greatly speeds up the entire development process, saving hundreds of hours of time, but also insures against a bunch of possible errors, including security errors, for example, you can not process some data when added to the database and get a potential security error.

I liked such a feature as safe deletion

if in the model just add a trait

use SoftDeletes;

then when deleting records, they will not be physically deleted, but simply marked as deleted, you will not see them on the site, since almost all methods of working with the database do not see these records, but they will be in the database and they can be easily restored if necessary.

What else, for example - pagination

If you need to add pagination, then in the controller, when receiving posts, do

$posts = Post::paginate(10);

and in the template
add just one line of code

{{ $posts->links() }}

and that's it, we get a working pagination, stylized for example. under the bootstrap, which itself creates all the links.
Now compare how much code you need to write in opencart in order to add the same pagination for each page .. that's a bunch of lines of code in each! controller.

    $url = '';
 
    if (isset($this->request->get['filter_name'])) {
      $url .= '&filter_name=' . urlencode(html_entity_decode($this->request->get['filter_name'], ENT_QUOTES, 'UTF-8'));
    }
 
    if (isset($this->request->get['filter_model'])) {
      $url .= '&filter_model=' . urlencode(html_entity_decode($this->request->get['filter_model'], ENT_QUOTES, 'UTF-8'));
    }
 
    if (isset($this->request->get['filter_price'])) {
      $url .= '&filter_price=' . $this->request->get['filter_price'];
    }
 
    if (isset($this->request->get['filter_quantity'])) {
      $url .= '&filter_quantity=' . $this->request->get['filter_quantity'];
    }
 
    if (isset($this->request->get['filter_status'])) {
      $url .= '&filter_status=' . $this->request->get['filter_status'];
    }
 
    if (isset($this->request->get['filter_image'])) {
      $url .= '&filter_image=' . $this->request->get['filter_image'];
    }
 
    if (isset($this->request->get['sort'])) {
      $url .= '&sort=' . $this->request->get['sort'];
    }
 
    if (isset($this->request->get['order'])) {
      $url .= '&order=' . $this->request->get['order'];
    }
 
    $pagination = new Pagination();
    $pagination->total = $product_total; // для этого нужно эту переменную еще вручную достать из базы, создав для этого отдельный метод с кучей sql кода и передать в контроллер. 
    $pagination->page = $page;
    $pagination->limit = $this->config->get('config_limit_admin');
    $pagination->url = $this->url->link('catalog/product', 'token=' . $this->session->data['token'] . $url . '&page={page}', true);
 
    $data['pagination'] = $pagination->render();


Great concept - queues and tasks.

If there are any actions, eg. sending mail or some calculations for which you need to contact third-party services, then instead of waiting a long time each time until the third-party server returns the result, you can create a new Job and add it to the Queue after that all actions in the queue will be quietly processed in background and nothing on the site will slow down while waiting for a response from the server.

Another great concept is Middleware

This is the code that is executed before or after the request.
It is very convenient to make various restrictions on access to some pages (for example, to allow access to the page only to an authorized user or admin, etc.) or to process incoming data, for example, spaces at the beginning and end are displayed by default for all input lines. In this case, all the logic is not written in one heap in the controller through a bunch of conditions, but lies in a separate class.
Through this mechanism, CSRF protection (cross-site request forgery) is also made
all you have to do is write in the form template
@csrf
and that's it! the framework itself will create the necessary tokens, pass them to the form, display the necessary input, and at the input it will check all requests (except get) whether there is a token there, whether it matches, if it does not match, it will return with an error and will not allow such a form to go further.
This mechanism works for all forms, that is, out of the box there is this protection for all forms. And there is no such protection at all in OpenCart.
Through the Middleware, throttling is also implemented out of the box, for the API you can specify how many requests, for example. per minute the server should process and no more.

Well, and so on, what I described above is a very small part of the capabilities of the framework, just for an example. There are many more possibilities.
I just wanted to show how everything is thought out to the smallest detail here, when the main basic actions are automated so much that they are done literally in 1-2 lines of code.
And how it contrasts with opencart, where for any action you need to write dozens of lines of code, and even duplicate it in dozens of controllers.
A clear example of a comparison when in one case a product is made by programmers for programmers and when in another case someone writes code without thinking at all about who then and how they will work with this code in the future ..
Perhaps that is why the popularity of Laravel has been growing over the past 5 years, which unfortunately cannot be said about OpenCart .. 

Add new comment

CAPTCHA
Spam protection
Target Image