Verification: a143cc29221c9be0

Php class для базы данных

Php class для базы данных

Начало работы

Создание базы данных Amazon RDS Aurora MySQL

Перед тем как создавать кластер Aurora, необходимо сначала выполнить подготовительные действия, такие как создание VPC и группы подсетей для базы данных (DB subnet group). Более подробную информацию о том, как это сделать, можно найти в документации в разделе DB cluster prerequisites. После этого, для создания базы данных выполните следующие действия.

  1. Выполните команду create-db-cluster в AWS CLI для создания кластера Aurora MySQL.
    aws rds create-db-cluster \
    --db-cluster-identifier sample-cluster \
    --engine aurora-mysql \
    --engine-version 5.7.12 \
    --master-username admin \
    --master-user-password secret99 \
    --db-subnet-group-name default-vpc-6cc1cf0a \
    --vpc-security-group-ids sg-d7cf52a3 \
    --enable-iam-database-authentication true
  2. Добавьте новый экземпляр базы данных в кластер.
    aws rds create-db-instance \
        --db-instance-class db.r5.large \
        --db-instance-identifier sample-instance \
        --engine aurora-mysql  \
        --db-cluster-identifier sample-cluster
  3. Сохраните учётные данные от созданной базы в виде секрета в AWS Secrets Manager.
    aws secretsmanager create-secret \
    --name MyTestDatabaseSecret \
    --description "My test database secret created with the CLI" \
    --secret-string '{"username":"admin","password":"secret99","engine":"mysql","host":"","port":"3306","dbClusterIdentifier":""}'

    Сохраните ARN секрета, который будет возвращён после выполнения команды. Он понадобится на следующем шаге.

    {
        "VersionId": "eb518920-4970-419f-b1c2-1c0b52062117", 
        "Name": "MySampleDatabaseSecret", 
        "ARN": "arn:aws:secretsmanager:eu-west-1:1234567890:secret:MySampleDatabaseSecret-JgEWv1"
    }

    Этот секрет используется RDS Proxy для создания пула подключений к базе данных. Чтобы RDS Proxy получил доступ к секрету, необходимо явно назначить ему соответствующие права.

  4. Создайте политику IAM, которая предоставляет права на вызовы secretsmanager к указанному секрету (замените поле на ARN из предыдущего шага).
    aws iam create-policy \
    --policy-name my-rds-proxy-sample-policy \
    --policy-document '{
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "VisualEditor0",
          "Effect": "Allow",
          "Action": [
            "secretsmanager:GetResourcePolicy",
            "secretsmanager:GetSecretValue",
            "secretsmanager:DescribeSecret",
            "secretsmanager:ListSecretVersionIds"
          ],
          "Resource": [
            "”
          ]
        },
        {
          "Sid": "VisualEditor1",
          "Effect": "Allow",
          "Action": [
            "secretsmanager:GetRandomPassword",
            "secretsmanager:ListSecrets"
          ],
          "Resource": "*"
        }
      ]
    }'

    Сохраните ARN созданной политики. Он понадобится, чтобы связать эту политику с новой ролью, которую мы создадим.

    {
        "Policy": {
            "PolicyName": "my-rds-proxy-sample-policy", 
            "PermissionsBoundaryUsageCount": 0, 
            "CreateDate": "2020-06-04T12:21:25Z", 
            "AttachmentCount": 0, 
            "IsAttachable": true, 
            "PolicyId": "ANPA6JE2MLNK3Z4EFQ5KL", 
            "DefaultVersionId": "v1", 
            "Path": "/", 
            "Arn": "arn:aws:iam::1234567890112:policy/my-rds-proxy-sample-policy", 
            "UpdateDate": "2020-06-04T12:21:25Z"
         }
    }
  5. Создайте роль IAM, которая будет использована сервисом RDS Proxy. С помощью этой роли RDS Proxy сможет получить доступ к учётным данным базы данных.
    aws iam create-role --role-name my-rds-proxy-sample-role --assume-role-policy-document '{
     "Version": "2012-10-17",
     "Statement": [
      {
       "Sid": "",
       "Effect": "Allow",
       "Principal": {
        "Service": "rds.amazonaws.com"
       },
       "Action": "sts:AssumeRole"
      }
     ]
    }'
  6. Добавьте созданную ранее политику в роль:
    aws iam attach-role-policy \
    --role-name my-rds-proxy-sample-role \
    --policy-arn arn:aws:iam::123456789:policy/my-rds-proxy-sample-policy

