'What makes the "Add article" form in this Laravel 8 application fail?

I am making a blogging application with Laravel 8 and Bootstrap 5.

I run into a problem with adding an article into the articles table.

In the migration file that generated the articles table, I have:

public function up() {
 Schema::create('articles', function (Blueprint $table) {
    $table->id();
    $table->unsignedInteger('user_id');
    $table->foreign('user_id')->references('id')->on('users');
    $table->unsignedInteger('category_id');
    $table->foreign('category_id')->references('id')->on('catergories');
    $table->string('title');
    $table->string('slug');
    $table->string('short_description');
    $table->longText('content');
    $table->tinyInteger('featured')->default('0');
    $table->string('image')->nullable();
    $table->timestamps();
  });
}

In the Article model, I have:

class Article extends Model {
  use HasFactory;

    protected $fillable = [
        'user_id',
        'category_id',
        'title',
        'slug',
        'short_description',
        'content',
        'featured',
        'image',
    ];

    // Join users to articles
    public function user() {
        return $this->belongsTo(User::class);
    }

    // Join categories to articles
    public function category() {
        return $this->belongsTo(ArticleCategory::class);
    }
}

In the controller, I have:

namespace App\Http\Controllers\Dashboard;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
use App\Models\ArticleCategory;
use App\Models\Article;
use Illuminate\Http\Request;

class ArticleController extends Controller {
    private $rules = [
        'category_id' => 'required|exists:article_categories,id',
        'title' => 'required|string|max:255',
        'short_description' => 'required|string|max:255',
        'image' =>  'image|mimes:jpeg,png,jpg|max:2048',
        'content' => 'required|string',
        'featured' => 'required'
    ];

    private $messages = [
        'category_id.required' => 'Please pick a category for the article',
        'title.required' => 'Please provide a title for the article',
        'short_description.required' => 'The article needs a short description',
        'content.required' => 'Please add content'
    ];
    
    public function categories() {
        return ArticleCategory::all();
    }
    
    public function index() {
        $articles = Article::paginate(10);

        return view('dashboard/articles',
            ['articles' => $articles]
        );
    }

    public function create() {
        // Load the view and populate the form with categories
        return view('dashboard/add-article',
            ['categories' => $this->categories()]
        );
    }

    public function save(Request $request) {

        // Validate form (with custom messages)
        $validator = Validator::make($request->all(), $this->rules, $this->messages);

        if ($validator->fails()) {
            return redirect()->back()->withErrors($validator->errors())->withInput();
        }

        $fields = $validator->validated();

        // Upload article image
        $current_user = Auth::user();

        if (isset($request->image)) {
            $imageName = md5(time()) . $current_user->id . '.' . $request->image->extension();
            $request->image->move(public_path('images/articles'), $imageName);
        }

        // Data to be added
        $form_data = [
            'user_id' => Auth::user()->id,
            'category_id' => $fields['category_id'],
            'title' => $fields['title'],
            'slug' => Str::slug($fields['title'], '-'),
            'short_description' => $fields['short_description'],
            'content' => $fields['content'],
            'image' => $fields['image'],
            'featured' => $fields['featured']
        ];

        // Insert data in the 'articles' table
        $query = Article::create($form_data);

        if ($query) {
            return redirect()->route('dashboard.articles')->with('success', 'Article added');
        } else {
            return redirect()->back()->with('error', 'Adding article failed');
        }
    }
}

The form:

