I'm currently trying to create a eloquent relationship between two models in my database, one in sql and on in mongodb:
<?php
namespace App\Models\Trend;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Jenssegers\Mongodb\Eloquent\HybridRelations;
class Location extends Model
{
use HasFactory, HybridRelations;
protected $table = 'trends';
protected $fillable = [
'woeid',
'name',
];
/**
* #return \Illuminate\Database\Eloquent\Relations\HasMany|\Jenssegers\Mongodb\Relations\HasMany
*/
public function trends()
{
return $this->hasMany(Trend::class);
}
}
<?php
namespace App\Models\Trend;
use App\Models\Scopes\AscendingOrderScope;
use App\Models\Team;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Jenssegers\Mongodb\Eloquent\Model;
class Trend extends Model
{
use HasFactory;
protected $connection = 'mongodb';
protected $collection = 'trends';
protected $fillable = ['trends'];
/**
* #return void
*/
protected static function booted()
{
static::addGlobalScope(new AscendingOrderScope);
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function location()
{
return $this->belongsTo(Location::class);
}
}
Once i try to call the ->location relationship on the Trend model I only get the following error:
PHP Error: Call to a member function prepare() on null in /home/fmk/Code/socmint/vendor/laravel/framework/src/Illuminate/Database/Connection.php on line 413
In the database the location_id field is corretly set with an id of an existing Location model.
Since it is a hybrid relation I also added the HybridRelation trait as described in the packages example (I'm nearly doing the exact stuff as in the example).
The other way around (calling location->trends is working without a problem)...
What am I missing?
Thanks for your help,
FMK
Found the solution after debugging through the callstack.
Laravel seems to does not know which connection to use on the relationship model so it defaults back to the one of the model with the relationship defined.
In my case the trend was using the mongodb connection and laravel tried to search for the location in this connection aswell.
Solution was was to define the connection on the SQL Trend model aswell:
protected $connection = 'mysql';
Related
I'm on Laravel 8 with Livewire, currently have 3 models, Category, SubCategory and MenuItem for 3 tables. All the above models have separate livewire controllers and have the code for the CRUD operations respectively. I have separate views and routes to edit the above tables and they all have a eloquent relationship between each other. Now what I need to do here to is, I need to display all the three tables in a single view to carry out the CRUD operations.
I tried to achieve this by using the sub-view function, to pass the view and make the variables available to the specific view, but it didn't work out and I think it isn't the way to do it, was just trying to figure a workaround. I'm mentioning my models down below for referencing. Please help me with this. Thanks a lot for your time!
App\Models\Category
class Category extends Model
{
use HasFactory;
protected $table = "categories";
protected $fillable = ['sub_category_name'];
public function SubCategories() {
return $this->hasMany(SubCategory::class, 'category_id');
}
public function MenuItems() {
return $this->hasManyThrough(
'MenuItem::class',
'SubCategory::class',
'sub_category_id',
'category_id'
);
}
}
App\Models\SubCategory
class SubCategory extends Model
{
use HasFactory;
protected $table = "sub_categories";
protected $fillable = ['category_id', 'sub_category_name'];
public function Categories() {
return $this->belongsTo(Category::class, 'category_id');
}
public function MenuItems() {
return $this->hasMany(MenuItem::class, 'sub_category_id');
}
}
App\Models\MenuItem
class MenuItem extends Model
{
use HasFactory;
protected $table = "menu_items";
protected $fillable = ['sub_category_id', 'item_name', 'item_description'];
public function SubCategories() {
return $this->belongsTo(SubCategory::class, 'sub_category_id');
}
}
This is what I tried to achieve the said result. As I needed to include the view with the sub category table to the menu items table view. I made the variables available to that specific view.
Resources\Views\Livewire\Menu-Item
<div>
#include('livewire.sub-category')
</div>
App\Providers\AppServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use App\Models\SubCategory;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
View::composer('livewire.menu-item', function ($view) {
$view::with('sub_category_name', SubCategory::orderBy('sub_category_name')->get());
});
}
}
I managed to solve the above problem, using livewire's component feature. Created 3 separate component for 3 separate tables, and finally used all the three components in the master-menu.blade.php file. Working with livewire and laravel is a treat.
I'm creating a school platform where students, teachers,... can login using their credentials. To reduce duplicate data I did not make a separate table called students, instead I keep all the data in the users table.
To know if a user is a student I a have a table that is called enrolments, in this table a user_id , schoolyear_id and class_id is stored.
I already made a student model that refers to the users table, but how can I ensure that this model only passes students?
EER:
Student.php:
<?php
namespace App;
class Student extends User
{
protected $table= 'users';
public function enrollments(){
return $this->belongsToMany(Enrollment::class);
}
}
User.php:
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;
use Illuminate\Support\Facades\Auth;
class User extends Authenticatable
{
use Notifiable;
use HasRoles;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'first_name','last_name', 'password'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function profiles(){
return $this->hasOne(Profile::class);
}
}
What I want to achieve is that when I call the Student::all(); function I get all the users who are enrolled in the school,hence students.
Check out model events: https://laravel.com/docs/5.5/eloquent#events
You should be able to drop this into your student model for a test:
protected static function boot(){
parent::boot();
static::retrieved(function($thisModel){
if($thisModel->isNotAStudent or whatever logic you need){
return false;
}
}
}
I'm still on 5.4, which does not have the retrieved model event built in, but returning false generally stops the call from going through. So applying that logic to the retrieved event may stop that model instance from being returned if it is not a student, but allow students to be returned. Just a thought.
Your provided solution lead me in the right direction. My problem is solved by using global scope:
<?php
namespace App;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class Student extends User
{
protected $table= 'users';
protected static function boot()
{
parent::boot();
static::addGlobalScope('student', function (Builder $builder) {
$builder->whereExists(function ($query) {
$query->select(DB::raw(1))
->from('enrollments')
->whereRaw('enrollments.user_id = users.id');
});
});
}
public function enrollments(){
return $this->belongsToMany(Enrollment::class);
}
}
For a unit test in laravel 5.1 I am trying to test a cascading delete function of the Client model, which, with the recursive flag set, should also delete all users associated with the client.
I want to use a mock user adn test only wether the delete function on the user is called, so I wont have to use the database, and to apply the same principle to other tests in the future.
at the moment the test fails because I cannot find a way to make the client model retreive the associated user without firing a query.
I think I need to mock the hasMany relation defining function of the client, but i have not found a way.
the client model:
class Client extends Model
{
protected $table = 'clients';
protected $fillable = [];
public function casDelete($recursive = false){
if($recursive) {
$users = $this->users()->get();
foreach($users as $user) {
$user->casDelete($recursive);
}
}
$this->delete();
}
public function users(){
return $this->hasMany('App\User');
}
}
the user model:
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable, CanResetPassword;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['name', 'email', 'password', 'client_id'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = ['password', 'remember_token'];
public function casDelete($recursive = false){
$this->delete();
}
public function client(){
return $this->belongsTo('App\Client');
}
}
the test:
class ClientModelTest extends TestCase
{
use DatabaseTransactions;
function testCasDelete(){
$client = factory(Client::class)->create();
$user = factory(User::class)->make(['client_id' => $client->id]);
$observer = $this->getMock('user');
$observer->expects($this->once())->method('casDelete');
$client->casDelete(true);
}
}
When you are using DatabaseTransactions, this mean you want to persist the data in the database. And when you are using create() from the factory still you are using the database, so either you should not use the database at all or if you want you to use the database then you can simply solve the problem. but what I can suggest is this solution, which I'm not using the database init.
$user = \Mockery::mock();
$user->shouldReceive('casDelete')->andReturnNull();
$queryMock = \Mockery::mock();
$queryMock->shouldReceive('get')->andReturn([$user]);
$clientMock = \Mockery::mock(Client::class)->makePartial();
$clientMock->shouldReceive('users')->andreturn($queryMock);
$clientMock->casDelete(true);
This way you can be sure that you have called casDelete on each user model.
this is a very simple test case, you can extend it in the way you like base on what you want to achieve.
I'm trying to create an additional attribute method in a model which appends a ratings label column to an existing rating model. I have the exact same code working for another model which adds click data. I'm getting an Undefined index: rating_label error - can't figure out why? Any help much appreciated.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Rating extends Model
{
protected $table = 'rating';
protected $primaryKey = 'rating_id';
protected $appends = array('rating_label');
/**
* Define a 1-many inverse relationship in eloquent
*/
public function Article()
{
return $this->belongsTo(Article::class, 'article_id');// (Model, primary_key)
}
/**
* Add additional column to Rating model for use in ClassifyingService class
*/
public function getRatingLabelAttribute()//accessor getter/setter
{
return $this->attributes['rating_label'];
}
}
I'm studing about Repository Design Pattern in Laravel and I'm using https://github.com/andersao/l5-repository to do it.
I think i install success in my project . But when i run code with repository i have some problem
SQLSTATE[42S02]: Base table or view not found: 1146 Table
'test.nhanviens' doesn't exist (SQL: select * from nhanviens)
Table in my database is Nhanvien not Nhanviens
Here in my code
NhanvienRepository.php
<?php
namespace App\Repositories;
use Prettus\Repository\Contracts\RepositoryInterface;
/**
* Interface NhanvienRepository
* #package namespace App\Repositories;
*/
interface NhanvienRepository extends RepositoryInterface
{
//
}
NhanvienRepositoryEloquent.php
<?php
namespace App\Repositories;
use Prettus\Repository\Eloquent\BaseRepository;
use Prettus\Repository\Criteria\RequestCriteria;
use App\Repositories\NhanvienRepository;
use App\Entities\Nhanvien;
use App\Validators\NhanvienValidator;
/**
* Class NhanvienRepositoryEloquent
* #package namespace App\Repositories;
*/
class NhanvienRepositoryEloquent extends BaseRepository implements NhanvienRepository
{
/**
* Specify Model class name
*
* #return string
*/
public function model()
{
return Nhanvien::class;
}
/**
* Boot up the repository, pushing criteria
*/
public function boot()
{
$this->pushCriteria(app(RequestCriteria::class));
}
}
DataController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\nhanvien;
use App\Repositories\NhanvienRepository;
class DataController extends Controller
{
protected $repository;
public function __construct(NhanvienRepository $repository){
$this->repository = $repository;
}
public function DanhSach(){
var_dump($this->repository->all());
}
}
from App\Nhanvien.php Add this variable to the class:
protected $table = 'nhanvien';
Explanation: The "snake case", plural name of the class will be used as the table name unless another name is explicitly specified. So, in this case, Eloquent will assume the nhanvien model stores records in the nhanviens table.
As stated in the official Eloquent documentation you need to specifically set the table name in your Model definition. That is, in your App\Nhanvien.php file set the following:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Nhanvien extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'Nhanvien';
}
or use
protected $table = 'nhanvien';
instead if your table name is full lowercase.
Check your migration file, maybe you are using Schema::table, like this:
Schema::table('table_name', function ($table) {
// ... });
If you want to create a new table you must use Schema::create:
Schema::create('table_name', function ($table) {
// ... });
See the Laravel migration documentation for more information.
If you are using Schema::create then please provide the contents of your migration file.
In my case, I've got rid of similar error by executing the command
php artisan config:cache