2

I am new to using PHP Frameworks. I decided to try out Laravel. In my project, I needed to write a search function which will search through a few entities based on some keywords, then do a UNION and return the results. So the SQL will look something like this:

SELECT pages.id,pages.updated_at,pages.created_at,page_translations.title,page_translations.description
FROM pages
INNER JOIN page_translations ON page_translations.page_id = pages.id
WHERE pages.deleted_at IS NULL
AND page_translations.deleted_at IS NULL
AND pages.published = 1
AND page_translations.locale = @lang
AND page_translations.active = 1
AND ( page_translations.title LIKE '%@keyword%'
    OR page_translations.description LIKE '%@keyword%'
    OR pages.id IN (
        SELECT blockable_id
        FROM blocks
        WHERE blockable_type = 'App\\\\Models\\\\Pages'
        AND content LIKE '%@keyword%'
    )
)
UNION
SELECT articles.id,articles.updated_at,articles.created_at,article_translations.title,article_translations.description
FROM articles
INNER JOIN article_translations ON article_translations.article_id = articles.id
WHERE articles.deleted_at IS NULL
AND article_translations.deleted_at IS NULL
AND articles.published = 1
AND article_translations.locale = @lang
AND article_translations.active = 1
AND ( article_translations.title LIKE '%@keyword%'
    OR article_translations.description LIKE '%@keyword%'
    OR articles.id IN (
        SELECT blockable_id
        FROM blocks
        WHERE blockable_type = 'App\\\\Models\\\\Articles'
        AND content LIKE '%@keyword%'
    )
)
ORDER BY updated_at DESC

I translated this query to use Laravel's Eloquent approach, and it looked something like this:

$pages = DB::table('pages')
  ->select(explode(',','pages.id,pages.updated_at,pages.created_at,page_translations.title,page_translations.description'))
  ->selectSub(function($query){
    $query->selectRaw("'pages'");
  },'content_type')
  ->join('page_translations','page_translations.page_id','=','pages.id')
  ->whereNull('pages.deleted_at')
  ->whereNull('page_translations.deleted_at')
  ->where([
    ['pages.published','=',1],
    ['page_translations.locale','=',$lang],
    ['page_translations.active','=',1],
  ])
  ->where(function($query) use ($keywords) {
    $query->where('page_translations.title','LIKE','%'.$keywords.'%')
      ->orWhere('page_translations.description','LIKE','%'.$keywords.'%')
      ->orWhereIn('pages.id',function($subquery) use ($keywords) {
        $subquery->select('blockable_id')
          ->from('blocks')
          ->where('blockable_type','=','App\\\\Models\\\\Page')
          ->where(function($blockquery) use ($keywords) {
            $blockquery->where('content','LIKE','%'.$keywords.'%');
          });
      });
  });
$articles = DB::table('articles')
  ->select(explode(',','articles.id,articles.updated_at,articles.created_at,article_translations.title,article_translations.description'))
  ->selectSub(function($query){
    $query->selectRaw("'articles'");
  },'content_type')
  ->join('article_translations','article_translations.article_id','=','articles.id')
  ->whereNull('articles.deleted_at')
  ->whereNull('article_translations.deleted_at')
  ->where([
    ['articles.published','=',1],
    ['article_translations.locale','=',$lang],
    ['article_translations.active','=',1],
  ])
  ->where(function($query) use ($keywords) {
    $query->where('article_translations.title','LIKE','%'.$keywords.'%')
      ->orWhere('article_translations.description','LIKE','%'.$keywords.'%')
      ->orWhereIn('articles.id',function($subquery) use ($keywords) {
        $subquery->select('blockable_id')
          ->from('blocks')
          ->where('blockable_type','=','App\\\\Models\\\\Article')
          ->where(function($blockquery) use ($keywords) {
            $blockquery->where('content','LIKE','%'.$keywords.'%');
          });
      });
  })
  ->union($pages)
  ->orderBy('content_type','desc')
  ->orderBy('updated_at','desc')
  ->get();

To me, the raw SQL approach is much more legible. And if my query had a few more subqueries, the SQL approach would still remain quite legible to me.

So my question is when developing with Laravel in a small team environment (2 other backend developers), is it best practice to never write Raw SQL? Is it the Laravel convention to always use Eloquent query builder methods unless there were exceptional circumstances such as bugs with eloquent, performance issues, etc...?

  • Your Laravel query appears to be (more or less) a direct transliteration of the raw SQL. I don't know that much about Laravel, but it seems to me that you might be missing the "spirit" of Laravel. If there is value in using Laravel, it would be in the simplification of your queries, not merely a direct translation with different syntax. Translating the query keyword by keyword merely adds an unnecessary layer of indirection for no good benefit, and quite possibly short-circuits some optimizations that might otherwise be available in the raw SQL. – Robert Harvey Feb 09 '20 at 18:45
  • 1
    As an aside, the term "bad practice" is useless without a frame of reference. It's not enough to merely state that something is "good" or "bad" practice. Why is it good or bad? Under what circumstances would it be good or bad? A practice that is useful in some cases might be harmful in others. So you have to know what your goals are, and then evaluate a given technique based on how well it fulfills those goals. – Robert Harvey Feb 09 '20 at 18:48

0 Answers0