I am using Yii together with PostgreSQL and PostGIS in my application. I have a model "User" with a field "Location". The content of the Location-field makes no sense what so ever to a human, but Postgres can convert the value of the Location-field and turn it into Longitude and Latitude using the ST_asText-method. I have created dummy fields in my model for longitude and latitude. When i save my model, i use the beforeSave and afterSave methods to set location to it's right value.
My problem now is that i want the latitude and longitude dummy fields to be populated upon model creation (with existing objects in the db). I was thinking that there might be something like beforeSelect and afterSelect that i can use to append the query with an extra calculated column, and therefore be able to process the values returned from that extra column after the query has been ran.
Is this possible somehow?
I was able to solve this pretty easy. I was looking for a beforeSelect and afterSelect method in the CActiveRecord class when i should have been looking for beforeFind and afterFind.
Here's how i did it. Any improvement suggestion are welcome :)
public function beforeFind(){
$criteria = new CDbCriteria;
$criteria->select = "*,ST_asText(location) as location";
$this->dbCriteria->mergeWith($criteria);
return parent::beforeFind();
}
public function afterFind(){
$location = str_replace('POINT(','',$this->location);
$location = str_replace(')','',$location);
$location = explode(" ", $location);
$this->lon = $location[0];
$this->lat = $location[1];
return parent::afterFind();
}
I met this problem with Yii2 and here is how I solved it.
Override find() method of AR and define your own ActiveQuery class:
public static function find()
{
$query = new MyActiveQueryClass(get_called_class());
return $query->select('*, ST_AsGeoJSON(location) AS location');
}
In afterFind() you will have geo-json and with that you can get lat/lng:
public function afterFind()
{
parent::afterFind();
$geoJson = json_decode($this->location, true);
$this->lat = $geoJson['coordinates'][0];
$this->lng = $geoJson['coordinates'][1];
}
That is it for Yii2. Remember to convert your lat/lng to correct object before saving. Mine was geography column. Here is a bonus:
public function beforeSave($insert)
{
//create field as expression
if(!empty($this->lat) && !empty($this->lng))
$this->location = new \yii\db\Expression("'POINT({$this->lat} {$this->lng})'");
return parent::beforeSave($insert);
}
Related
Backpack Crud have clone function. But it isn't work when our table has unique field column.
Backpack clone Documentation
When table has unique column how to clone it?
public function clone($id)
{
$this->crud->hasAccessOrFail('clone');
$this->crud->setOperation('clone');
$clonedEntry = $this->crud->model->findOrFail($id)->replicate();
return (string) $clonedEntry->push();
}
Recently I experienced the same problem. Here is my solution:
public function clone($id)
{
$this->crud->hasAccessOrFail('clone');
$this->crud->setOperation('clone');
$clonedEntry = $this->crud->model->findOrFail($id)->replicate();
// now resolve the value for unique attribute before save. e.g.
$slug = Str::slug($clonedEntry->name, '-');
$count = $this->crud->model->whereRaw("slug RLIKE '^{$slug}(-[0-9]+)?$'")->count();
$clonedEntry->slug = $count ? "{$slug}-{$count}" : $slug;
// when you are done, save changes
return (string) $clonedEntry->push();
}
You didn't specify anything about that "unique" attribute. Feel free to customize the resolver according your needs.
I'm retrieving data from Database like everyone else, but I'm facing a weird issue.
I'm using the slug in my table to retrieve the element's data but but I display the slug it gives me nulland using the famous dd()famous it shows up here is an example :
dd($element);
Result
dd($snippets->toArray());
Result
Table
Schema::create('elements', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id');
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->timestamps();
});
Eloquent
$snippets = Snippet::latest()->with('owner')->get();
Snippet Model
protected $primaryKey = 'slug';
public function owner()
{
return $this->belongsTo(User::class, 'user_id');
}
Is there any one who knows what is going on?
The problem was in the Primary Key Type, by default is set to int and have not set incrementing to false which is causing it to 'cast' it by the keyType. The keyType is set to 'int' unless set otherwise. (int)'es2015' == 0
protected $keyType = 'string';
Thanks everyone for help
I want to have _id in the database but want to output id when doing a query.
How can I achieve it?
You can try this (using an Accessor) :
Model
public function getIdAttribute() {
return $this->attributes['_id'];
}
Controller test
$user = User::find(1);
// this will call getIdAttribute which will return the `_id`
dd($user->id);
You can also override toArray() method if you want to show it :
Model
// ..
// getIdAttribute()
// ..
public function toArray()
{
$array = parent::toArray();
$array['id'] = $this->id;
unset($array['_id']);
return $array;
}
Controller Test
$user = User::find(1);
dd($user->toArray());
Another way to do this is using Transformers (http://fractal.thephpleague.com/transformers/). There is a service provider for Laravel here (https://github.com/gathercontent/laravel-fractal).
It will do that in a elegant way :)
Of course, if you need to do only with "id" field, I'll do like zorx told:
public function getIdAttribute() {
return $this->attributes['_id'];
}
But you'll probably put that in some BaseModel or abstractModel class, a parent for you models who need this.
I have Eloquent Event model, which is related towards multiple dates like this:
$event->dates // shows Collection of 8 Eloquent date models
After that i need to pick the only date, what is closest to current time. I know how to do this using query of raw SQL, or DB class. But isnt there any better solution? I dont want to jump into database for data, I already have.
Date format in eloquent models is surprisingly string.
You can use what we call in laravel mutators like this ->
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Event extends Model
{
public function dates()
{
return $this->hasMany('Date');
}
/**
* Get Dates for the event.
*
* #param string $value
* #return array
*/
public function getDates()
{
$dates = $this->dates()->getQuery()->orderBy('created_at', 'asc')->get();
return $dates;
}
}
Hope this helps.
UPDATE
I think now you can also directly do this in the model definition like this -
return $this->hasMany('Date')->orderBy('created_at', 'asc')
I was told to use automapper in the code below. I cannot get clarification for reasons that are too lengthy to go into. What object am I supposed to be mapping to what object? I don't see a "source" object, since the source is the database...
Would really appreciate any help on how to do this with automapper. Note, the actual fields are irrelevant, I need help with the general concept. I do understand how mapping works when mapping from one object to another.
public IQueryable<Object> ReturnDetailedSummaries(long orgId)
{
var summaries = from s in db.ReportSummaries
where s.OrganizationId == orgId
select new SummaryViewModel
{
Id = s.Id,
Name = s.Name,
AuditLocationId = s.AuditLocationId,
AuditLocationName = s.Location.Name,
CreatedOn = s.CreatedOn,
CreatedById = s.CreatedById,
CreatedByName = s.User.Name,
OfficeId = s.OfficeId,
OfficeName = s.Office.Name,
OrganizationId = s.OrganizationId,
OrganizationName = s.Organization.Name,
IsCompleted = s.IsCompleted,
isHidden = s.isHidden,
numberOfItemsInAuditLocations = s.numberOfItemsInAuditLocations,
numberOfLocationsScanned = s.numberOfLocationsScanned,
numberOfItemsScanned = s.numberOfItemsScanned,
numberofDiscrepanciesFound = s.numberofDiscrepanciesFound
};
return summaries;
}
It is a handy and a timesaver, especially if you use a one to one naming between translations layers. Here is how I use it.
For single item
public Domain.Data.User GetUserByUserName(string userName)
{
Mapper.CreateMap<User, Domain.Data.User>();
return (
from s in _dataContext.Users
where s.UserName==userName
select Mapper.Map<User, Domain.Data.User>(s)
).SingleOrDefault();
}
Multiple Items
public List<Domain.Data.User> GetUsersByProvider(int providerID)
{
Mapper.CreateMap<User, Domain.Data.User>();
return (
from s in _dataContext.Users
where s.ProviderID== providerID
select Mapper.Map<User, Domain.Data.User>(s)
).ToList();
}
It looks like you already have a model? SummaryViewModel?
If this isn't the DTO, then presumably you want to do:
Mapper.CreateMap<SummaryViewModel, SummaryViewModelDto>();
SummaryViewModelDto summaryViewModelDto =
Mapper.Map<SummaryViewModel, SummaryViewModelDto>(summaryViewModel);
AutoMapper will copy fields from one object to another, to save you having to do it all manually.
See https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
The source is your entity class ReportSummary, the target is SummaryViewModel:
Mapper.CreateMap<ReportSummary, SummaryViewModel>();
The best way to use AutoMapper in combination with an IQueryable data source is through the Project.To API:
var summaries = db.ReportSummaries.Where(s => s.OrganizationId == orgId)
.Project().To<SummaryViewModel>();
Project.To translates the properties in the target model straight to the selected columns in the generated SQL.
Mapper.Map, on the other hand, only works on in-memory collections, so you can only use it when you first fetch complete ReportSummary objects from the database. (In this case there may not be much of a difference, but in other cases it can be substantial).