Создание RDS Proxy

  1. Используйте AWS CLI, чтобы создать новый RDS Proxy. Замените значения -role-arn и SecretArn на соответствующие ARN из предыдущих шагов.
    aws rds create-db-proxy \
    --db-proxy-name sample-db-proxy \
    --engine-family MYSQL \
    --auth '{
            "AuthScheme": "SECRETS",
            "SecretArn": "arn:aws:secretsmanager:eu-west-1:123456789:secret:exampleAuroraRDSsecret1-DyCOcC",
             "IAMAuth": "REQUIRED"
          }' \
    --role-arn arn:aws:iam::123456789:role/my-rds-proxy-sample-role \
    --vpc-subnet-ids  subnet-c07efb9a subnet-2bc08b63 subnet-a9007bcf

    Для принудительной аутентификации пользователей RDS Proxy через IAM значение IAMAuth установлено в REQUIRED. Это более безопасная альтернатива встраиванию учётных данных базы данных в исходный код приложения.

    Кластер баз данных Aurora и его экземпляры называются целями (targets) созданного прокси.

  2. Добавьте кластер баз данных в прокси с помощью команды register-db-proxy-targets.
    aws rds register-db-proxy-targets \
    --db-proxy-name sample-db-proxy \
    --db-cluster-identifiers sample-cluster

Развёртывание Lambda-функции на PHP с доступом к VPC

В GitHub репозитории находится Lambda-функция со средой запуска PHP, настроенной с использованием слоя Lambda. Эта функция использует расширение MySQLi для подключения к RDS Proxy. Расширение было установлено и собрано вместе с исполняемым файлом PHP с помощью следующей команды:

Исполняемый файл PHP был упакован вместе с bootstrap-файлом для создания среды запуска PHP в Lambda. Более подробную информацию о создании своей среды запуска для PHP вы можете найти в предыдущей статье серии.

Установите стек приложения с помощью AWS Serverless Application Model (AWS SAM) CLI:

sam deploy -g

При запросе введите значения SecurityGroupIds и SubnetIds для вашего кластера Aurora.

Шаблон SAM передаёт параметры SecurityGroupIds и SubnetIds в Lambda-функцию, используя подресурс VpcConfig.

Lambda создаёт эластичный сетевой интерфейс (elastic network interface, ENI) для каждой комбинации группы безопасности (security group) и подсети в конфигурации VPC соответствующей функции. Функция может получить доступ к другим ресурсам (и интернету) только через этот VPC.

Добавление RDS Proxy к Lambda-функции

  1. Перейдите в консоль Lambda.
  2. Выберите только что созданную функцию PHPHelloFunction.
  3. Нажмите Add database proxy внизу страницы.
  4. Выберите опцию Choose an existing database proxy, затем выберите sample-db-proxy.
  5. Нажмите Add.

Использование RDS Proxy из Lambda-функции

Lambda-функция импортирует три библиотеки из AWS SDK для PHP. Они используются для генерации токена для подключения к базе данных на основе учётных данных, хранящихся в Secrets Manager.

Библиотеки AWS SDK для PHP предоставляются слоем PHP-example-vendor. Использование слоёв Lambda таким образом позволяет создать механизм по встраиванию дополнительных библиотек и зависимостей по мере развития приложения.

