Situation
Using Cake 3.2.4
I have a EventTicketSales table
that
$this->belongsToMany('Approvings', [
'className' => 'Contacts',
'joinTable' => 'event_ticket_sales_approvings',
'targetForeignKey' => 'contact_id',
'saveStrategy' => 'replace',
]);
When I do a pagination like this:
$this->paginate['contain'] = [
'Approvings' => function (\Cake\ORM\Query $query) {
return $query->select([
'Approvings.id',
'Approvings.name',
'Approvings.company',
'EventTicketSalesApprovings.event_ticket_sale_id'
]);
}
];
$this->paginate['fields'] = [
'EventTicketSales.id', 'EventTicketSales.event_id', 'EventTicketSales.billing_amount_in_sgd',
'EventTicketSales.payment_terms', 'EventTicketSales.invoice_number',
'EventTicketSales.due_date',
];
I get the following data:
id: "06f39ba3-9a17-47c6-9374-24b49fb64665",
event_id: 7,
billing_amount_in_sgd: 7680.03,
payment_terms: "45 days",
invoice_number: "9191",
due_date: "2016-03-05T00:00:00+0800",
approvings: [
{
id: 63,
name: "Jaime Jen",
company: "Apple Company",
_joinData: {
contact_id: 63,
id: 335,
event_ticket_sale_id: "06f39ba3-9a17-47c6-9374-24b49fb64665",
created: "2016-01-20T13:43:44+0800",
modified: "2016-01-20T13:43:44+0800"
}
}
]
What I want
I would like to also control the amount of data I retrieve from _joinData
Ideally I want as few fields from _joinData as I can possibly retrieve.
id: "06f39ba3-9a17-47c6-9374-24b49fb64665",
event_id: 7,
billing_amount_in_sgd: 7680.03,
payment_terms: "45 days",
invoice_number: "9191",
due_date: "2016-03-05T00:00:00+0800",
approvings: [
{
id: 63,
name: "Jaime Jen",
company: "Apple Company",
_joinData: {
id: 335,
contact_id: 63,
event_ticket_sale_id: "06f39ba3-9a17-47c6-9374-24b49fb64665",
}
}
]
I actually don't even need the _joinData if I can help it.
You could for example iterate the collection and remove the properties that you're not interested in, something like:
$eventTicketSales->map(function ($row) {
foreach ($row['approvings'] as &$approving) {
unset($approving['_joinData']);
}
return $row;
});
Which could also be done in a result formatter of a query object:
$query->formatResults(function (\Cake\Collection\CollectionInterface $results) {
return $results->map(function ($row) {
foreach ($row['approvings'] as &$approving) {
unset($approving['_joinData']);
}
return $row;
});
});
Or, since you seem to want to apply this in JSON representations, you could mark the _joinData property of the Approving entity as hidden, given that you'd generally want to ditch that property when converting entities to arrays or JSON:
class Approving extends Entity {
// ...
protected $_hidden = [
'_joinData'
];
// ...
}
See also
Cookbook > Collections
Cookbook > Database Access & ORM > Query Builder > Queries Are Collection Objects
Cookbook > Database Access & ORM > Query Builder > Adding Calculated Fields
Cookbook > Database Access & ORM > Entities > Converting to Arrays/JSON > Hiding Properties
Related
I'm using logstash aggregate filter plugin to insert data to ES.
I want to create a json like
"Countries" : {
"Asia" : {
"name" : "Srilanka"
},
"Africa" : {
"name" : "Kenya"
}
}
when uploaded to ES.
I have tried
map['Countries'] = {
map['Asia'] = {
'name' => event.get('name_Asia')
},
map['Africa'] = {
'name' => event.get('name_Africa')
}
}
But it doesn't work.
Is it possible to make create above json?
In the first place to produce nested hashes, you should use hashrockets => not assignments inside a hash. One might create this hash in one turn:
map = {
'Countries' => {
'Asia' => {
'name' => event.get('name_Asia')
},
'Africa' => {
'name' => event.get('name_Africa')
}
}
}
Then you can produce JSON out of it with JSON.dump
require 'json'
JSON.dump(map)
It's the first time i am using validation in laravel. I am trying to apply validation rule on below json object. The json object name is payload and example is given below.
payload = {
"name": "jason123",
"email": "email#xyz.com",
"password": "password",
"gender": "male",
"age": 21,
"mobile_number": "0322 8075833",
"company_name": "xyz",
"verification_status": 0,
"image_url": "image.png",
"address": "main address",
"lattitude": 0,
"longitude": 0,
"message": "my message",
"profession_id": 1,
"designation_id": 1,
"skills": [
{
"id": 1,
"custom" : "new custom1"
}
]
}
And the validation code is like below, for testing purpose i am validating name as a digits. When i executed the below code, the above json object is approved and inserted into my database. Instead, it should give me an exception because i am passing name with alpha numeric value, am i doing something wrong:
public function store(Request $request)
{
$this->validate($request, [
'name' => 'digits',
'age' => 'digits',
]);
}
Please try this way
use Validator;
public function store(Request $request)
{
//$data = $request->all();
$data = json_decode($request->payload, true);
$rules = [
'name' => 'digits:8', //Must be a number and length of value is 8
'age' => 'digits:8'
];
$validator = Validator::make($data, $rules);
if ($validator->passes()) {
//TODO Handle your data
} else {
//TODO Handle your error
dd($validator->errors()->all());
}
}
digits:value
The field under validation must be numeric and must have an exact length of value.
I see some helpful answers here, just want to add - my preference is that controller functions only deal with valid requests. So I keep all validation in the request. Laravel injects the request into the controller function after validating all the rules within the request. With one small tweak (or better yet a trait) the standard FormRequest works great for validating json posts.
Client example.js
var data = {first: "Joe", last: "Dohn"};
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST",'//laravel.test/api/endpoint');
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlhttp.send(JSON.stringify(data));
project/routes/api.php
Route::any('endpoint', function (\App\Http\Requests\MyJsonRequest $request){
dd($request->all());
});
app/Http/Requests/MyJsonRequest.php (as generated by php artisan make:request MyJsonRequest)
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MyJsonRequest extends FormRequest{
public function authorize(){
return true;//you'll want to secure this
}
public function rules(){
return [
'first' => 'required',
'last' => 'required|max:69',
];
}
//All normal laravel request/validation stuff until here
//We want the JSON...
//so we overload one critical function with SOMETHING LIKE this
public function all($keys = null){
if(empty($keys)){
return parent::json()->all();
}
return collect(parent::json()->all())->only($keys)->toArray();
}
}
Your payload should be payload: { then you can do
$this->validate($request->payload, [
'name' => 'required|digits:5',
'age' => 'required|digits:5',
]);
or if you are not sending the payload key you can just use $request->all()
$request->merge([
'meta_data' => !is_null($request->meta_data) ? json_encode($request->meta_data) : null
]);
validator = Validator::make($request->all(), [
'meta_data' => 'nullable|json'
]);
Use the Validator factory class instead using validate method derived from controller's trait. It accepts array for the payload, so you need to decode it first
\Validator::make(json_decode($request->payload, true), [
'name' => 'digits',
'age' => 'digits',
]);
Following the example of #tarek-adam, in Laravel 9 it would be:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MyJsonRequest extends FormRequest{
public function authorize(){
return true;//you'll want to secure this
}
public function rules(){
return [
'first' => 'required',
'last' => 'required|max:69',
];
}
//All normal laravel request/validation stuff until here
//We want the JSON...
//so we overload one critical function with SOMETHING LIKE this
public function validationData()
{
if(empty($this->all())){
$res = [
'success' => false,
'message' => 'Check your request',
];
throw new HttpResponseException(
response()->json($res, 422)
);
}
return $this->all();
}
}
My react-redux app is getting a single record in JSON but the record is an array and therefore it looks like this (notice [ ] brackets):
{"person":[{"PersonID":1,"Name":"John Smith","Gender":0}]}
So, the redux store shows it as person->0->{"PersonID":1,"Name":"John Smith","Gender":0}. As such, the state shows that the person object is empty:
Name: this.props.person?this.props.person.Name:'object is empty',
My PersonPage.js includes the details page like this:
<PersonDetail person={this.props.person} />
The details page has this:
import React from 'react';
import classnames from 'classnames';
class PersonDetail extends React.Component {
state = {
Name: this.props.person?this.props.person.Name:'',
PersonID: this.props.person?this.props.person.PersonID:null,
loading: false,
done: false
}
componentWillReceiveProps = (nextProps) => {
this.setState({
PersonID: nextProps.person.PersonID,
Name: nextProps.person.Name
});
}
This is my raw Redux state:
people: [
[
{
PersonID: 51,
Name: 'John Smith',
Gender: 0
}
]
]
Person is an array, that contains the object in which Name key is present, so you need to use index also, write it like this:
this.props.person && this.props.person.length ? this.props.person[0].Name : '';
Check this example:
var data = {
"person":[
{
"PersonID":1,
"Name":"John Smith",
"Gender":0
}
]
};
console.log('Name: ', data.person[0].Name);
I think that you are supposed to map the person detail foreach person's data.
on the PersonPage.js ,
map it as follows:
{
this.props.person.map((p)=>{
return (<PersonDetail person={p} />)
})
}
If I was you I would make an util function like this:
const parsePeople = people => {
if (people instanceof Array) return parsePeople(people.pop())
return people
}
const people = [
[{
PersonID: 51,
Name: 'John Smith',
Gender: 0
}]
]
const person = parsePeople(people)
console.log(person) -> Object
Using recursion we check if people is an instance of Array, we call the function again using people.pop() which return the last element of the array.
you have an array on your person data... you can only access that without the 0 using map...
example:
componentWillReceiveProps = (nextProps) => {
var PersonID = nextProps.person ? nextProps.person.map(item => { return item.PersonID}) : '';
var Name = nextProps.person ? nextProps.person.map(item => { return item.Name}) : '';
this.setState({
PersonID,
Name
});
}
this is considering you only have 1 array on person.
I fixed it! It was a combination of two of the answers given:
In the PersonPage.js, I had to call the PersonDetails object like this:
<PersonDetail
person={this.props.person[0]}
/>
And this is the new MapStatetoProps:
function mapStateToProps(state, props) {
const { match } = props;
if (match.params.PersonID) {
return {
person: state.people
}
}
Thanks to those who answered. This drove me nuts.
I have a Symfony3 Application setup and would like to rebuild the frontend based on React now.
One of the Entities is User and each of them can have one or more Groups so in the HTML form a list of Checkboxes appears, so the admin can select the groups attached to a User.
In UserType.php this looks like that:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', TextType::class)
->add('password', TextType::class)
->add('email', EmailType::class)
->add('groups', EntityType::class, [
'class' => Group::class,
'choice_label' => 'name',
'expanded' => true,
'multiple' => true//,
//'data' => $builder->getData()->getGroups()
]);
}
To render the Form using React, it would be extremely handy to get a JSON response which could look like that:
{
"user": {
…
"groups": [<gid 1>, …]
"groups_available": [
{
"id": <gid 1>,
"name": …
},
…
]
}
}
So that the groups array contains all the ids of the groups, the user is attached to and groups_available a list of all available groups.
Right now I am using FOSRestBundle and in the Controller it looks like that:
public function getUserformAction($id=null)
{
//if the id is null, create a new user
//else get the existing one
…
$form = $this->createForm(UserType::class, $user);
$view = $form->createView();
return $this->handleView($view);
}
How can I do that?
you should try the following code:
->add('groups', EntityType::class, array(
//if Group is in AppBundle or use the required Bundle name
'class' => 'AppBundle:Group',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.name', 'ASC')
},
'choice_label' => 'name',
'multiple' => true,
'expanded' => true,
));
You can also get a reference from here
After digging in the source and with the help of the debugger I could manage to do it in a more less robust and generic way like so:
protected function getFormStructure(Form $form)
{
return $this->iterateFormview($form->createView(), []);
}
private function iterateFormview(FormView $view, array $result)
{
foreach($view as $child) {
$vars = $child->vars;
$data = ['value' => $vars['value']];
if(isset($vars['choices'])) {
$data['choices'] = [];
foreach ($vars['choices'] as $choice) {
array_push($data['choices'], [
'label' => $choice->label,
'value' => $choice->value]);
}
}
$result[$vars['full_name']] = $data;
if(count($child) > 0) {
$result = $this->iterateFormview($child, $result);
}
}
return $result;
}
Result (as json):
{
…
"user[groups]":
{
"value": "",
"choices": [
{
"value": 100,
"label": "the name"
},
…
]
}
}
I guess this routine needs to be extended if I need to support more types… But for now this will do it.
I'm looking for a performant way to update selected records in a collection based on the presence of the record's ID in a reference array. For example, given a reference array of [1,2,5] I want each record in the collection with those IDs to be given an attribute of $selected: true. Each record in the collection will be associated with a checkbox in the view and $selected items will be checked (fiddle)
I've started with the approach below (coffeescript) but I'm new to Lodash so I thought I'd ask for some feedback. Also, I'm not sure what the cleanest way is to set $selected: false to the alternate records?
# Whole Collection
collection = [
{id:1, name: "one"},
{id:2, name: "two"},
{id:3, name: "three"},
{id:4, name: "four"},
{id:5, name: "five"}
]
# Mark subset as selected
for id in [1,2,5]
_.where( collection, 'id':id ).forEach( (record) ->
record.$selected = true
)
Here's a fiddle...
http://jsfiddle.net/zd78e4bj/
EDIT
Added reference to two-way binding $selected elements to checkboxes in the view
I would do something like this (sorry it's not CoffeScript, but you get the idea):
var collection = [
{ id:1, name: 'one' },
{ id:2, name: 'two' },
{ id:3, name: 'three' },
{ id:4, name: 'four' },
{ id:5, name: 'five'}
];
var ids = [ 1, 2, 5 ];
_(collection)
.indexBy('id')
.at(ids)
.each(_.ary(_.partialRight(_.assign, { $selected: true }), 1))
.value();
console.log(collection);
// →
// [
// { id: 1, name: 'one', $selected: true },
// { id: 2, name: 'two', $selected: true },
// { id: 3, name: 'three' },
// { id: 4, name: 'four' },
// { id: 5, name: 'five', $selected: true }
// ]
First, we create an intermediary object using indexBy(). The keys are the id values in collection. Next, we pass at() the ids we're interested in. This builds a new collection containing only the ids we're after.
The last step is to use each() to add the $selected property. This is done by combining partialRight(), assign(), and ary(). The partially-applied argument is the new $selected property. We use ary() to make sure the callback passed to each() only has one argument.
You can use the map or forEach function to do all of this either in Lodash or native JavaScript. Map creates a new list and forEach will mutate the existing list.
The key to these is the transformation function. This function will set the $selected to true or false if it exists or not in the ids collection.
function mark(item) {
item['$selected'] = (ids.indexOf(item.id) > -1);
return item;
}
You could build a more generic transformer using:
function markItemByList(list, property, searchField) {
return function(item) {
item[property] = (list.indexOf(item[searchField]) > -1);
return item;
}
}
Coffeescript would be:
markItemByList = (list, property, searchField) ->
(item) ->
item[property] = (list.indexOf(item[searchField])) > -1)
item
If you want a mutable implementation:
function select(ids, collection) {
collection.forEach(markItemByList(ids, '$selected', 'id'));
}
Here's a lodash immutable implementation:
function select(ids, collection) {
return _.map(collection, markItemByList(ids, '$selected', 'id'));
}
Here's a pure JavaScript immutable implementation:
function select(ids, collection) {
return collection.map(markItemByList(ids, '$selected', 'id'));
}
Here's an angular implementation:
angular.forEach(collection, markItemsByList(ids, '$selected', 'id'));