How to sort by related table field when sending Yii2 REST GET request - rest

I want to expand this question.
Basically I have users endpoint. But I am also returning data from the related profiles table. I am not expanding with profiles, I always want to return it. So I have fields method like this:
public function fields()
{
$fields = parent::fields();
$fields[] = 'profile';
return $fields;
}
When I do GET request and demand sorting by profile.created_at field and user.status, it does not sort by profile.created_at.
GET v1/users?sort=-profile.created_at,status
Can this be achieved somehow ?
This is my current code:
/** #var $query ActiveQuery */
$query = User::find();
// get data from profile table
$query->innerJoinWith('profile');
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort' => ['defaultOrder' => ['id' => SORT_DESC]],
'pagination' => [
'pageSize' => 10,
],
]);
return $dataProvider;

You have overridden 'sort' parameter of ActiveDataProvider. To keep default behaviour of Sort object and change defaultOrder property, create an instance, such as:
$sort = new \yii\data\Sort([
'attributes' => [
'profile.created_at',
],
'defaultOrder' => ['id' => SORT_DESC],
]);
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort' => $sort,
'pagination' => [
'pageSize' => 10,
],
]);

Related

Laravel backpack InlineCreate Operation Method not allowed error

Trying to use inline_create i can create in modal but i can't select i get error
Method not allowed The POST method is not supported for this route.
Supported methods: GET, HEAD.
URL is : http://127.0.0.1:8000/admin/question/fetch/tags
Field
$this->crud->addField(
[
'label' => "Les mote clé",
'minimum_input_length' => 0,
'type' => 'relationship',
'name' => 'tags', // the method that defines the relationship in your Model
'ajax' => true,
// 'method' => 'GET',
'minimum_input_length' => 0,
'attribute' => 'name', // foreign key attribute that is shown to user
'inline_create' => [ // specify the entity in singular
'entity' => 'tag', // the entity in singular
'force_select' => true, // should the inline-created entry be immediately selected?
'modal_class' => 'modal-dialog modal-md', // use modal-sm, modal-lg to change width
'modal_route' => route('tag-inline-create'), // InlineCreate::getInlineCreateModal()
'create_route' => route('tag-inline-create-save'), // InlineCreate::storeInlineCreate()
]
]
);
Question modal
// tags
public function tags()
{
return $this->belongsToMany(Tag::class, 'question_tags', 'question_id', 'tag_id');
}
I finally found the problem.
I needed to define the ajax route to work with the field, either creating my own endpoin, or using FetchOperation https://backpackforlaravel.com/docs/5.x/crud-operation-fetch#about-1.
In QuestionCrudController:
use \Backpack\CRUD\app\Http\Controllers\Operations\FetchOperation;
...
public function fetchTags()
{
return $this->fetch(Tag::class);
}

Laravel Backpack attribute accessor causing bug on update command

I am working on Laravel Backpack. I have two fields like this:
$this->crud->addField([ // SELECT2
'label' => 'Type',
'type' => 'select_from_array',
'name' => 'type',
'options' => [
'' => 'select type',
'Movie' => 'Movies',
'Series' => 'Series'
],
]);
$this->crud->addField([ // select2_from_ajax: 1-n relationship
'label' => "Entity", // Table column heading
'type' => 'select2_from_ajax',
'name' => 'entity_id', // the column that contains the ID of that connected entity;
'entity' => 'entity', // the method that defines the relationship in your Model
'attribute' => 'name', // foreign key attribute that is shown to user
'data_source' => url('api/entity'), // url to controller search function (with /{id} should return model)
'placeholder' => 'Select', // placeholder for the select
'include_all_form_fields' => true, //sends the other form fields along with the request so it can be filtered.
'minimum_input_length' => 0, // minimum characters to type before querying results
'dependencies' => ['type'], // when a dependency changes, this select2 is reset to null
// 'method' => 'GET', // optional - HTTP method to use for the AJAX call (GET, POST)
]);
The second field options are dependent on the first one.
In my model, I have:
public function getEntityIdAttribute()
{
$id = $this->attributes['entity_id'];
$type = $this->attributes['type'];
if ($type == "Movie") {
$attribute = Movie::find($id);
} else {
$attribute = Series::find($id);
}
return $attribute->name;
}
Create and List operations work perfectly. But on update, it throws this error:
Undefined array key "entity_id"
Why is this accessor not working on the update? or can we somehow skip the accessor on the update command?

Handle Request: ChoicesType

So I have this ChoiceType Form that will sort the items:
$sort = $this->createForm(ChoiceType::class, NULL, array(
'choices' => array(
'...' => 'default',
'A-Z' => 'title_up',
'Z-A' => 'title_down',
'Price low to high' => 'price_up',
'Price high to low' => 'price_down',
),
));
I want to use the Choices so that when one of them is selected from the dropdown menu will do this: $products = "SELECT a FROM AppBundle:Product a ORDER BY a.title ASC".
I tried this:
$sort->handleRequest($request);
if($sort->isSubmitted() && $sort->isValid()) {
if (isset($default)) {
$products = "SELECT a FROM AppBundle:Product a ORDER BY a.title ASC";
return $this->render('AppBundle:main:index.html.twig', array('products' => $products, ));
}
}
But $default is not working, since is not defined. I dont know how to access the choices, so I can pass them to an if statement.
I think you need to write something like this:
$sort = $this->createFormBuilder()
->setAction($this->generateUrl('your_process_route_here'))
->setMethod('POST')
->add('select', ChoiceType::class, [
'placeholder' => 'Please select',
'choices' => [
'...' => 'default',
'A-Z' => 'title_up',
'Z-A' => 'title_down',
'Price low to high' => 'price_up',
'Price high to low' => 'price_down',
]
])
To get the value inside the <select> element:
$select = $request->request->get('select'); // this will contain whatever value you've selected from the dropdown
Check if the value is what you expect, and then create the query:
if ('default' == $select){ // or you can use a switch
// create a custom method inside your Repository class containing the SELECT, and call it here
}
That select from ->add('select', ...) will be the name attribute for your <select> html element.

Multiple duplicate uri parameters in GuzzleHttp

I am accessing the Echo Nest API, which requires me to repeat the same uri parameter name bucket. However I can't make this work in Guzzle 6. I read a similar issue from 2012, however the approach does not work.
I have tried adding it manually into the query string without any success.
A sample API call could be:
http://developer.echonest.com/api/v4/song/search?format=json&results=10&api_key=someKey&artist=Silbermond&title=Ja&bucket=id:spotify&bucket=tracks&bucket=audio_summary
Here's my example Client:
/**
* #param array $urlParameters
* #return Client
*/
protected function getClient()
{
return new Client([
'base_uri' => 'http://developer.echonest.com/api/v4/',
'timeout' => 5.0,
'headers' => [
'Accept' => 'application/json',
],
'query' => [
'api_key' => 'someKey',
'format' => 'json',
'results' => '10',
'bucket' => 'id:spotify' // I need multiple bucket parameter values with the 'bucket'-name
]);
}
/**
* #param $artist
* #param $title
* #return stdClass|null
*/
public function searchForArtistAndTitle($artist, $title)
{
$response = $this->getClient()->get(
'song/search?' . $this->generateBucketUriString(),
[
'query' => array_merge($client->getConfig('query'), [
'artist' => $artist,
'title' => $title
])
]
);
// ...
}
Can you help me?
In the Guzzle 6 you are not allowed to pass any aggregate function anymore. Whenever you will pass an array to the query config it will be serialized with the http_build_query function:
if (isset($options['query'])) {
$value = $options['query'];
if (is_array($value)) {
$value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
}
To avoid it you should serialize a query string by your own and pass it as string.
new Client([
'query' => $this->serializeWithDuplicates([
'bucket' => ['id:spotify', 'id:spotify2']
]) // serialize the way to get bucket=id:spotify&bucket=id:spotify2
...
$response = $this->getClient()->get(
...
'query' => $client->getConfig('query').$this->serializeWithDuplicates([
'artist' => $artist,
'title' => $title
])
...
);
Otherwise you could pass into the handler option an adjusted HandlerStack that will have in its stack your Middleware Handler. The one will read some new config param, say, query_with_duplicates, build acceptable Query String and modify Request's Uri with it accordingly.
I had the same need today, but now we are on Guzzle 7, the easiest way of getting duplicates for params (bucket=value1&bucket=value2&bucket=value3...) is to use the Query Build method. For this to work do the following:
// Import the class
use GuzzleHttp\Psr7\Query;
Example params
$params = [
'bucket' => 'value1',
'bucket' => 'value2',
'bucket' => 'value3',
];
Then when passing the params array to the query key, first pass it through the Query::build method
$response = $client->get('/api', [
'query' => Query::build($params),
]);

Zend_Validate_Db_RecordExists against 2 fields

I usualy use Zend_Validate_Db_RecordExists to update or insert a record. This works fine with one field to check against. How to do it if you have two fields to check?
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => $this->_name,
'field' => 'id_sector,day_of_week'
)
);
if ($validator->isValid($fields_values['id_sector'],$fields_values['day_of_week'])){
//true
}
I tried it with an array and comma separated list, nothing works... Any help is welcome.
Regards
Andrea
To do this you would have to extend the Zend_Validate_Db_RecordExists class.
It doesn't currently know how to check for the existence of more than one field.
You could just use two different validator instances to check the two fields separately. This is the only work around that I can see right now besides extending it.
If you choose to extend it then you'll have to find some way of passing in all the fields to the constructor ( array seems like a good choice ), and then you'll have to dig into the method that creates the sql query. In this method you'll have to loop over the array of fields that were passed in to the constructor.
You should look into using the exclude parameter. Something like this should do what you want:
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => $this->_name,
'field' => 'id_sector',
'exclude' => array(
'field' => 'day_of_week',
'value' => $fields_values['day_of_week']
)
);
The exclude field will effectively add to the automatically generated WHERE part to create something equivalent to this:
WHERE `id_sector` = $fields_values['id_sector'] AND `day_of_week` = $fields_values['day_of_week']
Its kind of a hack in that we're using it for the opposite of what it was intended, but its working for me similar to this (I'm using it with Db_NoRecordExists).
Source: Zend_Validate_Db_NoRecordExists example
Sorry for the late reply.
The best option that worked for me is this:
// create an instance of the Zend_Validate_Db_RecordExists class
// pass in the database table name and the first field (as usual)...
$validator = new Zend_Validate_Db_RecordExists(array(
'table' => 'tablename',
'field' => 'first_field'
));
// reset the where clause used by Zend_Validate_Db_RecordExists
$validator->getSelect()->reset('where');
// set again the first field and the second field.
// :value is a named parameter that will be substituted
// by the value passed to the isValid method
$validator->getSelect()->where('first_field = ?', $first_field);
$validator->getSelect()->where('second_field = :value', $second_field);
// add your new record exist based on 2 fields validator to your element.
$element = new Zend_Form_Element_Text('element');
$element->addValidator($validator);
// add the validated element to the form.
$form->addElement($element);
I hope that will help someone :)
Although, I would strongly recommend a neater solution which would be to extend the Zend_Validate_Db_RecordExists class with the above code.
Enjoy!!
Rosario
$dbAdapter = Zend_Db_Table::getDefaultAdapter();
'validators' => array('EmailAddress', $obj= new Zend_Validate_Db_NoRecordExists(array('adapter'=>$dbAdapter,
'field'=>'email',
'table'=>'user',
'exclude'=>array('field'=>'email','value'=>$this->_options['email'], 'field'=>'is_deleted', 'value'=>'1')
))),
For those using Zend 2, If you want to check if user with given id and email exists in table users, It is possible this way.
First, you create the select object that will be use as parameter for the Zend\Validator\Db\RecordExists object
$select = new Zend\Db\Sql\Select();
$select->from('users')
->where->equalTo('id', $user_id)
->where->equalTo('email', $email);
Now, create RecordExists object and check the existence this way
$validator = new Zend\Validator\Db\RecordExists($select);
$validator->setAdapter($dbAdapter);
if ($validator->isValid($username)) {
echo 'This user is valid';
} else {
//get and display errors
$messages = $validator->getMessages();
foreach ($messages as $message) {
echo "$message\n";
}
}
This sample is from ZF2 official doc
You can use the 'exclude' in this parameter pass the second clause that you want to filter through.
$clause = 'table.field2 = value';
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => 'table',
'field' => 'field1',
'exclude' => $clause
)
);
if ($validator->isValid('value') {
true;
}
I am using zend framework v.3 and validation via InputFilter(), it uses same validation rules as zend framework 2.
In my case I need to check, if location exists in db (by 'id' field) and has needed company's id ('company_id' field).
I implemented it in next way:
$clause = new Operator('company_id', Operator::OP_EQ, $companyId);
$inputFilter->add([
'name' => 'location_id',
'required' => false,
'filters' => [
['name' => 'StringTrim'],
['name' => 'ToInt'],
],
'validators' => [
[
'name' => 'Int',
],
[
'name' => 'dbRecordExists',
'options' => [
'adapter' => $dbAdapterCore,
'table' => 'locations',
'field' => 'id',
'exclude' => $clause,
'messages' => [
'noRecordFound' => "Location does not exist.",
],
]
],
],
]);
In this case validation will pass, only if 'locations' table has item with columns id == $value and company_id == $companyId, like next:
select * from location where id = ? AND company_id = ?