Eloquent - Strange behavior on save - mysql

I'm saving a record and then returning the values from database and Eloquent is not returning all of the values.
Example:
If I have a MySQL table called names like this:
| ID | Date | Name |
|----------------------------|-------------------------------------|--------------|
| AUTO INCREMENT PRIMARY KEY | TIMESTAMP DEFAULT CURRENT_TIMESTAMP | VARCHAR(100) |
and I have an eloquent model called Names
and then I save and return like this:
function save($sName) {
$oName = new Names();
$oName->Name = $sName;
$oName->save();
$oReturn = new stdClass();
$oReturn->Name = $oName->Name;
$oReturn->Date = $oName->Date;
return $oReturn;
}
It returns:
echo json_encode(save("Test")); // {"Name": "Test", "Date": null}
Is this normal?

Yes that is normal. Eloquent doesn't save and then do another query to select all the data for the record it just inserted.

You can manually set field date, just add this code into your mode:
public static function boot()
{
static::creating(function ($model) {
$model->date = date('Y-m-d H:i:s');
});
}
You will avoid second DB request for selecting inserted item. Just be carefull with timezones when you're using date function.
Also you can use getAttributes() Eloquent method and get rid of stdClass:
function save($sName)
{
$oName = new Names();
$oName->Name = $sName;
$oName->save();
return $oName->getAttributes();
}

Related

Eloquent relation on data types (fields) table

Here my tables, User is the classic Auth table:
DataTypes
+----+--------------+
| id | field |
+----+--------------+
| 1 | address |
| 2 | mobile_phone |
| 3 | city |
+----+--------------+
UserData
+----+----------+--------------+---------+
| id | value | data_type_id | user_id |
+----+----------+--------------+---------+
| 1 | Milan | 3 | 1 |
| 2 | 99123233 | 2 | 1 |
+----+----------+--------------+---------+
My current crazy model:
class User extends Authenticatable {
public function field($field){
$field=DataType::where('field',$field)->first();
return $this->hasMany('App\UserData')->where('datatype_id',(isset($field->id) ? $field->id : 0));
}
}
Of course it works fine when I have to find values:
auth()->user()->field('mobile_phone')->get();
But how do I set or update a new mobile phone?
Another approach is link User and DataTypes as a many to many relationship and use a scope to handle it.
First of all, change UserData table to dataTypeUser, so you could use default eloquent relationships.
In any model put the belongsToMany relationship.
User Model
public function dataTypes()
{
return $this->belongsToMany('App\DataType')
->withPivot(['value']);
}
public function scopeField($query, $field)
{
return $query->whith(['dataTypes'=>function($q)use($field){
$q->where('data_types.field',$field);
}]);
}
DataType Model
public function users()
{
return $this->belongsToMany('App\User')
->withPivot(['value']);
}
To get some value you can use $field = $user->field('something')->first() to save new data could use $user->field('something')->updateExistingPivot($field->id,['value'=>$newValue]).
Anyway, if you don't have many data of same type attached to your user (more than one phone number for example), it could be a better approach to use one single table extending user migration, or at last an userData with columns for each datatype. In small applications you have no problems but as your application grows performance will be a problem with many tables and many relationships.
To avoid a long declaration, you could overwrite magic methods __get and __set. Both are declared in Illuminate\Database\Eloquent\Model so put in your User Model:
public function __get($key)
{
return $this->getAttribute($key) ?? $this->getAttributesFromPivot($key);
}
public function __set($key, $value)
{
if(in_array($key,array_keys($this->original)))
$this->setAttribute($key, $value);
else
$this->setAttributeFromPivot($key, $value);
}
public function setAttributeFromPivot($key, $value)
{
$this->dataTypes()->where('field',$key)->update(['value'=>$value]);
}
protected function getAttributesFromPivot($key)
{
return $this->dataTypes()
->where('field',$key)
// use this to get only one value direct
->first()->pivot->value ?? null;
// or use this to get all of them as array
// ->get()
// ->map(function($item){ return $item->pivot->value;}) ?? null;
}
In this approach, you could use $user->city to get field city or another one replacing ->city. Or you could use $user->address = 'foo'; to set a new value in pivot table. Note that it will update database directly.
Now, if you are not comfortable to overwrite those methods, you don't need to. Change the signatures of setAttributeFromPivot and getAttributeFromPivot to public function getField($key) and public function setField($key, $value). Now you can use them as common methods.

Symfony 4 Doctrine multiple table joins in one property

