+1

Laravel custom messages for array validation

Indexs

Laravel: Loading tags with select2 and ajax

  • Như ở bài trước mình có nói thì bài này mình tiếp tục chia sẻ bài viết dạng Vấn Đề - Giải pháp về các issue trong quá trình code để bạn nào có gặp vấn đề tương tự thì có thế tham khảo giải pháp của mình hoặc có giải pháp nào hay hơn thì comment chia sẻ cho mọi người. Bài này viết về validation trong Laravel, cụ thể là custom lại message khi validate array, có chút liên quan để bài trước đấy nhé, các bạn có thể tham khảo ở link bên trên.

1. Vấn đề

Input:

  • Thực hiện validate tag trong lúc create hoặc edit bài viết, phần hiển thị tag sử dụng thư viện Select2 :
    • Độ dài mỗi tag tối đa 15 ký tự
    • Một post có tối đa 5 tag
    • Nếu chọn tag đã bị block thì hiển thị ra thông báo lỗi. (bên Admin sẽ cho chức năng block tag)

Output:

  • Validate được tag và hiển thị ra thông báo lỗi tương ứng.

2. Giải pháp

  • Database:
    • Bảng Tag có id, tag_name, activated
  • Ý tưởng: Trong bài trước ta đã thực hiện validate độ dài mỗi tag và số lượng tag tối đa ở phía client, nhưng để đảm bảo tính an toàn ta vẫn nên thực hiện validate ở phía server, sau đây là một vài cách:
    • Cách 1: Trước khi submit form đến method create hoặc update, ta sẽ gửi request ajax đến method checkValidate() để kiểm tra xem nếu data hợp lệ thì ta sẽ tiếp tục điều hướng request đến method create hoặc update tương ứng, còn không sẽ báo lỗi.

    • File js sẽ như sau:

      $(document).on('click', '.btnSubmit', function() {
          var tags = $('#tag').val();
      
          $.ajax({
              url: baseUrl() + '/posts/checkValidate/',
              type: 'POST',
              data: {
                  tags: tags,
              },
              success: function (response) {
                  if (response.success) {
                      $.ajax({
                          url: baseUrl() + '/posts/update/',
                          type: 'POST',
                          data: {
                              tags: tags,
                          },
                          success: function (response) {
                              if (response.success) {
                                  alert(response.msgSuccess);
                              } else {
                                  alert(response.msgError);
                              }
                          }
                      });
                  } else {
                      $('.tagError').text(response.msgError);
                  }
              }
          });
      });
      
    • Phương thức checkValidate():

      public function checkValidate(Request $request)
      {
          $tags = $request->input('tags');
          if (count($tags) > 0) {
              if (count($tags) > 5) {
                  return response()->json([
                      'success' => false,
                      'msgError' => "The tag may not have more than 5 items.",
                  ]);
              }
      
              $tmp = '';
              foreach ($tags as $tag) {
                  if (strlen($tag) > 15) {
                      $tmp = $tmp . $tag . ", ";
                  }
              }
      
              if ($tmp != '') {
                  return response()->json([
                      'success' => false,
                      'msgError' => substr($tmp, 0, -2) . " may not be greater than 15 characters.",
                  ]);
              }
      
              $tagNotActivate = DB::table('tag')->whereIn('id', $tags)->where('activated', 0)->pluck('tag_name')->toArray();
              if (count($tagNotActivate)) {
                  return response()->json([
                      'success' => false,
                      'msgError' => implode(', ', $tagNotActivate) . " have been blocked.",
                  ]);
              }
          }
          return response()->json([
              'success' => true
          ]);
      }
      
    • Trên view ta cần thêm thẻ chứa class .tagError để hiển thị lỗi

          <select multiple="true" name="tags[]" id="tag" class="form-control select2">
                 @foreach(old('tags', $topic->tags) as $tag)
                     <option value="{{ $tag }}" selected="selected">{{ $tag }}</option>
                 @endforeach
         </select>
         <span class="tagError"></span>
      
    • So với view ở bài trước sử dụng if để check xem có tồn tại old("tag") thì lần này ta truyền luôn đối thứ 2 vào phương thức old('tags', $topic->tags) của helper có vẻ hay hơn.

    • Nhưng cách này khá thủ công, phải gửi request 2 lần đến server và với những người dùng biết chút ít kỹ thuật thì vẫn có thể pass qua được validate bằng cách sửa file js => không an toàn.

  • Cách 2: sử dụng luôn validate array của laravel
    • Tạo file PostRequest.php bằng câu lệnh make:request bên controller ta chỉ cần truyền đối số PostRequest vào method store hoặc update là data sẽ được valid qua file PostRequest.php (Tất nhiên ta cần use PostRequest ở đầu file nhé).
      public function update(PostRequest $request, $id) {
          //
      }
      
    • Phương thức rules
      public function rules(Request $request)
      {
          $tagsError = "";
          $input = $request->all();
          if (isset($input['tags'])) {
              $tagsBlockName = DB::table('tag')->whereIn('tag_name', $input['tags'])
                  ->where('activated', 1)
                  ->pluck('tag_name')
                  ->toArray();
              $tagsError = implode(',', $tagsBlockName);
          }
      
          $rules = [
              'tags.*' => "max:15|size:5|not_in:$tagsError",
          ];
      
          return $rules;
      }
      
    • Phương thức message
      public function messages()
      {
          return [
              'tags.*.max' => "The tag may not be greater than 15 characters.",
               'tags.*.size' => "The tag may not have more than 5 items.",
              'tags.*.not_in' => "The tag has been blocked.",
          ];
      }
      
    • Ở trường hợp valid đội dài mỗi tag và số lượng tag tối đa thì message hiển thị lỗi ổn, nhưng ở phần valid tag block nếu ta không chỉ rõ tag nào bị block thì người dùng sẽ không biết được tag nào trong 5 tag đã bị block để gỡ bỏ. Ngoài ra khi thực hiện test bạn sẽ thấy message lỗi bị hiển thi nhiều lần do đây là validate mảng, rất củ chuối đúng không.
    • Đến đây ban đầu mình đã định viết một validation mới cho phần này theo hướng dẫn documment. Nhưng ngó lại thấy phần Customizing The Error Format có thể giải quyết được nên mình triển luôn.
    • Ta sẽ thêm phương thức formatErrors như sau:
      protected function formatErrors(Validator $validator)
      {
          $results = [];
          $tagFlag = false;
          $messages = $validator->errors()->messages();
      
          foreach ($messages as $key => $value) {
              if (!str_contains($key, 'tags') || !$tagFlag) {
                  $results[] = $value[0];
              }
              if (str_contains($key, 'tags') && !$tagFlag) {
                  $tagFlag = true;
              }
          }
      
          return $results;
      }
      
    • Phương thức formatErrors sẽ loại bỏ các message trùng lặp chỉ giữ lại một message duy nhất, hàm str_contains check xem có tồn tại text trong chuỗi hay không, thấy helper của laravel có nên sài luôn 😄
    • Lưu ý: ta cũng cần sửa lại message lỗi phần valid tag block để hiển thị được tag nào người dùng nhập vào đã bị block rồi:
      tags.*.not_in' => ":values have been blocked.",
      
    • Cách 2 này khá ngon lành rồi, nếu các bạn có cách nào hay hơn nữa thì share ở dưới nhé

Tài liệu tham khảo Laravel Validation


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí