Build E-Commerce System in Seconds With TomatoPHP

https://media.dev.to/cdn-cgi/image/width=1000,height=500,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F19c0mf70jkiwwgaqv7x9.png

hi, community.

Introducing #tomatophp, a cutting-edge open-source Laravel package designed to streamline development within the VILT stack environment. This innovative ecosystem leverages the power of Splade to effortlessly generate modern, high-performance single-page applications (SPAs) using only Blade files. #tomatophp redefines the development experience, making it both efficient and enjoyable.

As we progress, we continuously enhance our ecosystem by incorporating additional features into our plugins. Presently, we have developed a comprehensive e-commerce system using these plugins. In this article, we will guide you through the implementation process, demonstrating how you can seamlessly integrate a robust e-commerce system into your application.



Install Tomato Admin

you need to install tomato-admin plugin on your fresh Laravel app so let’s create a new Laravel app.

composer create-project laravel/laravel tomato

if you don’t have an environment for Laravel you can use this doc to build one on your Ubuntu Linux.

now cd inside your project folder change .env of your database and make sure that your Laravel app is running and the database is connected, you can check that by running migrations

php artisan migrate

now let’s start installing tomato-admin

composer require tomatophp/tomato-admin

after the composer is done run this command for auto-install

php artisan tomato-admin:install

if you are using macOS you can easily use auto yarn package install if not just build your assets like this

yarn & yarn build

now you have tomato-admin installed on your Laravel project.

we will use some media on our package to we need to publish Spatie Media Library migrations

php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"

if this command does not work please use this command

Image description

php artisan vendor:publish

then on the search type media and select the migration one.

now migrate your files

php artisan migrate

you can check your browser now and you will see a homepage like this

Image description

we need to change HOME const on the RouteServiceProvider.php to be /admin to make the redirect after auth to admin.



Install Tomato Roles

No e-commerce system is complete without a robust role management structure. To fulfill this essential requirement, we’ll be installing ‘tomato-roles’ to seamlessly handle roles within our mission to build a comprehensive e-commerce system.

composer require tomatophp/tomato-roles

after the composer is done run this command

php artisan tomato-roles:install

now go to your app\Models\User.php and add this trait to it

use \Spatie\Permission\Traits\HasRoles;

now your dashboard is ready to log in using admin@admin.com and password as a password from this URL /admin/login

Image description

if you try to access any page you will be redirected to Two-factor Confirmation If you don’t go it for now, you can easily stop it by removing implements MustVerifyEmail from your User.php model.



Install Tomato CRM

As an integral part of our e-commerce system, the management of customer interactions, authentications, and other crucial actions is paramount. To efficiently handle these aspects, we’ll be installing ‘tomato-crm.’ Let’s proceed with the installation to empower our system with advanced customer relationship management capabilities.

composer require tomatophp/tomato-crm

now let’s install it

php artisan tomato-crm:install

let’s publish Accounts.php model to our app to custom it

php artisan vendor:publish --tag="tomato-crm-model"

and you need to publish tomato-crm config

php artisan vendor:publish --tag="tomato-crm-config"

on tomato-crm.php config change the model path to like this

"model" => \App\Models\Account::class,

now we need to add a new guard to our app, so let’s add it on the config auth.php like this.

<?php

return [

    /*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    /*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session"
|
*/

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'accounts' => [
            'driver' => 'session',
            'provider' => 'accounts',
        ]
    ],

    /*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
        'accounts' => [
            'driver' => 'eloquent',
            'model' => App\Models\Account::class,
        ],
    ],

    /*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_reset_tokens',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    /*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| times out and the user is prompted to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/

    'password_timeout' => 10800,

];

now clear your config cache

php artisan config:clear

now rebuild your assets

yarn & yarn build

now your CRM is ready you can check it on your dashboard



Install Tomato Wallet

For seamless transaction management between customers and vendors within the e-commerce system, a robust payment handler is crucial. Introducing ‘tomato-wallet,’ a feature-rich package that not only manages customer wallets but also seamlessly handles payments. Packed with a multitude of integrated payment gateways, ‘tomato-wallet’ ensures a magical experience in managing transactions. Let’s proceed with the installation to unlock the full potential of this powerful payment solution.

composer require tomatophp/tomato-wallet

let’s install it

php artisan tomato-wallet:install

now we need to implement Wallet interface to our Account.php and add HasWallet trait to it to make the wallet of the customer work, your Account model must be like this

<?php

namespace App\Models;

use Bavix\Wallet\Interfaces\Wallet;
use Bavix\Wallet\Traits\HasWallet;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Macroable\Macroable;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\Permission\Traits\HasRoles;
use TomatoPHP\TomatoCrm\Models\Group;

/**
* @property integer $id
* @property string $name
* @property string $username
* @property string $loginBy
* @property string $address
* @property string $type
* @property string $password
* @property string $otp_code
* @property string $otp_activated_at
* @property string $last_login
* @property string $agent
* @property string $host
* @property integer $attempts
* @property boolean $login
* @property boolean $activated
* @property boolean $blocked
* @property string $deleted_at
* @property string $created_at
* @property string $updated_at
* @property AccountsMeta[] $accountsMetas
* @property Activity[] $activities
* @property Comment[] $comments
* @property Model meta($key, $value)
* @property Location[] $locations
*/
class Account extends Authenticatable implements HasMedia, Wallet
{
    use InteractsWithMedia;
    use HasApiTokens, HasFactory, Notifiable;
    use HasWallet;

    /**
* @var array
*/
    protected $fillable = [
        'email',
        'phone',
        'parent_id',
        'type',
        'name',
        'username',
        'loginBy',
        'address',
        'password',
        'otp_code',
        'otp_activated_at',
        'last_login',
        'agent',
        'host',
        'is_login',
        'is_active',
        'deleted_at',
        'created_at',
        'updated_at'
    ];

    protected $casts = [
        'is_login' => 'boolean',
        'is_active' => 'boolean'
    ];
    protected $dates = [
        'deleted_at',
        'created_at',
        'updated_at',
        'otp_activated_at',
        'last_login',
    ];


    protected $appends = [
        'birthday',
        'gender',
        'more'
    ];

    public function getMoreAttribute()
    {
        $metas = $this->accountsMetas()->get()->pluck('value', 'key')->toArray();
        return $metas;
    }

    public function getBirthdayAttribute()
    {
        return $this->meta('birthday') ?: null;
    }

    public function getGenderAttribute()
    {
        return $this->meta('gender') ?: null;
    }

    /**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
    public function accountsMetas()
    {
        return $this->hasMany('TomatoPHP\TomatoCrm\Models\AccountsMeta');
    }

    /**
* @param string $key
* @param string|null $value
* @return Model|string
*/
    public function meta(string $key, string|null $value=null): Model|string|null
    {
        if($value){
            return $this->accountsMetas()->updateOrCreate(['key' => $key], ['value' => $value]);
        }
        else {
            return $this->accountsMetas()->where('key', $key)->first()?->value;
        }
    }
    /**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
    public function activities()
    {
        return $this->hasMany('TomatoPHP\TomatoCrm\Models\Activity');
    }

    /**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
    public function comments()
    {
        return $this->hasMany('TomatoPHP\TomatoCrm\Models\Comment');
    }

    /**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
    public function locations()
    {
        return $this->hasMany('TomatoPHP\TomatoCrm\Models\Location');
    }

    public function groups(){
        return $this->belongsToMany(Group::class, 'account_groups', 'account_id', 'group_id');
    }
}

now your wallet is working you can start any transaction and check the customer’s balance.



Install Tomato CMS

In any functioning e-commerce setup, a well-optimized front end is indispensable. To enhance SEO performance and add essential content such as posts and pages, we’ll be incorporating our ‘tomato-cms’ package. Let’s proceed with the installation to seamlessly integrate this package and elevate our e-commerce platform.

composer require tomatophp/tomato-cms

now let’s install it

php artisan tomato-cms:install



Install Tomato Themes

To craft a dynamic frontend tailored as a theme, we’ll be utilizing the ‘tomato-themes’ package. This package simplifies the implementation of multi-themes for your project, employing a Hierarchical Model-View-Controller (HMVC) architecture. Let’s initiate the installation process for ‘tomato-themes’ to facilitate the seamless integration of diverse themes into your project.

composer require tomatophp/tomato-themes

now let’s install it

php artisan tomato-themes:install

now rebuild your assets

yarn & yarn build

make sure that you have Themes folder in your project root and add this line to your composer.json

 "autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Themes\\": "Themes/"
}
},

now you need to reload the composer

composer dump-autoload

now inside your tailwind.config.js add these lines

content: [
    ...
    "./Themes/**/*.blade.php",
    "./Themes/**/**/*.blade.php",
],

now your theme is ready to upload or create a new theme.



Install Tomato E-Commerce

Now that we have our foundational elements in place, including a ready CRM, role management, and several essential packages, it’s time to bring it all together by installing the E-Commerce System. The seamless integration of these components promises a robust and feature-rich platform. Let’s proceed with the installation to witness the culmination of our efforts in building a comprehensive and efficient E-Commerce System.

composer require tomatophp/tomato-ecommerce

let’s install it

php artisan tomato-ecommerce:install

now everything i ready to install our e-commerce theme.

this package will install tomato-products, tomato-orders, tomato-offers, tomato-branches for you

to make everything work fine you need some actions, we need to install tomato-branches

php artisan tomato-branches:install

after that, you need to create just 1 Shipping Vendor from this endpoint /admin/shipping-vendors

now you need to change your site SEO data and your site Logo from this endpoint /admin/settings/seo upload your logos and change the Site Name.



Install E-commerce Theme

Having established a solid foundation, including CRM, role management, and various essential packages, we’re now ready to enhance the visual appeal of our E-Commerce and CMS platforms. Introducing our user-friendly theme with a simple yet stylish design – an ideal canvas for customization. Let’s embark on the installation process to seamlessly integrate this theme and provide users the flexibility to tailor it to their preferences.

inside your Themes Folder clone this repo

cd Themes

now clone our Theme.

git clone git@github.com:tomatophp/Ecommerce.git

now go to your dashboard /themes and you will get the new theme you can just activate it.

Please note that you must not have / route on your main routes/web.php because these routes can override the Theme Routes.

to fix style rebuild your assets

yarn & yarn build

now if you check your home page you will get something like this

Image description

you can select from the top dropdown any section and add it to your page.

and you can build a menu from /admin/menus using this endpoint

  • Home /
  • About /about
  • Shop /shop
  • Blog /blog
  • Contact /contact
  • Terms & Conditions /terms
  • Privacy /privacy

you can create 2 menus main, footer the main will show up auto to your header and the footer to your footer.

change the middleware of Authenticate redirect to this route

<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;

class Authenticate extends Middleware
{
    /**
* Get the path the user should be redirected to when they are not authenticated.
*/
    protected function redirectTo(Request $request): ?string
    {
        return $request->expectsJson() ? null : route('accounts.login');
    }
}

now in your Account.php model add this trait

    use \TomatoPHP\TomatoEcommerce\Services\Traits\InteractsWithEcommerce;
    use \TomatoPHP\TomatoNotifications\Traits\InteractWithNotifications;
    use \TomatoPHP\TomatoOrders\Services\Traits\InteractsWithOrders;

With the successful installation of our comprehensive E-commerce system, you are now equipped to seamlessly manage your product catalog. Begin by adding new products and defining categories to tailor your offerings. Feel empowered to kickstart the order creation process, as your E-commerce system stands ready to facilitate smooth transactions and streamline your online business operations.

???? Thanks for using Tomato Plugins & TomatoPHP framework
???? Join the support server on Discord here
???? You can check docs here
⭐ Please give us a star on any repo if you like it TomatoPHP GitHub
???? Sponsor us here

Laravel News Links

Watch Timothée Chalamet Ride a Sandworm in Extended Dune: Part Two Clip

https://i.kinja-img.com/image/upload/c_fill,h_675,pg_1,q_80,w_1200/eb0ccae04f5ca6091e1591c68777727d.jpg

Dune: Part Two is finally just a few weeks away from hitting theaters, and a new sneak peek lifts the lid on one of the sequel’s most eagerly awaited scenes: Paul Atreides (Timothée Chalamet) riding a sandworm on Arrakis.

Spoilers of the Week April 24-29

