Laravel merging Eloquent result and array - php

I'm trying to merge eloquent result and array because I need to add all possible filters that user can use for this model. If anyone have any other idea how to make it I really would be very thankful. Here is an example code:
<?php
class School extends Eloquent {
protected $table = 'schools';
public function listSchoolsEndUser()
{
$schools_data = new School;
$schools_data = $schools_data->paginate(12);
$filters = array(
'filters' => array(
'name' => 'Neshtoto'
)
);
$schools_data = (object) array_merge(get_object_vars($schools_data), $filters);
echo '<pre>';
print_r( $schools_data );
exit;
return $schools_data;
}
And the result is very interesting:
stdClass Object
(
[filters] => Array
(
[name] => Neshtoto
)
)

If you just want to send both, filters and the school_data back in a JSON response you can do it this way:
return Response::json(array(
'filters' => array(
'name' => 'Neshtoto'
),
'data' => $school_data->toArray()
));
Or if you want to use array_merge:
$school_data = array_merge($filters, array('data' => $school_data->toArray()));
return $school_data;
Edit
If you are just injecting the data into a view I see no reason at all to merge the data, just pass two variables!
return View::make('view', array('schools' => $school_data, 'filters' => $filters));
(Here $filters would obviously only be array('name' => 'Neshtoto') and not the full thing including 'filters' => ...)

Related

CodeIgniter-Access from model to controller

I have the following code in Model:
<?php
class Route_Model extends CI_Model
{
function __construct()
{
parent::__construct();
}
public function getRoute($date = array())
{
try {
$data = array(
'route' => array(
'id' => 1,
'name' => 'budapest-athens',
'price' => 150,
'id' => 2,
'name' => 'rome-madrid',
'pret' => 250,
'id' => 3,
'name' => 'belgrade-bucharest',
'price' => 180,
'id' => 4
)
);
return $data;
} catch (Exception $e) {
return $e->getMessage();
}
}
}?>
And I want to access array elements in my controller.
How can I access each field separately?
Something like $price = $this->data['price']?
Thank you!
You are returning an array with two levels, if you want to get the price from the array $data, simply do this in your controller:
$data = $this->route_model->getRoute($date);
$price = $data['route']['price'];
Please, note that your array is not well formed because you have repeated keys and this may cause problems
This array will never work since you're overwriting keys, I think you would want the following array:
$data = [
'route' => [
[
'id' => 1,
'name' => 'budapest-athens',
'price' => 150
], [
'id' => 2,
'name' => 'rome-madrid',
'price' => 250
], [
'id' => 3,
'name' => 'belgrade-bucharest',
'price' => 180
]
]
];
Next to that, your try / catch seems unnecessary here, there is no real try. It's a hard-coded array, so unless this will actually do some interactions there is no need for the try / catch.
Anyway, to receive this data in your controller you should do:
$this->load->model('Route_model');
$route = $this->Route_model->getRoute();
var_dump($route);
exit;
Now you will have this array. Another wonder, are you actually trying to grab all the routes in this array, or is there something you want to do with the $date parameter? Since right now it doesn't looks like it's used unless you stripped some code away.

Laravel: eloquent relationship create multi

i have a ParentItem model
$parentItem = ParentItem::get()->first();
I have this array
$items = array(
array(
'title' => 'test1',
'desc' => 'test1'
),
array(
'title' => 'test2',
'desc' => 'test2'
)
);
i want to add it as a has many relationship.
so i can do:
foreach($items as $item) {
$parentItem->items()->create($item)
}
is there any way way to create all at once..?
something like:
$parentItem->items()->createMany($items);
You can tryout two path.
Using saveMany method:
$parentItem = ParentItem::first();
$items = array(
new Item(['title' => 'test1','desc' => 'test1']),
new Item(['title' => 'test2','desc' => 'test2'])
);
$parentItem->items()->saveMany($items)
For further info you can read here.
https://laravel.com/docs/5.1/eloquent-relationships#inserting-related-models
Using insert method:
$parentItem = ParentItem::first();
$items = array(
array('title' => 'test1','desc' => 'test1','parent_item_id' => $parentItem->id),
array('title' => 'test2','desc' => 'test2','parent_item_id' => $parentItem->id)
);
Item::insert($items);
Remember in insert created_at and updated_at won't be inserted automatically, you have to provide them in the array if you have them in your table. For further info you can read here.
https://laravel.com/docs/5.1/queries#inserts
As of right now, you may use the createMany Eloquent method to achieve this.
Details here: https://laravel.com/docs/9.x/eloquent-relationships#the-create-method

Avoid duplicates by associating to inserted records with CakePHP saveMany

I am trying to take advantage of CakePHP's saveMany feature (with associated data feature), however am creating duplicate records. I think it is because the find() query is not finding authors, as the transaction has not yet been committed to the database.
This means that if there are two authors with the same username, for example, in the spreadsheet, then CakePHP will not associate the second with the first, but rather create two. I have made up some code for this post:
/*
* Foobar user (not in database) entered twice, whereas Existing user
* (in database) is associated
*/
$spreadsheet_rows = array(
array(
'title' => 'New post',
'author_username' => 'foobar',
'content' => 'New post'
),
array(
'title' => 'Another new post',
'author_username' => 'foobar',
'content' => 'Another new post'
),
array(
'title' => 'Third post',
'author_username' => 'Existing user',
'content' => 'Third post'
),
array(
'title' => 'Fourth post', // author_id in this case would be NULL
'content' => 'Third post'
),
);
$posts = array();
foreach ($spreadsheet_rows as $row) {
/*
* This query doesn't pick up the authors
* entered automatically (see comment 2.)
* within the db transaction by CakePHP,
* so creates duplicate author names
*/
$author = $this->Author->find('first', array('conditions' => array('Author.username' => $row['author_username'])));
$post = array(
'title' => $row['title'],
'content' => $row['content'],
);
/*
* Associate post to existing author
*/
if (!empty($author)) {
$post['author_id'] = $author['Author']['id'];
} else {
/*
* 2. CakePHP creates and automatically
* associates new author record if author_username is not blank
* (author_id is NULL in db if blank)
*/
if (!empty($ow['author_username'])) {
$post['Author']['username'] = $row['author_username'];
}
}
$posts[] = $post;
}
$this->Post->saveMany($posts, array('deep' => true));
Is there any way that this can be achieved, while also keeping transactions?
Update
You new requirement to save also posts that have no associated authors changes the situation a lot, as mentioned in the comments, CakePHPs model save methods are not ment to be able to save data from different models at once if it's not an association, if you need to do this in a transaction, then you'll need to handle this manually.
Save authors and their posts instead of posts and their authors
I would suggest that you save the data the other way around, that is save authors and their associated posts, that way you can easily take care of the duplicate users by simply grouping their data by using the username.
That way around CakePHP will create new authors only when neccessary, and add the appropriate foreign keys to the posts automatically.
The data should then be formatted like this:
Array
(
[0] => Array
(
[username] => foobar
[Post] => Array
(
[0] => Array
(
[title] => New post
)
[1] => Array
(
[title] => Another new post
)
)
)
[1] => Array
(
[id] => 1
[Post] => Array
(
[0] => Array
(
[title] => Third post
)
)
)
)
And you would save via the Author model:
$this->Author->saveMany($data, array('deep' => true));
Store non associated posts separately and make use of transactions manually
There is no way around this if you want to use the CakePHP ORM, just imagine what the raw SQL query would need to look like if it would need to handle all that logic.
So just split this into two saves, and use DboSource::begin()/commit()/rollback() manually to wrap it all up.
An example
Here's a simple example based on your data, updated for your new requirements:
$spreadsheet_rows = array(
array(
'title' => 'New post',
'author_username' => 'foobar',
'content' => 'New post'
),
array(
'title' => 'Another new post',
'author_username' => 'foobar',
'content' => 'Another new post'
),
array(
'title' => 'Third post',
'author_username' => 'Existing user',
'content' => 'Third post'
),
array(
'title' => 'Fourth post',
'content' => 'Fourth post'
),
array(
'title' => 'Fifth post',
'content' => 'Fifth post'
),
);
$authors = array();
$posts = array();
foreach ($spreadsheet_rows as $row) {
// store non-author associated posts separately
if (!isset($row['author_username'])) {
$posts[] = $row;
} else {
$username = $row['author_username'];
// prepare an author only once per username
if (!isset($authors[$username])) {
$author = $this->Author->find('first', array(
'conditions' => array(
'Author.username' => $row['author_username']
)
));
// if the author already exists use its id, otherwise
// use the username so that a new author is being created
if (!empty($author)) {
$authors[$username] = array(
'id' => $author['Author']['id']
);
} else {
$authors[$username] = array(
'username' => $username
);
}
$authors[$username]['Post'] = array();
}
// group posts under their respective authors
$authors[$username]['Post'][] = array(
'title' => $row['title'],
'content' => $row['content'],
);
}
}
// convert the string (username) indices into numeric ones
$authors = Hash::extract($authors, '{s}');
// manually wrap both saves in a transaction.
//
// might require additional table locking as
// CakePHP issues SELECT queries in between.
//
// also this example requires both tables to use
// the default connection
$ds = ConnectionManager::getDataSource('default');
$ds->begin();
try {
$result =
$this->Author->saveMany($authors, array('deep' => true)) &&
$this->Post->saveMany($posts);
if ($result && $ds->commit() !== false) {
// success, yay
} else {
// failure, buhu
$ds->rollback();
}
} catch(Exception $e) {
// failed hard, ouch
$ds->rollback();
throw $e;
}
You need to use saveAll, which is a mix between saveMany and saveAssociated (you will need to do both of them here).
Plus, you need to change the structure of each post.
Here is an example of the structures you will need to create inside the loop.
<?php
$posts = array();
//This is a post for a row with a new author
$post = array (
'Post' => array ('title' => 'My Title', 'content' => 'This is the content'),
'Author' => array ('username' => 'new_author')
);
$posts[] = $post;
//This is a post for a row with an existing author
$post = array (
'Post' => array ('title' => 'My Second Title', 'content' => 'This is another content'),
'Author' => array ('id' => 1)
);
$posts[] = $post;
//This is a post for a row with no author
$post = array (
'Post' => array ('title' => 'My Third Title', 'content' => 'This is one more content')
);
$posts[] = $post;
$this->Post->saveAll($posts, array ('deep' => true));
?>
Following the "use transactions manually" bit suggested by ndm, this piece of code (written in a unit test!) seemed to do the trick:
public function testAdd() {
$this->generate('Articles', array());
$this->controller->loadModel('Article');
$this->controller->loadModel('Author');
$csv_data = array(
array(
'Article' => array(
'title' => 'title'
)),
array(
'Article' => array(
'title' => 'title'
),
'Author' => array(
'name' => 'foobar'
),
),
array(
'Article' => array(
'title' => 'title2'
),
'Author' => array(
'name' => 'foobar'
)
),
/* array( */
/* 'Article' => array( */
/* 'title' => '' */
/* ), */
/* 'Author' => array( */
/* 'name' => '' // this breaks our validation */
/* ) */
/* ), */
);
$db = $this->controller->Article->getDataSource();
$db->begin();
/*
* We want to inform the user of _all_ validation messages, not one at a time
*/
$validation_errors = array();
/*
* Do this by row count, so that user can look through their CSV file
*/
$row_count = 1;
foreach ($csv_data as &$row) {
/*
* If author already exists, don't create new record, but associate to existing
*/
if (!empty($row['Author'])) {
$author = $this->controller->Author->find('first',
array(
'conditions' => array(
'name' => $row['Author']['name']
)
));
if (!empty($author)) {
$row['Author']['id'] = $author['Author']['id'];
}
}
$this->controller->Article->saveAssociated($row, array('validate' => true));
if (!empty($this->controller->Article->validationErrors)) {
$validation_errors[$row_count] = $this->controller->Article->validationErrors;
}
$row_count++;
}
if (empty($validation_errors)) {
$db->commit();
} else {
$db->rollback();
debug($validation_errors);
}
debug($this->controller->Article->find('all'));
}

Return two types with NuSOAP

I have a working webService using NuSOAP. Now, I have to make a validation before returning the data requested. If everything is ok, I return it normally, otherwise I would like to return a String message explaining why I'm not giving the information requested. Problem is that I can't get to add two different types of return to RegisterFunction of NuSOAP. If I add a ComplexType as return, I can't return a String.
The function can't have two return-values. You should add the error-message-string to your complex type. If you don't wanna touch your complex type, then you should create another
complex type wich contains your datatype and a string.
Example - the complex type you have right now:
$server->wsdl->addComplexType('myData','complexType','struct','all','',
array( 'important' => array('name' => 'important','type' => 'xsd:string'),
'stuff' => array('name' => 'stuff','type' => 'xsd:string')
)
);
the extra complex type:
$server->wsdl->addComplexType('package','complexType','struct','all','',
array( 'data' => array('name' => 'data','type' => 'tns:myData'),
'errormsg' => array('name' => 'errormsg','type' => 'xsd:string')
)
);
registration of the function:
$server->register(
'getData',
array('validation'=>'xsd:string'),
array('return'=>'tns:package'),
$namespace,
false,
'rpc',
'encoded',
'description'
);
the function:
function GetData($validation)
{
if($validation == "thegoodguy") {
$result['data'] = array(
"important" => "a top secret information",
"stuff" => "another one"
);
$result['errormsg'] = null;
} else {
$result['data'] = null;
$result['errormsg'] = "permission denied!";
}
return $result;
}
That way the client could try to analyse the received data and if it is null then he
shows up the errormessage.
You first need to define a new type that describes an array of strings like so:
$server->wsdl->addComplexType(
'ArrayOfString',
'complexType',
'array',
'sequence',
'',
array(
'itemName' => array(
'name' => 'itemName',
'type' => 'xsd:string',
'minOccurs' => '0',
'maxOccurs' => 'unbounded'
)
)
);
Then you can use tns:ArrayOfString as the return type.

cakePHP build a select box from custom array

So we have a variety of search pages each with different search criteria, so I decided to write a component which will get parameters passed to it from the controller, collect the neccesary data and return an array which I could then set to be used within my view file to populate drop down boxes to filter the criteria.
I have managed to get everything write up to where I must use the cakePHP helper to build a dynamical select box. I am convinced that I am doing something wrong and if there is an easier way to do this and still keep it somewhat dynamic please assist where you can:
// COMPONENT METHOD:
public function filterQueries($parameters) {
// Get defaults from the user:
$criteria = $parameters["custom"];
$defaults = $parameters["defaults"];
// Validate the defaults the user may want and assign them to the return array:
if($defaults != "false") {
foreach($defaults as $key => $value) {
if(array_key_exists($value, $this->defaults)) {
$this->returnArray["defaults"][$value] = $this->defaults[$value];
}
}
}
// Get all data for the custom requested form fields:
if($criteria != false) {
foreach($criteria as $model => $arguments) {
$fields = $arguments["fields"];
$conditions = $arguments["conditions"];
$recursive = $arguments["recursive"];
if(!in_array($model,$this->uses)) {
$useModel = ClassRegistry::init($model);
} else {
$useModel = $this->$$model;
}
$array = $useModel->find("all",array("conditions" => $conditions, "fields" => $fields, "recursive" => $recursive));
$this->returnArray["custom"][$model] = $array;
}
}
return $this->returnArray;
}
The above function will get an array which breaks down either custom searches or defaults (not included above but it all works, it returns the array exactly as I would have expected it.
// The code within my action to get the content from above:
// Load the Filters component to search data:
$search = $this->Components->load("Filter");
// Tell search what you want:
$searchBoxes = array(
"defaults" => array("statuses", "survey_type"),
"custom" => array(
"User" => array(
"fields" => array("User.id","User.first_name", "User.last_name"),
"conditions" => array("User.user_group_id" => "4f847c63-1840-446e-88be-3e4d29566cf0"),
"recursive" => -1
)
)
);
$filterResults = $search->filterQueries($searchBoxes);
$this->set("filters",$filterResults);
So now I get this multi-associative array within my view file (all still fine), but I want to now build example a drop down list of the users based on the array created above, but the outcome is nothing like what I expected:
echo $this->Form->input('user_id',
array(
"type" => "select",
"options" => $filters["custom"]["User"]
)
);
The HTML output is broken and displays it like this:
<option value="last_name">Doe</option>
<option value="first_name">Jihn</option>
<optgroup label="User"> </optgroup>
<optgroup label="1"> </optgroup>
<option value="last_name">Marilyn</option>
<option value="first_name">Monroe</option>
I acknowledge that I do not have a lot of cake experience but cannot understand how to just get the results to :
<option value='USERID'>NAME</option> // Yes I know the names and surnames must be concatinated still
Any advise help or guidance on how to do it, and do it the right way, would greatly be appreciated :)
VARDUMP ON $filters['custom']['users']
array
0 =>
array
'User' =>
array
'id' => string '4f84840e-cda8-4704-8fdf-210729566cf0' (length=36)
'first_name' => string 'Name' (length=4)
'last_name' => string 'Surname' (length=11)
1 =>
array
'User' =>
array
'id' => string '4f8488cb-53e0-4f72-af73-3de229566cf0' (length=36)
'first_name' => string 'Name' (length=6)
'last_name' => string 'Surname' (length=6)
You can enhance your output by doing as follows:
1) for combining two fields of a table, you can use "virtualfields" in the model, as follows: For example if you have the user model, you can define as follows:
public $virtualFields = array(
'full_name' => 'CONCAT(first_name, " ",last_name)'
);
So now the "full_name" field will be got whenever you call the find method of the User model.
2) For getting the data from the table for a select box, you can use the find('list') method. For example for the User model if you need the id,full_name (last and first name combined using the virtual fields) of the table,it can be done as follows :
$this->User->find('list',array('fields'=>array('id','full_name'),'conditions'=>$conditions))
I hope this helps..
Well I guess what you want to do is actually create another array with formatted options.
foreach ($filters["custom"]["User"] as $arr)
{
$options[$arr['id']] = $arr['first_name'] . ' ' . $arr['last_name'];
}
then
echo $this->Form->select('user_id', $options);
I think you need something like this:
$result = Set::combine($filters["custom"]["User"],
'{n}.User.id', // this is the value of select box
array(
'{0} {1}',
'{n}.User.first_name',
'{n}.User.last_name'
) // this is the text of select box
);
pr($result);
$this->form->input('inputname', array('label' => false, 'options' => array('Select optionstype','a','b'), 'class'=>array('form-a', 'b'), 'id' => 'bacid', 'style' => 'width:280px;','value'=>$abc['model']['array_attribute']));
On cakePHP 3, I can't use "virtualFields" yet, but I can use as follows:
//In PostsController
$users = $this->Posts->Users->find('list',
['keyField' => 'id',
'valueField' => 'full_name', //Don't forget to call concatened field here
'conditions' => $conditions,
'order' => $orderBy
]);
//here the magic happens
$concat = $users->func()->concat(
['Users.first_name' => 'identifier',
' ',
'Users.last_name' => 'identifier'
]);
//selecting fields
$users->select(['id', 'full_name' => $concat]);
//sending to view
$this->set('users', $users->toArray());
I hope this helps CakePHP 3 developers

Categories