Обработчик функции под названием index представляет собой точку входа в код функции. Вначале вызывается getenv() для того, чтобы получить переменные окружения, установленные при развёртывании приложения через SAM. После этого они доступны в локальных переменных до окончания вызова Lambda-функции.

Класс AuthTokenGenerator используется при аутентификации IAM и генерирует токен для аутентификации в RDS. Для его инициализации необходимо передать в конструктор объект credential provider. Затем вызывается метод createToken(), в который в качестве параметров передаются адрес конечной точки прокси, порт, регион и имя пользователя базы данных. Полученный таким образом токен впоследствии используется для подключения к прокси.

Класс mysqli в PHP используется для подключения к базе данных MySQL. Метод real_connect() используется для открытия подключения к базе данных через RDS Proxy. Вместо адреса базы данных первым параметром необходимо передать адрес конечной точки прокси. Также передаются имя пользователя базы данных, временный токен, название базы данных и порт. Кроме того, указана константа MYSQLI_CLIENT_SSL, чтобы убедиться, что подключение будет использовать шифрование SSL.

После успешного подключения вы можете использовать соответствующий объект для работы с базой данных. В примере ниже выполняется запрос SHOW TABLES. После этого подключение закрывается, а результат конвертируется в JSON и возвращается из Lambda-функции.

Вывод после успешного запуска функции выглядит следующим образом:

Мониторинг RDS Proxy и настройка производительности

RDS Proxy позволяет вам осуществлять мониторинг и настраивать ограничения на количество подключений и интервалы максимального времени ожидания без изменения исходного кода приложения.

Установите максимальное время ожидания в соответствии с требованиями вашего приложения через настройку Connection borrow timeout. Она указывает на то, как долго сервис будет ждать доступного подключения из пула, перед тем как вернёт ошибку.

Измените интервал Idle client connection timeout, чтобы помочь вашим приложениям обрабатывать устаревшие ресурсы. С помощью него можно уберечь ваши приложения от ошибочного оставления открытых подключений к базе данных, которые тратят важные системные ресурсы.

Несколько приложений, использующих одну базу данных, могут каждый использовать RDS Proxy для разделения общей квоты подключений между ними. Вы можете установить максимальное количество подключений для каждого прокси в виде процента от значения max_connections (для MySQL).

В следующем примере показано, как можно поменять MaxConnectionsPercent для целевой группы в прокси.

aws rds modify-db-proxy-target-group \
--db-proxy-name sample-db-proxy \
--target-group-name default \
--connection-pool-config '{"MaxConnectionsPercent": 75 }'

Ответ:

{
    "TargetGroups": [
        {
            "DBProxyName": "sample-db-proxy",
            "TargetGroupName": "default",
            "TargetGroupArn": "arn:aws:rds:eu-west-1:####:target-group:prx-tg-03d7fe854604e0ed1",
            "IsDefault": true,
            "Status": "available",
            "ConnectionPoolConfig": {
            "MaxConnectionsPercent": 75,
            "MaxIdleConnectionsPercent": 50,
            "ConnectionBorrowTimeout": 120,
            "SessionPinningFilters": []
        	},            
"CreatedDate": "2020-06-04T16:14:35.858000+00:00",
            "UpdatedDate": "2020-06-09T09:08:50.889000+00:00"
        }
    ]
}

Если RDS Proxy обнаружит, что сессия находится в таком состоянии, что её нельзя повторно использовать, он будет использовать для неё одно и то же подключение до завершения этой сессии. Такое поведение называется закреплением (pinning). При настройке производительности RDS Proxy необходимо максимизировать повторное использование подключений путём минимизации количества закреплений.

Вы можете следить за метрикой Amazon CloudWatch под названием DatabaseConnectionsCurrentlySessionPinned, чтобы понять как часто в вашем приложении происходит закрепление.