<form method="POST" action="{{ route('dashboard.articles.add') }}" enctype="multipart/form-data" novalidate>
    @csrf
    <div class="row mb-2">
            <label for="title" class="col-md-12">{{ __('Title') }}</label>

            <div class="col-md-12 @error('title') has-error @enderror">
                    <input id="title" type="text" placeholder="Title" class="form-control @error('title') is-invalid @enderror" name="title" value="{{ old('title') }}" autocomplete="title" autofocus>

                    @error('title')
                        <span class="invalid-feedback" role="alert">
                            <strong>{{ $message }}</strong>
                        </span>
                    @enderror
            </div>
    </div>

    <div class="row mb-2">
            <label for="short_description" class="col-md-12">{{ __('Short description') }}</label>

            <div class="col-md-12 @error('short_description') has-error @enderror">
                    <input id="short_description" type="text" placeholder="Short description" class="form-control @error('short_description') is-invalid @enderror" name="short_description" value="{{ old('short_description') }}" autocomplete="short_description" autofocus>

                    @error('short_description')
                        <span class="invalid-feedback" role="alert">
                            <strong>{{ $message }}</strong>
                        </span>
                    @enderror
            </div>
    </div>

    <div class="row mb-2">
        <label for="category" class="col-md-12">{{ __('Category') }}</label>
    
        <div class="col-md-12 @error('category_id') has-error @enderror">
    
            <select name="category_id" id="category" class="form-control @error('category_id') is-invalid @enderror">
                <option value="0">Pick a category</option>
                @foreach($categories as $category)
                    <option value="{{ $category->id }}">{{ $category->name }}</option>
                @endforeach
            </select>
                
                @error('category_id')
                    <span class="invalid-feedback" role="alert">
                        <strong>{{ $message }}</strong>
                    </span>
                @enderror
        </div>
    </div>

    <div class="row mb-2">
        <div class="col-md-12 d-flex align-items-center switch-toggle">
                <p class="mb-0 me-3">Featured article?</p>
                <input class="mt-1" type="checkbox" name="featured" id="featured">
                <label class="px-1" for="featured">{{ __('Toggle') }}</label>
        </div>
    </div>

    <div class="row mb-2">
        <label for="image" class="col-md-12">{{ __('Article image') }}</label>
    
        <div class="col-md-12 @error('image') has-error @enderror">
            <input type="file" name="image" id="file" class="file-upload-btn">
    
            @error('image')
                <span class="invalid-feedback" role="alert">
                    <strong>{{ $message }}</strong>
                </span>
            @enderror
        </div>
    </div>

    <div class="row mb-2">
        <label for="content" class="col-md-12">{{ __('Content') }}</label>

        <div class="col-md-12 @error('content') has-error @enderror">

            <textarea name="content" id="content" class="form-control @error('content') is-invalid @enderror" placeholder="Content" cols="30" rows="6">{{ old('content') }}</textarea>

            @error('content')
                <span class="invalid-feedback" role="alert">
                    <strong>{{ $message }}</strong>
                </span>
            @enderror
        </div>
    </div>
    
    <div class="row mb-0">
            <div class="col-md-12">
                    <button type="submit" class="w-100 btn btn-primary">
                            {{ __('Save') }}
                    </button>
            </div>
    </div>
</form>

The routes:

// Article routes
Route::group(['prefix' => 'articles'], function() {
  Route::get('/', [ArticleController::class, 'index'])->name('dashboard.articles');
  Route::get('/new', [ArticleController::class, 'create'])->name('dashboard.articles.new');
  Route::post('/add', [ArticleController::class, 'save'])->name('dashboard.articles.add');
}); 

The problem:

Even though the form passes validation, and a redirect occurs, the record is not actually added to the articles table.

Laravel throws the error General error: 1366 Incorrect integer value: 'on' for column 'featured' at row 1.

What is my mistake?



Solution 1:[1]

When dealing with checkboxes you need to know that;

a) you cannot use required since the checkbox is not sent with the form data if it is unchecked.

b) the default value is 'on' but actually, the presence of the field in the data means it was checked so what I normally do is like;

'featured' => $request->has('featured')

this will return a boolean true or false, suitable for storing in your db

So you could write

        // Turn the 'featured' field value into a tiny integer
        //$fields['featured'] = $request->get('featured') == 'on' ? 1 : 0;

        // If no image is uploaded, use default.jpg
        $fields['image'] = $request->get('image') == '' ? 'default.jpg' : $imageName;

        // Data to be added
        $form_data = [
            'user_id' => Auth::user()->id,
            'category_id' => $fields['category_id'],
            'title' => $fields['title'],
            'slug' => Str::slug($fields['title'], '-'),
            'short_description' => $fields['short_description'],
            'content' => $fields['content'],
            'featured' => $request->has('featured'),
            'image' => $fields['image']
        ];

