laravel / Mongodb - How to insert new key/ value to an existing document - php

I have this document:
Now I want to add a new key / value to that document which id is : 34401
My key / value is like an array:
$data = [
'id_project' => $id_project,
'id_product' => $product_id,
'link' => $link,
'id_product_competitor' => $id_competitor,
'link_competitor' => ''
];
what I am doing is this:
$insert_to_mongodb = DB::connection('mongodb')
->collection( 'products_' . $id_project . '_' . $id_competitor )
->insert($data);
Here this 'products_' . $id_project . '_' . $id_competitor is products_1_23 as collection name.
After run this code Its inserting newly document but I want to add ths key/value to an existing document.

I belive the better way is to create a model for the mongodb collection,
install Jenssegers\Mongodb using the command composer require jenssegers/mongodb, reference link
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
class ProductsModel extends Eloquent{
/**
* Connection name
*
*/
protected $connection = 'mongodb';
/**
* Collection name
*
*/
protected $collection = 'products';
}
Now you can insert or update using the eloquent way
ProductsModel::insert($data);
ProductsModel::where('_id',$id)->update($data);

Well, I have soleve it by following way:
$insert_to_mongodb = DB::connection('mongodb')
->collection( 'products_' . $id_project . '_' . $id_competitor )
->where('id', (int) $product_id)
->update($data, ['upsert' => true]);

Related

Laravel 5.5 Eloquent - Article::find returning null

I have two files,
CommentTransformer
And
ImageTransformer
In the first file, CommentTransformer, I can retrieve which Article it belongs to by doing this:
$article = Article::find($comment->article_id);
I'm doing the same exact thing in my ImageTransformer, but it returns null. Even if instead of using Article::find($image->article_id) I use Article::find(1) I still get a null result!
Here's the full code:
namespace App\Transformers;
use App\Article;
use App\User;
use App\Image;
use League\Fractal\TransformerAbstract;
class ImageTransformer extends TransformerAbstract
{
/**
* A Fractal transformer.
*
* #return array
*/
public function transform(Image $image)
{
$user = User::findOrFail($image->user_id);
$article = Article::find($image->article_id);
// $userArticle = User::find($article->user_id);
return [
'id' => (int) $image->id,
'original_filename' => $image->original_filename,
'filename' => $image->filename,
'size' => (int) $image->size,
'path' => url('/') . "/" . $image->path . '/' . $image->filename,
'posted_by_username' => $user->name,
'article' => $article //if I call $article->id it returns "Trying to get property of non-object" as it is, it returns null
];
}
}
This is the response:
{
"data": [
{
"id": 1,
"original_filename": "ptd.jpg",
"filename": "f11bbe288649e76ec3b694890160abf930601aed.jpeg",
"size": 103297,
"path": "http:\/\/192.168.1.85:1299\/uploads\/f11bbe288649e76ec3b694890160abf930601aed.jpeg",
"posted_by_username": "josh beck",
"article": null
}
]
}
I assume that this one returns null because findOrFail() would throw an
exception:
$article = Article::find($image->article_id);
If this returns null, it means there is no Article with the specified ID. You can check ID with dd($image->article_id);
Also, you can just load the data by using load() method:
$image->load(['user', 'article']);

Laravel 5 collection issue: Where not equal to

I am currently working on a modal where a user can insert an excel file. The task of the system is to upload and/or add a new database record if the records are new or identical to what exists in the database. BUT it also needs a delete function for getting rid of those records where the slug column is not identical to the name column.
At the moment I am using Laravel 5.3, and this is my controller as it is now:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Product;
use App\Http\Requests;
use Illuminate\Support\Facades\DB;
use Input;
use Maatwebsite\Excel\Facades\Excel;
class ProductsController extends Controller {
public function importExcel(Request $request) {
if (Input::hasFile('productFile')) {
$path = Input::file('productFile')->getRealPath();
$checkbox = Input::get('productCheckbox');
$data = Excel::load($path, function($reader) {
})->get();
if (!empty($data) && $data->count()) {
foreach ($data as $key => $value) {
$product = Product::all()->where('slug', $value->slug)->first();
$product_false = Product::all()->where('slug', '!=' , 'name')->get();
if ($product_false !== null){
//delete row if slug does not matches name
dd($product_false);
}
The dd above returns all products, so the collection query is not working properly (see below for the raw SQL that I am trying to run in this collection)
if ($product !== null) {
//update row if exist
$product->name = $value->name;
$product->description = $value->description;
$product->price = $value->price;
$product->save();
} else {
//add new row if not exist
$product = new Product;
$product->slug = $value->slug;
$product->name = $value->name;
$product->description = $value->description;
$product->price = $value->price;
$product->save();
}
}
header("Location: /products");
}
}
}
}
This is the Product model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'slug', 'name', 'description', 'price',
];
}
Here is the PHPMyAdmin raw SQL (which works) that I basically am looking for to use in the collection:
SELECT * FROM `products` WHERE `slug` != `name`
I hope someone can help me out from this pit. I have been sailing the waves of the internet for about 12 hours now just to get this done.
~ nitsuJ
Collections, eloquent and query builder are not the same. Collection provide a bunch of methods to work on arrays, rather then on the database or model.
In collection context whereNot() is not available.
but the same function can be achieved through whereNotIn('key', [value])
collect([
[
'name' => 'foo',
'rank' => 2
],[
'name' => 'bar',
'rank' => 3
],[
'name' => 'foobar',
'rank' => 4
],
])->whereNotIn('rank', [4])
same as where rank not in (4)
Change
$product = Product::all()->where('slug', $value->slug)->first();
$product_false = Product::all()->where('slug', '!=' , 'name')->get();
Into
$product = Product::where('slug', $value->slug)->first();
$product_false = Product::where('slug', '!=' , 'name')->get();
Try this
$product = Product::where('slug', $value->slug)->first();
$product_false = Product::whereRaw('slug != name')->get();
Simple where won't work as it compares products.slug with "name"(string).
I managed to solve it.
$data = Excel::load($path, function($reader) {
$importedSlugs = $data->select(array('slug'))->toArray();
//collection of imported slugs
$collectionOfImportedSlugs = collect($importedSlugs)->flatten()->all();
//get all product slugs
$productSlugs = Product::all()->pluck('slug');
//get all different slugs!
$diffSlugsArray = $productSlugs->diff($collectionOfImportedSlugs)->all();
//dd($diffSlugsArray);
foreach ($diffSlugsArray as $diffSlug) {
$product_false = Product::all()->where('slug', $diffSlug)->first();
echo $product_false->slug . 'has been deleted!';
$product_false->delete();
}
})->get();

Laravel Unit Testing, how to "seeInDatabase" soft deleted row?

I'm working on a small unit test where I soft delete a row. To mark the test as successful I have to find that row with:
a given ID and
deleted_at column should not be null.
I can fulfil first condition - because obviously I know the ID.
Unfortunately I don't know how to tell seeInDatabase method that I expect deleted_at not to be null:
$this->seeInDatabase(
'diary_note_categories',
[
'id' => 'a7e35ad0-6f00-4f88-b953-f498797042fc',
'deleted_at' => null // should be is not null, like <> or != or whatever
]
);
Any hints?
'deleted_at <>' => null breaks
'deleted_at' => ['!=' => null] breaks as well
I did it in this way:
$this->seeInDatabase('diary_note...',['id' => 'a7e35ad0'])
->notSeeInDatabase('diary_note...',['id' => 'a7e35ad0','deleted_at'=>null]);
So I'm checking in two steps
I check if there is a record with our id in the table
I check if there is no record with our id and deleted_at = null in the table
It's not currently possible. Both seeInDatabase and notSeeInDatabase just pass the array directly to the where method of the query builder and that doesn't understand how to deal with anything other than = when passed an array.
https://github.com/laravel/framework/blob/2b4b3e3084d3c467f8dfaf7ce5a6dc466068b47d/src/Illuminate/Database/Query/Builder.php#L452
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
// If the column is an array, we will assume it is an array of key-value pairs
// and can add them each as a where clause. We will maintain the boolean we
// received when the method was called and pass it into the nested where.
if (is_array($column)) {
return $this->whereNested(function ($query) use ($column) {
foreach ($column as $key => $value) {
$query->where($key, '=', $value);
}
}, $boolean);
}
// ...
}
Option 1 - Add the following code to your TestCase class which you extend your test cases from
Gist: https://gist.github.com/EspadaV8/73c9b311eee96b8e8a03
<?php
/**
* Assert that a given where condition does not matches a soft deleted record
*
* #param string $table
* #param array $data
* #param string $connection
* #return $this
*/
protected function seeIsNotSoftDeletedInDatabase($table, array $data, $connection = null)
{
$database = $this->app->make('db');
$connection = $connection ?: $database->getDefaultConnection();
$count = $database->connection($connection)
->table($table)
->where($data)
->whereNull('deleted_at')
->count();
$this->assertGreaterThan(0, $count, sprintf(
'Found unexpected records in database table [%s] that matched attributes [%s].', $table, json_encode($data)
));
return $this;
}
/**
* Assert that a given where condition matches a soft deleted record
*
* #param string $table
* #param array $data
* #param string $connection
* #return $this
*/
protected function seeIsSoftDeletedInDatabase($table, array $data, $connection = null)
{
$database = $this->app->make('db');
$connection = $connection ?: $database->getDefaultConnection();
$count = $database->connection($connection)
->table($table)
->where($data)
->whereNotNull('deleted_at')
->count();
$this->assertGreaterThan(0, $count, sprintf(
'Found unexpected records in database table [%s] that matched attributes [%s].', $table, json_encode($data)
));
return $this;
}
Option 2 - Install the following composer package
This composer package is the exact same code as above, but packaged up for Composer.
composer require kirkbater/soft-deletes
Then use it inside of your specific test class:
<?php
use Kirkbater\Testing\SoftDeletes;
class MyTestClass extends TestClass {
use SoftDeletes;
}
This is an old question, but for those using more recent versions of Laravel (5.4 and above), there is now an assertSoftDeleted assertion: documentation.
So the answer to the original question would now be:
$this->assertSoftDeleted('diary_note_categories', [
'id' => 'a7e35ad0-6f00-4f88-b953-f498797042fc'
]);
Assert the given record has been deleted (Laravel 5.4 and above).
assertSoftDeleted(string|Model $table, array $data = [], string|null $connection = null)
Example with id:
$this->assertSoftDeleted('table_name', ['id'='value'])
Example with model:
$user = User::factory()->create();
$user->delete();
$this->assertSoftDeleted($user);
I used in Laravel 6
$this->assertDatabaseMissing('stores', [
'id' => $test_data['store']->id, 'deleted_at' => null
]);
$this->assertDatabaseHas('stores', ['id' => $id]);
It is not tested, but try like this :
$this->seeInDatabase(
'diary_note_categories',
[
'id' => 'a7e35ad0-6f00-4f88-b953-f498797042fc',
'deleted_at' => ['deleted_at' ,'!=', null ] // should be is not null, like <> or != or whatever
]
);

Handling Mysql Spatial datatypes in Laravel Eloquent ORM

How to handle mysql spatial datatypes in eloquent ORM?, This include how to create migration, insert spatial data and performing spatial query's. If there is not actual solutions exists, is there any workarounds?
A workaround I have implemented a while ago is to have a latitude and longitude fields on the model with the following validations (see Validator class):
$rules = array('latitude' => 'required|numeric|between:-90,90',
'longitude'=>'required|numeric|between:-180,180',)
The magic comes on the boot method of the model, which sets the correct value of the spatial point field:
/**
* Boot method
* #return void
*/
public static function boot(){
parent::boot();
static::creating(function($eloquentModel){
if(isset($eloquentModel->latitude, $eloquentModel->longitude)){
$point = $eloquentModel->geoToPoint($eloquentModel->latitude, $eloquentModel->longitude);
$eloquentModel->setAttribute('location', DB::raw("GeomFromText('POINT(" . $point . ")')") );
}
});
static::updated(function($eloquentModel){
if(isset($eloquentModel->latitude, $eloquentModel->longitude)){
$point = $eloquentModel->geoToPoint($eloquentModel->latitude, $eloquentModel->longitude);
DB::statement("UPDATE " . $eloquentModel->getTable() . " SET location = GeomFromText('POINT(" . $point . ")') WHERE id = ". $eloquentModel->id);
}
});
}
About migrations, like #jhmilan says you can always use the Schema::create and DB::statement methods to customize the migration.
Schema::create('locations', function($table){
$table->engine = "MYISAM";
$table->increments('id')->unsigned();
$table->decimal('latitude', 10, 8);
$table->decimal('longitude', 11, 8);
$table->timestamps();
});
/*Espatial Column*/
DB::statement('ALTER TABLE locations ADD location POINT NOT NULL' );
/*Espatial index (MYISAM only)*/
DB::statement( 'ALTER TABLE locations ADD SPATIAL INDEX index_point(location)' );
It is available to use https://github.com/grimzy/laravel-mysql-spatial
you may use:
namespace App;
use Illuminate\Database\Eloquent\Model;
use Grimzy\LaravelMysqlSpatial\Eloquent\SpatialTrait;
/**
* #property \Grimzy\LaravelMysqlSpatial\Types\Point $location
*/
class Place extends Model
{
use SpatialTrait;
protected $fillable = [
'name',
];
protected $spatialFields = [
'location',
];
}
then you are able to run queries on the 'location' field.
to store model you may use:
$place1 = new Place();
$place1->name = 'Empire State Building';
$place1->location = new Point(40.7484404, -73.9878441);
$place1->save();
to retrieving a model you should use:
$place2 = Place::first();
$lat = $place2->location->getLat(); // 40.7484404
$lng = $place2->location->getLng(); // -73.9878441

Laravel orderBy on a relationship