This clip shared by Fandango fully reveals the first time Paul rides a sandworm—you easily get a sense of how thrilling (in the moment) and important (to Paul’s journey as he becomes a great leader) the scene is. It’s very cool and exciting to watch. However, you have to assume you won’t really get the full sensory experience until you’re watching Denis Villeneuve’s film on the biggest screen possible… preferably with one of those viral Dune: Part Two popcorn buckets clutched in your hands.

Still, sandworm-riding still looks rather amazing, even when it’s scaled down to fit your phone or computer screen, don’t you think?

Dune: Part Two hits theaters March 1.


Want more io9 news? Check out when to expect the latest Marvel, Star Wars, and Star Trek releases, what’s next for the DC Universe on film and TV, and everything you need to know about the future of Doctor Who.

Gizmodo

Laravel – Eager loading can be bad!

Laravel – Eager loading can be bad!

January 28, 2024

Hello ????




cover el

Yes, you read it right. Eager loading can be bad, really bad. However, we often resort to it when dealing with an N+1 scenario, thinking that we’ve resolved the issue, when in fact, we might have made it worse. How? Let’s see.

How bad it gets

For this demo, we are building Laravel Forge. Like (almost) every Laravel application, we will have a One To Many relationship.

We aim to log every activity for a server. A log can include the activity type, the user who initiated it, and other useful information for later analysis.

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Server extends Model
{
public function logs(): HasMany
{
return $this->hasMany(Log::class);
}
}

Now, in the application, we want to list all the servers. So, we might do something like



<table>
    <tr>
        <th>Name</th>
    </tr>
    @foreach ($servers as $server)
    <tr>
        <td></td>
    </tr>
    @endforeach
</table>

Moving forward, we have 10 servers, and each of them has 1000 logs.

So far, so good. Now, we want to display when the last activity on a server occurred

<table>
    <tr>
        <th>Name</th>
        <th>Last Activity</th>
    </tr>
    @foreach ($servers as $server)
    <tr>
        <td></td>
        <td>
            
        </td>
    </tr>
    @endforeach
</table>

Basic things, we access the logs() relation, ordering it to retrieve the latest record, getting the created_at column, and formatting it for better readability using diffForHumans(). The latter yields something like "1 week ago".

But this is bad, we’ve introduced an N+1 problem.

If you don’t know what a N+1 is, we are running the following queries


select * from `servers`


select * from `logs` where `logs`.`server_id` = 1 and `logs`.`server_id` is not null order by `created_at` desc limit 1
select * from `logs` where `logs`.`server_id` = 2 and `logs`.`server_id` is not null order by `created_at` desc limit 1

select * from `logs` where `logs`.`server_id` = 10 and `logs`.`server_id` is not null order by `created_at` desc limit 1

To resolve this issue, we typically reach out to Eager Loading (I know you did).


$servers = Server::query()
    ->with('logs')
    ->get();


<table>
    <tr>
        <th>Name</th>
        <th>Last Activity</th>
    </tr>
    @foreach ($servers as $server)
    <tr>
        <td>{{ $server->name }}</td>
        <td>
            {{ $server->logs->sortByDesc('created_at')->first()->created_at->diffForHumans() }}
        </td>
    </tr>
    @endforeach
</table>

With this update, we manage to reduce it to only 2 queries


select * from `servers`


select * from `logs` where `logs`.`server_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

And it looks like we addressed the problem, right?

Wrong! We’re only considering the number of queries. Let’s examine the memory usage and the count of loaded models; these factors are equally important.

  • Before eager loading
    • 11 queries: 1 to retrieve all servers and 10 queries for each server.
    • A total of 20 models loaded.
    • Memory usage: 2MB.
    • Execution time: 38.19 ms.




before

  • After eager loading
    • 2 queries: 1 to get all servers and 1 to get all logs.
    • A total of 10010 models loaded ????.
    • Memory usage: 13MB (6.5x increase).
    • Execution time: 66.5 ms (1.7x increase).
    • Slower computational time due to loading all the models ????.




after

The tool in the screenshot is Debugbar.

Looks like we didn’t fix anything; in fact, we made it worse.. And keep in mind, this is a very simplified example. In a real world scenario, you can easily end up with hundreds or thousands of records, leading to the loading of millions of models.. The title makes sense now?

How do we truly solve this?

In our case, eager loading is a NO NO. Instead, we can use sub-queries and leverage the database to perform tasks it is built and optimized for.

$servers = Server::query()
    ->addSelect([
        'last_activity' => Log::select('created_at')
            ->whereColumn('server_id', 'servers.id')
            ->latest()
            ->take(1)
    ])
    ->get();

This will result in a single query

select `servers`.*, (
        select `created_at`
        from `logs`
        where
            `server_id` = `servers`.`id`
        order by `created_at` desc
        limit 1
    ) as `last_activity`
from `servers`

Since the column we need from the relationship is now computed in a subquery, we have the best of both worlds: only 10 models loaded and minimal memory usage.

You might be thinking that with this approach comes a drawback: the last_activity column is now a regular string. So, if you want to use the diffForHumans() method, you’ll encounter the Call to a member function diffForHumans() on string error. But no worries, you haven’t lost the casting; it’s as simple as adding a single line.

$servers = Server::query()
    ->addSelect([
        'last_activity' => Log::select('created_at')
            ->whereColumn('server_id', 'servers.id')
            ->latest()
            ->take(1)
    ])
    ->withCasts(['last_activity' => 'datetime']) 
    ->get();

By chaining the withCasts() method, you can now treat the last_activity as if it were a date.

How about the Laravel way?

The reddit community never disappoints! They have pointed out another alternative solution, a Laravel-ish approach; One Of Many.

Let’s define a new relationship to always retrieve the latest log


public function latestLog(): HasOne
{
    return $this->hasOne(Log::class)->latestOfMany();
}

Now we can use the relationship like this


$servers = Server::query()
    ->with('latestLog')
    ->get();

This will result in the following queries

select * from `servers`

select `logs`.*
from
    `logs`
    inner join (
        select MAX(`logs`.`id`) as `id_aggregate`, `logs`.`server_id`
        from `logs`
        where
            `logs`.`server_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        group by
            `logs`.`server_id`
    ) as `latestOfMany` 
    on `latestOfMany`.`id_aggregate` = `logs`.`id`
    and `latestOfMany`.`server_id` = `logs`.`server_id`

And it can be used in the Blade like this


@foreach ($servers as $server)
    {{$server->latestLog }}
@endforeach

For a comparison between the two methods:

  • Using subqueries
    • 1 query.
    • A total of 10 models loaded.
    • Memory usage: 2MB.
    • Execution time: 21.55 ms.




old

  • Using the latestOfMany()
    • 2 queries
    • A total of 20 models loaded.
    • Memory usage: 2MB.
    • Execution time: 20.63 ms




new

Both methods are really good; which one to use will depend on your case. If you absolutely need the child model hydrated and will make use of all its fields, go with the latestOfMany(). However, if you only need a few fields, then the subquery will perform better. This is because, in the subquery, you select exactly what you need. Regardless of the number of records you have, the memory usage will be almost the same. Now, for the second method, memory usage is heavily dependent on the number of columns your table has. In reality, a table can easily have 50 columns, so hydrating the model will be expensive, even if it is only one per parent, that is to keep in mind when choosing!

Conclusion

I have seen some developers, by design, choose to force eager loading for all the models. You can’t just use it for everything, as much as it seems like you’ve solved the issue, you might have actually created a worse one. Not everything is a nail; the hammer might not work ????


Laravel News Links

The History of Zip Ties

https://theawesomer.com/photos/2024/01/all_about_zip_ties_t.jpg

The History of Zip Ties

Link

There are a few items every maker, mechanic, and technician needs in their repair kit – duct tape, WD-40, a hot glue gun, and zip ties. If you’ve ever wondered where these sturdy plastic ties came from, New Mind is here with the history of this versatile item. While their primary use is bundling cables, they’re helpful for holding many other items together.

The Awesomer

Peer-Reviewed Study: “COVID-19 vaccination is strongly associated with a serious adverse safety signal of myocarditis, particularly in children and young adults”

https://media.notthebee.com/articles/65b913049e31965b913049e31a.jpg

It seems like forever ago that the COVID vaccines were released, we learned of a ton of possible negative health implications associated with them, and then everyone just decided that those didn’t matter and we all went ahead with this charade.

Not the Bee