Very new to symfony and Doctrine. I have the following tables in my database.
mo_user
id | email | password
__________________________________
9144 | summer#h.com | !password!
mo_user_role
user_id| role_id
_________________
9144 | 5
mo_permission
id | namespace | name | description
______________________________________________
1 | admin | - | -
2 | users | - | -
3 | view_summary_report | - | -
4 | view_user_statement | - | -
mo_role_permission
role_id | permission_id
________________________
5 | 3
5 | 4
I am trying to return an array of the permissions of the current user in this case user with id = 9144 which should be array('view_summary_report','view_user_statement').
I have mapped all the tables to their corresponding entity classes. and in MoUser.php entity class which corresponds to mo_user table, I have a
permissions method which should return the array but my join from annotations is failing,
My getPermissions() method in MoUser.php
/**
* #var Collection|MoPermission[]
* #ORM\ManyToMany(targetEntity="App\Entity\MoPermission")
* #ORM\JoinTable(
* name="mo_user_role",
* joinColumns={#ORM\JoinColumn(name="user_id",referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id",referencedColumnName="id")}
* )
*/
private $permissions;
public function getPermissions()
{
$currentPermissions = array();
foreach ($this->permissions->toArray() as $index => $permission) {
$currentPermissions[] = $permission->getNamespace();
}
//Return default role if Roles are not assigned to this user.
if(count($currentPermissions)>0) {
return $currentPermissions;
} else {
return array('DEFAULT_PERMISSION');
}
}
So I figured out the raw sql to achieve what I wanted which is below, but I would like to know the Symfony/Doctrine annotated way of achieving the following raw SQL.
SELECT t0.id AS id_1, t0.namespace AS namespace_2, t0.name AS name_3, t0.description AS description_4
FROM mo_permission t0
LEFT JOIN mo_role_permission ON t0.id = mo_role_permission.permission_id
LEFT JOIN mo_user_role ON mo_role_permission.role_id = mo_user_role.role_id
WHERE mo_user_role.user_id = 9144;
I don't think there is a proper way to achieve what you're trying to do directly through property annotations with your current setup.
You could achieve what you want with one of these solution though :
One of mo_user_role and mo_role_permission is not needed, since none of them have additional field. You should just have a mo_user_permission table generated by a ManyToMany relationship between MoUser and MoPermission, which would grant you direct access to MoPermission from MoUser's getPermissions()
Another way would be to create a service which would have a GetPermissionsFromUser(MoUser $moUser) method (for example), calling the proper query from the entity's repository, which you would call when needed.
You could still achieve what you want in your getPermissions() method with your current setup, but you would have to loop through each relation's items to build your new result array manually.
e.g. for last point :
public function getPermissions() {
$permissions = [];
foreach($this->roles as $role) {
foreach($role->getPermissions() as $permission) {
permissions[] = $permission->getNamespace();
}
}
return $permissions;
}
This would assume you have a MoRole entity, which would make sense regarding your current setup, but you didn't mention it. Otherwise, same logic could still be applied though, it's just a naming matter.
I'm pretty sure that you could do that query using Doctrine (and a QueryBuilder) like...
use Doctrine\ORM\EntityRepository
class PermissionRepository extends EntityRepository
{
//....
/**
* #param UserInterface $user
* #return array|Permission[]
*/
public function getPermissionsForUser(UserInterface $user)
{
$queryBuilder = $this->createQueryBuilder('permission');
/**
* permissions will be in a multi-dimensional array
* with a single key per array of 'namespace'
*/
$permissions = $queryBuilder
->select('permission.namespace')
->join('permission.role', 'role')
->join('role.user', 'user')
->where($queryBuilder->expr()->eq('user', ':user'))
->setParameter('user', $user)
->getQuery()
->getArrayResult();
if (count($permissions) > 0) {
/**
* If there are any permissions found just return
* the 'namespace' property from each "sub-array"
*/
return array_column($permissions, 'namespace');
}
return ['DEFAULT_PERMISSION'];
}
//...
}
And then you would call it like..
$permissions = $repository->getPermissionsForUser($user);

How to insert one field of a table to another table in laravel 5?

I want to create image galleries for stuffs. So in Mysql I created two tables:
table 1 multiples contained field name (id, stuff_id, images ) which id is primary key and auto increment, stuff_id is index.
table 2 stuffs contained field name (stuff_id, name, detail) which stuff_id is primary key and auto increment.All I want is to upload a new stuff with 4 images,and be able to get stuff_id in table multiples.
Ex: If I upload a new stuff with 4 images with stuff_id =1 in table multiples should look like this::
|id||stuff_id||images|
|1| |1| |image1.jpg|
|2| |1| |image2.jpg|
|3| |1| |image3.jpg|
|4| |1| |image4.jpg|
And this is my code but it dose not do what I want (I have 2 models Stuffs and Multiple). It just insert record to tables but, in table multiples stuff_id it gets '0' 4 times.
public function upload_multiple(Request $request){
$stuff = new Stuffs();
$stuff->name = $request->input('name');
$stuff->detail = $request->input('detail');
$stuff->save();
$files = $request->file('images');
foreach ($files as $file){
$multiple = new Multiple(); //Multiple is a 'Model' not a table name
$destinationPath = 'uploads/';
$filename = $file->getClientOriginalName();
$upload_success = $file->move($destinationPath, $filename);
$multiple->images =$destinationPath.$filename;
$multiple->save();
}
So how can I do this in Laravel 5?
Many thank for help!
What you need is a One to Many relationship. Here's how you can set it up:
In your Model classes, add the following:
class Stuffs {
...
// A Stuff has many Mutliples
public function multiples() {
return $this->hasMany('App\Multiple');
}
}
class Multiple {
...
// A Multiple belongs to only one Stuff
public function stuff() {
return $this->belongsTo('App\Stuffs');
}
}
Then, while adding a Stuff, you can simply attach Multiples to it:
public function upload_multiple(Request $request) {
$stuff = new Stuffs();
$stuff->name = $request->input('name');
$stuff->detail = $request->input('detail');
$stuff->save();
$files = $request->file('images');
$multiples = [];
foreach ($files as $file) {
$multiple = new Multiple(); //Multiple is a 'Model' not a table name
...
$multiples[] = $multiple;
}
$stuff->multiples()->saveMany($multiples);
}

Inserting data by user id

I'm new to using database/sql so any links and education reads would be nice.
Right now it's inserting into the use column, which I understand because there no in id to the query, how can I make it so?
public function update_balance($balance){
$query = $this->db->prepare("INSERT INTO `users` (`balance`) VALUES (?)");
$query->bindValue(1, $balance);
try{
$query->execute();
} catch(PDOException $e) {
die($e->getMessage());
}
}
}
Here's me calling the function
$user = $users->userdata($_SESSION['id']);
$balance = $user['balance'];
$users->update_balance($balance);
I'm connecting via pdo if that makes any differencehttp://stackoverflow.com/editing-help
The table
ID | Username | Balance
1 | Something | 400
This site was really useful to figure it out
http://wiki.hashphp.org/PDO_Tutorial_for_MySQL_Developers
$query = $this->db->prepare("UPDATE users set balance = ? WHERE id = ?");
$query->bindValue(1, $balance);
$query->bindValue(2, $id);
you add a WHERE clause to tell it which row to insert into or to update.
what you are saying above is always add a new record with just this balance in it.
maybe something like this:
"INSERT INTO `users` ('id',`balance`) VALUES (?,?)"
and
$query->bindValue($user, $balance);

MySQL - select missing dates from table?

Suppose you have the following table values:
date | value
2012-01-01 | 8
2012-01-02 | 3
2012-01-03 | 17
2012-01-09 | 100
2012-01-12 | 2
Now suppose you want to select all the dates between 2012-01-02 and 2012-01-12 and show their values if present. If you simply query the table for the appropriate date range, the dates that don't have values are going to be absent, for obvious reasons. Is there a way to fill in those dates in the query?
The obvious solution is to create a table dates that just stores a list of all dates that may come up, and then to select from the dates table and join values to it, but I'd like to have a solution that doesn't rely on creating a single-column table if I can.
Of note: there are existing questions on SO on this topic, but they are all from 2010 (at least the ones I found when searching were), and MySQL features have grown in that time; there may be a dynamic solution now. If that's not the case, and the dates table is still the best solution, then this question should be closed as a duplicate.
The lack of answers from others suggests to me that at the current time, it is not possible to traverse a range of dates in MySQL without a table that holds those dates. I have, however, written some code in PHP that I'm using to fill in the missing dates after the fact:
function formatResults($inbound, $from, $to) {
$results = array();
$count = 0;
// In order not to lose any results, we have to change how the results are referenced
$indexes = array();
$stats = array();
foreach ($inbound as $stat) {
// ['listindex'] is the date, renamed in the query
$stats[$stat['listindex']] = $stat;
}
// In a function in case you want to pop it out
function dateArray($from, $to) {
$begin = new DateTime($from);
$end = new DateTime($to);
$interval = DateInterval::createFromDateString('1 day');
$days = new DatePeriod($begin, $interval, $end);
$baseArray = array();
foreach ($days as $day) {
$dateKey = $day->format("Y-m-d");
$baseArray[] = $dateKey;
}
$baseArray[] = $to;
return $baseArray;
}
$indexes = dateArray($from, $to);
// Now all the rows we need to return are uniquely identified in $indexes
// So we traverse $indexes to create our results array, rather than relying on $inbound
foreach($indexes as $index) if ($index != '') {
$data = array();
// Make sure we do not run into any 'missing index' problems
if (!isset($stats[$index]))
$stats[$index] = array(
'listindex' => $index,
// ... populate full list of empty fields
);
foreach ($stats[$index] as $key => $value) {
$data[] = $value;
}
$results[$count] = $data;
$count++;
}
return $results;
}