I am looping over all comments posted by the Author of a particular post.
foreach($post->user->comments as $comment)
{
echo "<li>" . $comment->title . " (" . $comment->post->id . ")</li>";
}
This gives me
I love this post (3)
This is a comment (5)
This is the second Comment (3)
How would I order by the post_id so that the above list is ordered as 3,3,5
It is possible to extend the relation with query functions:
<?php
public function comments()
{
return $this->hasMany('Comment')->orderBy('column');
}
[edit after comment]
<?php
class User
{
public function comments()
{
return $this->hasMany('Comment');
}
}
class Controller
{
public function index()
{
$column = Input::get('orderBy', 'defaultColumn');
$comments = User::find(1)->comments()->orderBy($column)->get();
// use $comments in the template
}
}
default User model + simple Controller example; when getting the list of comments, just apply the orderBy() based on Input::get().
(be sure to do some input-checking ;) )
I believe you can also do:
$sortDirection = 'desc';
$user->with(['comments' => function ($query) use ($sortDirection) {
$query->orderBy('column', $sortDirection);
}]);
That allows you to run arbitrary logic on each related comment record. You could have stuff in there like:
$query->where('timestamp', '<', $someTime)->orderBy('timestamp', $sortDirection);
Using sortBy... could help.
$users = User::all()->with('rated')->get()->sortByDesc('rated.rating');
Try this solution.
$mainModelData = mainModel::where('column', $value)
->join('relationModal', 'main_table_name.relation_table_column', '=', 'relation_table.id')
->orderBy('relation_table.title', 'ASC')
->with(['relationModal' => function ($q) {
$q->where('column', 'value');
}])->get();
Example:
$user = User::where('city', 'kullu')
->join('salaries', 'users.id', '=', 'salaries.user_id')
->orderBy('salaries.amount', 'ASC')
->with(['salaries' => function ($q) {
$q->where('amount', '>', '500000');
}])->get();
You can change the column name in join() as per your database structure.
I made a trait to order on a relation field. I had this issues with webshop orders that have a status relation, and the status has a name field.
Example of the situation
Ordering on with "joins" of eloquent models is not possible since they are not joins. They are query's that are running after the first query is completed. So what i did is made a lil hack to read the eloquent relation data (like table, joining keys and additional wheres if included) and joined it on the main query. This only works with one to one relationships.
The first step is to create a trait and use it on a model. In that trait you have 2 functions. The first one:
/**
* #param string $relation - The relation to create the query for
* #param string|null $overwrite_table - In case if you want to overwrite the table (join as)
* #return Builder
*/
public static function RelationToJoin(string $relation, $overwrite_table = false) {
$instance = (new self());
if(!method_exists($instance, $relation))
throw new \Error('Method ' . $relation . ' does not exists on class ' . self::class);
$relationData = $instance->{$relation}();
if(gettype($relationData) !== 'object')
throw new \Error('Method ' . $relation . ' is not a relation of class ' . self::class);
if(!is_subclass_of(get_class($relationData), Relation::class))
throw new \Error('Method ' . $relation . ' is not a relation of class ' . self::class);
$related = $relationData->getRelated();
$me = new self();
$query = $relationData->getQuery()->getQuery();
switch(get_class($relationData)) {
case HasOne::class:
$keys = [
'foreign' => $relationData->getForeignKeyName(),
'local' => $relationData->getLocalKeyName()
];
break;
case BelongsTo::class:
$keys = [
'foreign' => $relationData->getOwnerKeyName(),
'local' => $relationData->getForeignKeyName()
];
break;
default:
throw new \Error('Relation join only works with one to one relationships');
}
$checks = [];
$other_table = ($overwrite_table ? $overwrite_table : $related->getTable());
foreach($keys as $key) {
array_push($checks, $key);
array_push($checks, $related->getTable() . '.' . $key);
}
foreach($query->wheres as $key => $where)
if(in_array($where['type'], ['Null', 'NotNull']) && in_array($where['column'], $checks))
unset($query->wheres[$key]);
$query = $query->whereRaw('`' . $other_table . '`.`' . $keys['foreign'] . '` = `' . $me->getTable() . '`.`' . $keys['local'] . '`');
return (object) [
'query' => $query,
'table' => $related->getTable(),
'wheres' => $query->wheres,
'bindings' => $query->bindings
];
}
This is the "detection" function that reads the eloquent data.
The second one:
/**
* #param Builder $builder
* #param string $relation - The relation to join
*/
public function scopeJoinRelation(Builder $query, string $relation) {
$join_query = self::RelationToJoin($relation, $relation);
$query->join($join_query->table . ' AS ' . $relation, function(JoinClause $builder) use($join_query) {
return $builder->mergeWheres($join_query->wheres, $join_query->bindings);
});
return $query;
}
This is the function that adds a scope to the model to use within query's. Now just use the trait on your model and you can use it like this:
Order::joinRelation('status')->select([
'orders.*',
'status.name AS status_name'
])->orderBy('status_name')->get();

Categories