Skip to content

Instantly share code, notes, and snippets.

@Big-Shark
Last active June 3, 2019 22:23
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save Big-Shark/bafe87c66c716a04aabc to your computer and use it in GitHub Desktop.
Почему я использую propel, а не eloquent на примере.

Начнем с самого простого, установка и создание базы из 2 таблиц, про установку пропела я писать не буду, так как это достаточно неплохо описано в документации

Итак шаг первый, создание таблиц.

##Элоквоент

Делаем 3 миграции

./artisan make:migration create_posts
./artisan make:migration create_comments
./artisan make:migration create_comments_foreign_key

И наполняем их

    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->text('content');
        });
    }
    public function up()
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->increments('id');
            $table->text('text');
            $table->unsignedInteger('post_id');
        });
    }
    public function up()
    {
        Schema::table('comments', function ($table) {
            $table->foreign('post_id')->references('id')->on('posts');
        });
    }

(даунгрейд миграции приводить не буду)

Ну и делаем 2 файла моделей

./artisan make:model Post
./artisan make:model Comment

После чего прописываем в них связи

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
    public function post()
    {
        return $this->belongsTo(Post::class);
    }

Готово, можно начинать работать. Пришла очередь пропела.

##Пропел

Сгенерим пустую схемку

./artisan propel:schema:create

Теперь изменим ее под наши нужны, в итоге у меня она получилось вот такой, она полностью совместима с тем что генерирует ларавель в приведеных выше миграциях

<?xml version="1.0" encoding="UTF-8"?>
<database name="default" defaultIdMethod="native" namespace="App\Models">

    <table name="posts" phpName="Post">
        <column name="id" type="integer" sqlType="int(10) unsigned" required="true" primaryKey="true" autoIncrement="true"/>
        <column name="content" type="longvarchar" required="true"/>
    </table>

    <table name="comments" phpName="Comment">
        <column name="id" type="integer" sqlType="int(10) unsigned" required="true" primaryKey="true" autoIncrement="true"/>
        <column name="text" type="longvarchar" required="true" />
        <column name="post_id" type="integer" sqlType="int(10) unsigned" required="true"/>
        <foreign-key foreignTable="posts" name="comments_post_id_foreign">
            <reference local="post_id" foreign="id"/>
        </foreign-key>
        <index name="comments_post_id_foreign">
            <index-column name="post_id" />
        </index>
    </table>

</database>

Но если нам не нужна такая строгости, то мы можем сделать схему легче и понятней

<?xml version="1.0" encoding="UTF-8"?>
<database name="default" defaultIdMethod="native" namespace="App\Models">

    <table name="post">
        <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
        <column name="content" type="longvarchar" required="true"/>
    </table>

    <table name="comment">
        <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
        <column name="text" type="longvarchar" required="true" />
        <column name="post_id" type="integer" required="true"/>
        <foreign-key foreignTable="posts">
            <reference local="post_id" foreign="id"/>
        </foreign-key>
    </table>

</database>

В таком случае ключи будут созданы сами с сгенерироваными именами.

После создания схемы вызываем

./artisan propel:migration:diff
./artisan propel:migration:up
./artisan propel:model:build

И все, модели готовы к использованию, ничего создавать и прописывать больше не нужно.

##Итог

Как по мне, так установка и первоначальная настройка пропела даже для 2 таблиц оказывается более легкой и быстрой чем использование элоквоена втроеного в ларавель, что немного странно.

Сейчас мы вкратце пробежимся по схожим функциям и запросам которые они выполняют, чтоб немного освоить АПИ и его структуру.

(Там где нет авто комплита я буду писать про это, если я не написал этого, значит авто комплит есть)

Выборка по ПК и по полю ИД

Post::find($id);//Нет авто комплита
//select * from `posts` where `posts`.`id` = '1' limit 1
Post::whereId($id)->first();
//select * from `posts` where `id` = '1' limit 1

PostQuery::create()->findPk($id);
//SELECT id, content FROM posts WHERE id = 1
PostQuery::create()->findOneById($id);
//SELECT posts.id, posts.content FROM posts WHERE posts.id=1 LIMIT 1

Выборка по произвольному полю

Post::where('content', $text)->first();//Нет авто комплита
Post::whereContent($text)->first();
//select * from `posts` where `content` = 'text' limit 1

PostQuery::create()->findOneByContent($text);
PostQuery::create()->filterByContent($text)->findOne();
//SELECT posts.id, posts.content FROM posts WHERE posts.content='text' LIMIT 1

Получаем все записи

Post::all();
Post::get();//Нет авто комплита
//select * from `posts`

PostQuery::create()->find();
//SELECT posts.id, posts.content FROM posts

Получаем все записи подходящие под условия

Post::where('content', $text)->get();//Нет авто комплита
Post::whereContent($text)->get();
//select * from `posts` where `content` = 'text'

PostQuery::create()->findByContent($text);
PostQuery::create()->filterByContent($text)->find();
//SELECT posts.id, posts.content FROM posts WHERE posts.content='text'

####Я все тестировал на 8 версии шторма, @SerafimArts говорит что на 10 версии шторма все прекрасно работает

Вроде базис прошли, и поняли что разницы то особо и нет, а вот не так, разница есть и она большая, давайте поговорим о ней. При получении одного поста через элоквоент пхпШторм не подсказывает что у нас там за обьект, и какие в нем переменные есть, то есть если мы напишем