Solution 2:[2]

Your column is defined as a tinyInteger that can only accept a single digit, but you seem to be passing a string on to it.

$table->tinyInteger('featured')->default('0');

General error: 1366 Incorrect integer value: 'on' for column 'featured' at row 1

Try setting the value of your checkbox to 1.

<input class="mt-1" type="checkbox" name="featured" id="featured" value="1">

Edit, make sure to add boolean to your validation rules after adding the value to your input element.

private $rules = [
    'category_id' => 'required|exists:article_categories,id',
    'title' => 'required|string|max:255',
    'short_description' => 'required|string|max:255',
    'image' =>  'image|mimes:jpeg,png,jpg|max:2048',
    'content' => 'required|string',
    'featured' => 'required|boolean'
];

Solution 3:[3]

In case it helps anyone, here is the working code for creating an article:

The routes:

// Article routes
Route::group(['prefix' => 'articles'], function() {
  Route::get('/', [ArticleController::class, 'index'])->name('dashboard.articles');
  Route::get('/new', [ArticleController::class, 'create'])->name('dashboard.articles.new');
  Route::post('/add', [ArticleController::class, 'save'])->name('dashboard.articles.add');
}); 

In the controller:

namespace App\Http\Controllers\Dashboard;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
use App\Models\ArticleCategory;
use App\Models\Article;
use Illuminate\Http\Request;

class ArticleController extends Controller
{
    
    private $rules = [
        'category_id' => 'required|exists:article_categories,id',
        'title' => 'required|string|max:255',
        'short_description' => 'required|string|max:255',
        'image' =>  'image|mimes:jpeg,png,jpg|max:2048',
        'content' => 'required|string'
    ];

    private $messages = [
        'category_id.required' => 'Please pick a category for the article',
        'title.required' => 'Please provide a title for the article',
        'short_description.required' => 'The article needs a short description',
        'content.required' => 'Please add content'
    ];
    
    public function categories() {
        return ArticleCategory::all();
    }
    
    public function index() {
        $articles = Article::orderBy('id', 'desc')->paginate(10);

        return view('dashboard/articles',
            ['articles' => $articles]
        );
    }

    public function create() {
        // Load the view and populate the form with categories
        return view('dashboard/add-article',
            ['categories' => $this->categories()]
        );
    }

    public function save(Request $request) {

        // Validate form (with custom messages)
        $validator = Validator::make($request->all(), $this->rules, $this->messages);

        if ($validator->fails()) {
            return redirect()->back()->withErrors($validator->errors())->withInput();
        }

        $fields = $validator->validated();

        // Upload article image
        $current_user = Auth::user();

        if (isset($request->image)) {
            $imageName = md5(time()) . $current_user->id . '.' . $request->image->extension();
            $request->image->move(public_path('images/articles'), $imageName);
        }

        // Turn the 'featured' field value into a tiny integer
        $fields['featured'] = $request->get('featured') == 'on' ? 1 : 0;

        // If no image is uploaded, use default.jpg
        $fields['image'] = $request->get('image') == '' ? 'default.jpg' : $imageName;

        // Data to be added
        $form_data = [
            'user_id' => Auth::user()->id,
            'category_id' => $fields['category_id'],
            'title' => $fields['title'],
            'slug' => Str::slug($fields['title'], '-'),
            'short_description' => $fields['short_description'],
            'content' => $fields['content'],
            'featured' => $fields['featured'],
            'image' => $fields['image']
        ];

        // Insert data in the 'articles' table
        $query = Article::create($form_data);

        if ($query) {
            return redirect()->route('dashboard.articles')->with('success', 'Article added');
        } else {
            return redirect()->back()->with('error', 'Adding article failed');
        }
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3