Amazon CloudWatch собирает и обрабатывает сырые данные из RDS Proxy и строит на их основе удобочитаемые метрики практически в реальном времени. Используйте указанные в документации метрики, чтобы следить за количеством подключений и расходуемой на управление подключениями памятью. Благодаря им, вы можете понять, будет ли польза вашему экземпляру или кластеру базы данных от использования RDS Proxy. Например, в случае если он обрабатывает много коротких подключений или подключений, которые открываются и закрываются с высокой скоростью.

Классы для наполнения БД

По умолчанию в Laravel уже определён класс DatabaseSeeder. Из этого класса можно вызывать метод call() для подключения других классов с данными, что позволит контролировать порядок их выполнения.

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder {
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run() {
        // $this->call(UserTableSeeder::class);
    }
}

Для создания заготовок классов UserTableSeeder и PostTableSeeder используем команду:

> php artisan make:seeder UserTableSeeder
> php artisan make:seeder PostTableSeeder
use Illuminate\Database\Seeder;

class UserTableSeeder extends Seeder {
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run() {
        // .....
    }
}
use Illuminate\Database\Seeder;

class PostTableSeeder extends Seeder {
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run() {
        // .....
    }
}

Отредактируем исходный код класса DatabaseSeeder, а точнее — метод run():

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder {
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run() {
        $this->call(UserTableSeeder::class);
        $this->command->info('Таблица пользователей загружена данными!');

        $this->call(PostTableSeeder::class);
        $this->command->info('Таблица постов блога загружена данными!');
    }
}

Фабрики моделей

Файлы фабрик моделей хранятся в директории database/factories, и там уже есть один готовый файл UserFactory.php — это фабрика для модели User.

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;

/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/

$factory->define(User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '.....',
        'remember_token' => Str::random(10),
    ];
});

Для заполнения полей name и email используется класс Faker, который как раз и генерирует случайные, но правдоподобные значения. Чтобы создать фабрику для модели Post, используем Artisan-команду:

> php artisan make:factory PostFactory --model=Post
/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Post;
use Faker\Generator as Faker;

$factory->define(Post::class, function (Faker $faker) {
    return [
        // .....
    ];
});

Отредактируем этот файл, чтобы заполнять таблицу БД правдоподобными значениями:

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Post;
use Faker\Generator as Faker;

$factory->define(Post::class, function (Faker $faker) {
    return [
        'author_id' => rand(1, 4),
        'title' => $faker->realText(rand(25, 30)),
        'excerpt' => $faker->realText(rand(100, 120)),
        'body' => $faker->realText(rand(200, 300)),
        'created_at' => $faker->dateTimeBetween('-60 days', '-30 days'),
        'updated_at' => $faker->dateTimeBetween('-20 days', '-1 days'),

    ];
});

И отредактируем файлы классов UserTableSeeder и PostTableSeeder:

use Illuminate\Database\Seeder;

class UserTableSeeder extends Seeder {
    public function run() {
        // создать 4 пользователей сайта
        factory(App\User::class, 4)->create();
    }
}
use Illuminate\Database\Seeder;

class PostTableSeeder extends Seeder {
    public function run() {
        // создать 20 постов блога
        factory(App\Post::class, 20)->create();
    }
}

Команды для наполнения БД

Для добавления начальных данных в БД предназначена Artisan-команда db:seed:

> php artisan db:seed

По умолчанию команда db:seed вызывает класс DatabaseSeeder, который может быть использован для вызова других классов, заполняющих БД начальными данными. Однако, можно использовать параметр --class для указания конкретного класса для вызова:

> php artisan db:seed --class=UserTableSeeder

Также можно сначала откатить все миграции, накатить их снова + заполнить начальными данными:

> php artisan migrate:refresh --seed

Или можно удалить все таблицы БД, накатить все миграции и заполнить БД начальными даннми:

> php artisan migrate:fresh --seed
Dropped all tables successfully.
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.03 seconds)
Migrating: 2020_09_06_104713_create_posts_table
Migrated:  2020_09_06_104713_create_posts_table (0.05 seconds)
Seeding: UserTableSeeder
Seeded:  UserTableSeeder (0.1 seconds)
Таблица пользователей загружена данными!
Seeding: PostTableSeeder
Seeded:  PostTableSeeder (0.46 seconds)
Таблица постов блога загружена данными!
Database seeding completed successfully.

Введение

Laravel предлагает возможность наполнения базы данных тестовыми данными с использованием классов-наполнителей. Все классы наполнителей хранятся в каталоге database/seeders. Класс DatabaseSeeder уже определен по умолчанию. В этом классе вы можете использовать метод call для запуска других наполнителей, что позволит вам контролировать порядок наполнения БД.

При наполнении базы данных автоматически отключается защита массового присвоения.

Написание наполнителей

Чтобы сгенерировать новый наполнитель, используйте команду make:seeder Artisan. Эта команда поместит новый класс наполнителя в каталог database/seeders вашего приложения:

php artisan make:seeder UserSeeder

Класс наполнителя по умолчанию содержит только один метод: run. Этот метод вызывается при выполнении команды db:seed Artisan. В методе run вы можете вставлять данные в свою базу данных, как хотите. Вы можете использовать построитель запросов для самостоятельной вставки данных или использовать фабрики моделей Eloquent.

В качестве примера давайте изменим класс DatabaseSeeder, созданный по умолчанию, и добавим выражение вставки фасада DB в методе run:

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

class DatabaseSeeder extends Seeder
{
    
    public function run()
    {
        DB::table('users')->insert([
            'name' => Str::random(10),
            'email' => Str::random(10).'@gmail.com',
            'password' => Hash::make('password'),
        ]);
    }
}
В методе run вы можете объявить любые необходимые типы зависимостей. Они будут автоматически извлечены и внедрены через контейнер служб Laravel.

Использование фабрик моделей

Конечно, ручное указание атрибутов для каждой модели наполнителя обременительно. Вместо этого вы можете использовать фабрики моделей для удобного создания большого количества записей в БД. Сначала просмотрите документацию фабрики моделей, чтобы узнать, как определить свои фабрики.

Например, давайте создадим 50 пользователей, у каждого из которых будет по одному посту:

use App\Models\User;


public function run()
{
    User::factory()
            ->count(50)
            ->hasPosts(1)
            ->create();
}

Вызов дополнительных наполнителей

Внутри класса DatabaseSeeder вы можете использовать метод call для запуска других наполнителей. Использование метода call позволяет вам разбить ваши наполнители БД на несколько файлов, так что ни один класс наполнителя не станет слишком большим. Метод call принимает массив классов, которые должны быть выполнены:


public function run()
{
    $this->call([
        UserSeeder::class,
        PostSeeder::class,
        CommentSeeder::class,
    ]);
}

Создание таблиц БД

Начнем с каталога товаров. Нам потребуются три таблицы в базе данных для хранения категорий, брендов и товаров. Подключаемся к серверу БД и создаем новую базу данных larashop. После этого создаем три модели — Product, Category и Brand — вместе с файлами миграции. Отредактируем файлы классов миграций, чтобы наши таблицы содержали все необходимые поля.

> php artisan make:model Category -m
> php artisan make:model Brand -m
> php artisan make:model Product -m
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCategoriesTable extends Migration {
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('parent_id')->nullable(false)->default(0);
            $table->string('name', 100);
            $table->string('content', 200)->nullable();
            $table->string('slug', 100)->unique();
            $table->string('image', 50)->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        Schema::dropIfExists('categories');
    }
}
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBrandsTable extends Migration {
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        Schema::create('brands', function (Blueprint $table) {
            $table->id();
            $table->string('name', 100);
            $table->string('content', 200)->nullable();
            $table->string('slug', 100)->unique();
            $table->string('image', 50)->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        Schema::dropIfExists('brands');
    }
}
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateProductsTable extends Migration {
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->bigInteger('category_id')->unsigned()->nullable();
            $table->bigInteger('brand_id')->unsigned()->nullable();
            $table->string('name', 100);
            $table->text('content')->nullable();
            $table->string('slug', 100)->unique();
            $table->string('image', 50)->nullable();
            $table->decimal('price', 10, 2, true)->default(0);
            $table->timestamps();

            // внешний ключ, ссылается на поле id таблицы categories
            $table->foreign('category_id')
                ->references('id')
                ->on('categories')
                ->nullOnDelete();
            // внешний ключ, ссылается на поле id таблицы brands
            $table->foreign('brand_id')
                ->references('id')
                ->on('brands')
                ->nullOnDelete();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        Schema::dropIfExists('products');
    }
}

Перед тем, как создавать таблицы базы данных, надо задать параметры подключения к серверу БД в файле .env:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=larashop
DB_USERNAME=root
DB_PASSWORD=qwerty

Теперь все готово к миграции, создаем таблицы базы данных с помощью команды:

> php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.03 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.03 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0.02 seconds)
Migrating: 2020_09_28_130327_create_categories_table
Migrated:  2020_09_28_130327_create_categories_table (0.03 seconds)
Migrating: 2020_09_28_130335_create_brands_table
Migrated:  2020_09_28_130335_create_brands_table (0.03 seconds)
Migrating: 2020_09_28_130346_create_products_table
Migrated:  2020_09_28_130346_create_products_table (0.1 seconds)
--
-- Структура таблицы `users`
--
CREATE TABLE `users` (
  `id` bigint(20) UNSIGNED NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email_verified_at` timestamp NULL DEFAULT NULL,
  `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Индексы таблицы `users`
--
ALTER TABLE `users`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `users_email_unique` (`email`);

--
-- AUTO_INCREMENT для таблицы `users`
--
ALTER TABLE `users`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;
COMMIT;
--
-- Структура таблицы `categories`
--
CREATE TABLE `categories` (
  `id` bigint(20) UNSIGNED NOT NULL,
  `parent_id` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
  `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `content` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `slug` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `image` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Индексы таблицы `categories`
--
ALTER TABLE `categories`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `categories_slug_unique` (`slug`);

--
-- AUTO_INCREMENT для таблицы `categories`
--
ALTER TABLE `categories`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;
COMMIT;
--
-- Структура таблицы `brands`
--
CREATE TABLE `brands` (
  `id` bigint(20) UNSIGNED NOT NULL,
  `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `content` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `slug` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `image` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Индексы таблицы `brands`
--
ALTER TABLE `brands`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `brands_slug_unique` (`slug`);

--
-- AUTO_INCREMENT для таблицы `brands`
--
ALTER TABLE `brands`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;
COMMIT;
--
-- Структура таблицы `products`
--
CREATE TABLE `products` (
  `id` bigint(20) UNSIGNED NOT NULL,
  `category_id` bigint(20) UNSIGNED DEFAULT NULL,
  `brand_id` bigint(20) UNSIGNED DEFAULT NULL,
  `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `content` text COLLATE utf8mb4_unicode_ci,
  `slug` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `image` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `price` decimal(10,2) UNSIGNED NOT NULL DEFAULT '0.00',
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Индексы таблицы `products`
--
ALTER TABLE `products`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `products_slug_unique` (`slug`),
  ADD KEY `products_category_id_foreign` (`category_id`),
  ADD KEY `products_brand_id_foreign` (`brand_id`);

--
-- AUTO_INCREMENT для таблицы `products`
--
ALTER TABLE `products`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- Ограничения внешнего ключа таблицы `products`
--
ALTER TABLE `products`
  ADD CONSTRAINT `products_brand_id_foreign` FOREIGN KEY (`brand_id`) REFERENCES `brands` (`id`) ON DELETE SET NULL,
  ADD CONSTRAINT `products_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL;
COMMIT;