$post = \App\Post::find($id);
$post->

То мы ровным счетом ничего не увидим, при использовании же пропела ИДЕ будет знать какой обьект мы получили, и подскажет все его методы и атрибуты этих методов.

А как же у нас обстоят дела с коллекциями, да не сильно то и лучше, метод all() вернет коллекцию, и автокомплит будет работать, но вот при попытки написать форич автокомплита у элемента уже не будет. А если использовать Post::whereContent($text)->get() то результат будет вообще забавным, автокомплита для колекции мы не получим, а вот в фориче у нас будет автокомплит работать прекрасно.

Как же обстоят дела с пропелом, да все хорошо, автокомплит будет работать как у коллекции так и у обьектов внутри этой колекции при попытке перебрать их форичем.

Сейчас будет расматривать более сложные запросы, и их различия

Начнем с простого, выборка 1 поста и всех его комментов, через ленивую загрузку

$post = Post::find($id);
$post->comments;
//select * from `posts` where `posts`.`id` = '1' limit 1
//select * from `comments` where `comments`.`post_id` = '1' and `comments`.`post_id` is not null

$post = PostQuery::create()->findOneById(1);
$post->getComments();
//SELECT posts.id, posts.content FROM posts WHERE posts.id=1 LIMIT 1
//SELECT comments.id, comments.text, comments.post_id FROM comments WHERE comments.post_id=1

Отличия в запросах конечно есть, но они незначительыне, как и апри запросов.

Теперь сделает тоже самое, только с жадной загрузкой.

Post::with('comments')->find(1);
//select * from `posts` where `posts`.`id` = '1' limit 1
//select * from `comments` where `comments`.`post_id` in ('1')

PostQuery::create()->joinWithComment()->findOneById(1);
//Cannot use limit() in conjunction with with() on a one-to-many relationship. Please remove the with() call, or the limit() call.
PostQuery::create()->joinWithComment()->findPk(1);
//SELECT posts.id, posts.content, comments.id, comments.text, comments.post_id FROM posts INNER JOIN comments ON (posts.id=comments.post_id) WHERE posts.id=1

Как мы видит элоквоент делает 2 запроста, а пропел 1 запрос, в каждом подходе есть свои плюсы и минусы Но обратити внимание что первый запрос в пропеле вызвал ошибку, так как все findOne, ставят limit 1, что сломает джоин.

Теперь давайте выберем все посты и их комментарии

Post::with('comments')->get();
//select * from `posts`
//select * from `comments` where `comments`.`post_id` in ('1', '2', '3', '4', '5', '6', '7', '8', '9', '10')

PostQuery::create()->joinWithComment()->find();
//SELECT posts.id, posts.content, comments.id, comments.text, comments.post_id FROM posts INNER JOIN comments ON (posts.id=comments.post_id)

Как мы видим все достаточно просто и не сильно отличается от предыдущего примера.

А теперь усложним нашу задачу, и попробуй выбрать только 10 постов с комментариями

Post::with('comments')->limit(10)->get();
//select * from `posts` limit 1
//select * from `comments` where `comments`.`post_id` in ('1', '2', '3', '4', '5', '6', '7', '8', '9', '10')

PostQuery::create()->joinWithComment()->limit(10)->find();
//Cannot use limit() in conjunction with with() on a one-to-many relationship. Please remove the with() call, or the limit() call.
$posts = PostQuery::create()->limit(10)->find();
$posts->populateRelation('Comment');
//SELECT posts.id, posts.content FROM posts LIMIT 10
//SELECT comments.id, comments.text, comments.post_id FROM comments WHERE comments.post_id IN (1,2,3,4,5,6,7,8,9,10)

У элеквоент получился очень красивый и удобный код, а вот у пропела снова проблемма с выборкой из за джойна Но тут нам на помощь приходит populateRelation, который может сделать выборку в 2 запроса, как это делает элоквоент) И это единственное место где нам приходится вспоминать имя связей или колонок, так как коллекции не привязаны в моделям, а являются общими для всех, надеюсь это мы скоро исправим))

ОченьНемного более сложные запросы

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

Россия
  Москва (210)
  Питер (120)
  Владивосток (10)
США
  Нью-Йорк (200)
  Сан-Франциско (150)

Попробуем реализовать это на чистом sql, получим примерное такой запрос

SELECT `state`.*, `region`.*, COUNT(DISTINCT `property`.`id`) AS count
FROM `state`
INNER JOIN `region` ON (`state`.`id`=`region`.`state_id`)
INNER JOIN `property` ON (`region`.`id`=`property`.`region_id`)
GROUP BY `region`.`id`
ORDER BY `state`.`rank` ASC,count DESC

Вроде даже не сложно, но хочется использовать привычные модели, а не массивы, поэтому попробуем переписать это на пропел

$states = StateQuery::create()
  ->joinWithRegion()
  ->useRegionQuery()
      ->joinProperty()
      ->withColumn('COUNT(DISTINCT '.PropertyTableMap::COL_ID.')', 'count')
      ->groupById()
  ->endUse()
  ->orderByRank()
  ->orderBy('count', Criteria::DESC)
  ->find();

Тоже не слишком сложно, а теперь попробуйте сами переписать это на элоквоент, да так чтоб получить объекты, а не массив, удачи)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment