How do I create a single-value field in Solr/Solarium? - php

I've done a vanilla install of Solr and Solarium for PHP
Solarium 5.x
PHP 7.1
Solr 8.5.1
I can create and query documents. But all fields I create are returned as arrays - except for "id". obviously there is some schema somewhere that specifies that id is a single-value field. how can I create my own single-value fields? All fields I create are multi-value arrays.
The multi-value field feature is useful but there are only a few cases where I will need it.
It seems I should be able to define the field types and specify whether they are multi-value or not but instead all fields I create are multi-value arrays and I can't appear to change that. the solarium documentation has a section on multi-value fields but not single-value fields.
https://solarium.readthedocs.io/en/stable/documents/#multivalue-fields
I don't see any documentation in solarium for defining the documents and their field types. possibly something is wrong with my installation.
here is my code example:
$client = new Solarium\Client($config);
// get an update query instance
$update = $client->createUpdate();
// create a new document for the data
$doc1 = $update->createDocument();
// add data to the document
$doc1->id = 123; // single value by default
$doc1->name = 'document name'; // always results in an array
$doc1->price = 5; // always results in an array
// add the documents and a commit command to the update query
$update->addDocuments(array($doc1));
$update->addCommit();
// this executes the query and returns the result
$result = $client->update($update);
// then query the documents
$query = $client->createSelect();
$query->setQuery('*:*');
$resultSet = $client->select($query);
echo 'NumFound: '.$resultSet->getNumFound().'<br>';
foreach ($resultSet as $document) {
foreach ($document as $field => $value) {
// I can test to see if $value is an array but my point is that I don't want
// to do so for every single field. how can I define fields that are single-value by default?
echo '<div'> . $field . ' : ' . $value . '</div>';
}
}
this outputs:
NumFound: 1
id : 123
name : Array
price : Array
yes I know how to get those values out of the array but I know there must be some way to get single-value fields by default.
Thanks in advance.

I have discovered that I can define multiValued="false", and many other detailed properties of a field, by manually editing managed-schema (schema.xml) in the conf directory for the core.
however it says at the top of this file:
So now the real question is, how do you edit the SOLR schema?

Related

Laravel Eloquent: any way to update DB with dynamic fields?

I am trying to implement the "Edit Application Settings" feature. After a bit of thinking, my configuration values are stored in the DB with key -> value structure, like this:
id
key
value
1
logo_path
img/logo.png
As you can see, for each setting, there is only a key & value column. I made an App Service provider to cache them forever, and a helper function (config('setting_key')) to get the value, but now I'd like to update it in the most efficient way.
The user interface consists of the <form action="post" ...> and input with a corresponding name, like this: <input name="setting_key_name" ... />. As you can see, the name attribute here has the value of the key column value and the actual value of the input would be the value column value (a bit of confusion here).
First thing that came to my mind, was to make a foreach loop and find & update every row in DB, but IMHO it is very unoptimized way, cause if the page has a form with 10 values, it is 10 SQL queries. But till now, this is what I've done:
$keys = collect($request->except('_token'))->keys()->toArray();
// get all settings if the key name matches the request's input name
$setting = Setting::whereIn('key', $keys)->get();
$logo = self::GENERAL_APP_LOGO; // contant with a key-name (general_application_logo);
if($request->has(self::GENERAL_APP_LOGO) && $request->$logo) {
// Processing uploaded image here;
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name); // Using an upload trait
$setting->where('key', $logo)->value = self::LOGO_IMAGE_PATH . $name; // just a try to update the DB this way
}
foreach ($keys as $key) {
$setting->where('key', $key)->value = $request->$key; // putting all request's input values to corresponding key
}
$setting->save(); // saving the DB.
As you can see, this won't work and will throw an Exception, like Call to undefined method ...\Eloquent\Builder::save(). I tried the same code with an update, but the difficult part here is to update it multiple times (since the if section should have the update as well, for the logo), as well as binding the key to value.
So, a little bit of your help would be appreciated - what the logic should be here? How can I update a DB rows with corresponding column's value? I mean - like this (update where key = 'general_app_name' set value, 'some_setting_value'), but using the optimized and clear way?
Working solution
As #miken32 stated in his answer, I used hid version of code, but with slight changes:
// Changed the $request->settings->keys() to PHP native method array_keys():
$settings = Settings::whereIn('key', array_keys($request->settings))->get()->groupBy('id');
// Also, here I changed the `whereIn('id', ...)` to `whereIn('key', ...)`, since it was my primary index.
foreach ($request->settings as $k=>$v) {
if ($k === self::GENERAL_APP_LOGO_ID) {
// not sure about this one, but I think this is
// how you'd access a file input in an array
$image = $request->file('settings')[$k];
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name);
$v = self::LOGO_IMAGE_PATH . $name;
}
// take the Setting object out of the list we pulled
// Here I added the ->first() to get the first element from the retrieved collection;
$setting = $settings->get($k)->first();
$setting->value = $v;
$setting->save();
}
Since I was fetching the configuration values via helper, that only returns the value of the current key (and no id column), I changed the id to key and made the key as my PK in a model. Works like a charm!
With each setting in a separate row, there's no way to avoid multiple database queries – one to get the current values for all settings, and other to update each one. Looking up items by primary key is more efficient, so I'd recommend putting the contents of the id column in your blade view, like this:
<label for="setting_{{$setting->id}}">{{$setting->key}}</label>
<input name="settings[{{$setting->id}}]" id="setting_{{$setting->id}}" value="{{$setting->value}}"/>
Now in your controller, $request->settings will be an array you can loop through. You can continue treating your file upload separately, but now you've got the id column to look up, so change your constant to that.
$settings = Settings::whereIn('id', $request->settings->keys())->get()->groupBy('id');
foreach ($request->settings as $k=>$v) {
if ($k === self::GENERAL_APP_LOGO_ID) {
// not sure about this one, but I think this is
// how you'd access a file input in an array
$image = $request->file('settings')[$k];
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name);
$v = self::LOGO_IMAGE_PATH . $name;
}
// take the Setting object out of the list we pulled
$setting = $settings->get($k);
$setting->value = $v;
$setting->save();
}
Note that Laravel does offer methods to bulk-update multiple models at once, but they are doing separate queries to the database in the background. IIRC, the save() method doesn't do anything if the value hasn't changed, which will spare you some hits.
You could try creating a text field, or a json field if your database supports it, and storing all of your settings as a JSON string in that field.
id
settings
1
{ "logo_path" : "img/logo.png", "foo" : "bar", "thing_count" : 17 }
2
{ "logo_path" : "img/logo2.png", "foo" : "baz", "thing_count" : 4 }
In your Laravel model, you can cast it as an array
protected $casts = ["settings" => "array"];
and then use it from the model
echo $theModel->settings['logo'];
echo $theModel->settings['foo'];
or you can cast it as a fully fledged object if you need to using value object casting.
One gotcha that can be confusing for people is the setting of the values in the array to update it. This will not work:
$theModel->settings['foo'] = "boz";
The reason is due to the way the Laravel mutators work. Instead, you make a value copy of the settings, change that, and reassign it to the model:
$settings = $theModel->settings;
$settings['foo'] = "boz";
$theModel->settings = $settings;
This approach has the capacity to infinitely expandable in the future as you just add new keys to your json. Be sure to do checks on the settings array to ensure fields you are looking for are set (which is why value objects can be very handy to do validation).
It also solves your database query problem - it's only ever one.
You don't need to put
$setting->where('key', $logo)->value = ...;
Just call
$setting->where('key', $logo)->update($request->toArray());
$setting->save(); called when you instantiated setting class like :
$setting = new Setting();
Or
$setting = Setting::whereIn('key', $keys)->get()->first();
Then
$setting->val = ...;
$setting->save(); // then it work's

A more efficient way of saving 125 columns in a Laravel controller

I have a rather large table that has 125 data inputs, each of which has to be saved in a separate column.
I have named each HTML input as 1,2,3 etc... and the same within the table, to hopefully help things.
At the moment, I have the following code:
$observation = new Observation();
$observation->site_id = Input::get('site_id');
$observation->result = Input::get('1');
$observation->result_id = '1';
$observation->save();
Is there a way I could use a loop to iterate through the 125 data inputs (Input::get('X') and result_id = 'X') and then save them all?
for($i=1; $i<=125; $i++) {
$data[$i] = [
'site_id'=>Input::get('site_id'),
'result'=>Input::get($i), 'result_id'=>$i
];
}
Using Query builder:
DB::table('table')->insert($data); // Change this with your table name.
and if you want to use Eloquent:
Model::insert($data);
You can use something like this pattern in your controller to handle creating new Observations and editing existing Observations without worrying about the specific attribute names:
// get all non-empty inputs except 'token'
$inputs = array_filter(Input::except('_token'), 'strlen');
// assuming 'id' is your primary key and sent as an input in your form
if (Input::has('id'))
{
// An edit
$id = Input::get('id');
$observation = Observation::find($id);
foreach ($inputs as $key => $value)
{
$observation->$key = $value;
}
}
else
{
// A create
$observation = new Observation($inputs);
}
This solution doesn't force you to use sequential column names or html input names like 1..125 and will allow you to use more meaningful column names if you prefer. However, it does assume that your column names (and therefore object attributes) are the same as the html input names.
Related, but you might also like to know that if you use the HTML helpers in the Blade view template to construct your form, and open the form with Form::model, it will fill even fill in the values of the inputs using the object that you pass to it.
For example, in the view:
{{ Form::model($observation) }}

SugarCRM Custom Dropdown Field

I have made a custom dropdown field in leads module. Its a dynamically fetching users from users table from the leads module as key => value pair.
The field works fine but when in the edit mode (create a new lead)...the value is not getting stored and instead the key is getting stored not value..
I mean like instead of 'James Bond' the id is getting stored ..which is like '7896877'
Now the funny thing is that in the detail view in sugarcrm (leads module) the name is displayed properly as i wanted it to work. ONly in the list view it displays the ID and also in the database its getting stored as KEY i.e the hash ID.
This is the function:
function getUSERS($bean) {
$resultArray = Array();
$query = "select id,(first_name + ' ' + last_name) AS Name from dbo.users ORDER BY first_name ASC";
$resultArray [''] = '';
$result = $bean->db->query($query);
while ($row = $bean->db->fetchByAssoc($result)) {
$resultArray[$row['id']] = $row['Name'];
}
return $resultArray;
}
Dropdowns in Sugar work as key/value pairing, the key is what is stored in the database and Sugar does the appropriate lookup to display the value. Except the list view seems to work differently for dynamic dropdowns.
Instead of building your array as $resultArray[$row['id'] ]= $row['Name'] you could use the username -$resultArray[$row['username'] ]= $row['Name']as usernames have to be unique but will be more meaningful to your users in the list view.
However, is there any reason you're not using a relate field to the Users module? That should solve all your problems without any coding.

Only outputting specific fiels from Podio Item Filter

We need to extract specific fields, no matter if they have data or not.
If I f.ex. want an putput with only the field with external id : 'logo' i am trying this:
$limit = 10;
$app_id = xxxxxxx;
$items = PodioItem::filter($app_id,array('Limit' => $limit,'external_id' => 'logo'));
Within podio not all the fields are filled in, and the result is that we do not get a return value for field 'logo'.
When we don't get a return from field logo - the array output is not structured in a way so we can grab the data we need.
How do we achieve this ?
Added text
Rough Example on print output:
items[0] = Companyname,Logo,Address,Phone,Country
(has data in Logo)
items[1] = Companyname,Address,Phone,Country,ContactPerson
(no data in Logo)
items[2] = Companyname,Address,Phone,Country
PROBLEMS ARISING
items[2][4] is not existing (but i won't know this)
items[0][2] is Address Field (but i won't know this)
items[1][2] is Phone Field (but i won't know this)
Looking for Company Contact Person [x][4] accross items will end in
[0] = Wrong (country)
[1] = Right data (ContactPerson)
[2] = PHP error
Like in SQL, i would take the coloumn_name_1,coloumn_name_2, etc.. and grab data from only these fields.
PodioItem objects have a collection of item fields. As you've noted only fields that have values are included (since including empty fields is redundant). As you've noted you can't reliably access the fields by their array offset.
What you should do it access it by field_id or external_id. These are the unique identifiers. You can use the field method on the PodioItem object for this purpose:
$items = PodioItem::filter(...);
foreach ($items['items'] as $item) {
// Get field with external_id "logo". You can also pass in field_id
$field = $item->field('logo');
if ($field) {
print "Found a logo field!";
}
}

How to apply PHP function to a column returned from CakePHP query

In my controller I am retrieving records from my institutions table with the following fields
$params = array(
'fields' => array(
'Institution.id',
'Institution.name',
'Institution.about',
'Institution.picture'),
);
$institutions = $this->Institution->find('all',$params);
How can I prefix each 'Institution.picture' field with the full URL address, 'Institution.picture' itself only holds the name of the file.
I would also like to perform html_entity_decode() on each 'Institution.about' value from the returned set.
I know how to do this only without the framework if I make custom queries from scratch, then I would iterate each row and apply PHP functions to the field of interest. But is there a place in CakePHP (find or paginator) that I can specify such PHP manipulation on each field value from the returned set?
NOTE: I don't like to do this in the View, as I want to output it as json directly
You can define a virtualField for model:
public $virtualFields = array('image_url' => "CONCAT('/img/', Institution.picture)");
$params = array(
'fields' => array(
'Institution.id',
'Institution.name',
'Institution.about',
'Institution.picture',
'Institution.image_url'),
);
$institutions = $this->Institution->find('all',$params);
Unfortunaly MySQL doesn't have a function to decode HTML entities. You may utilize an afterFind() callback instead of virtualField. This lets you to decode entities as well as add a prefix.
CakePHP is php
Just iterate over the array and prepare it however you want:
$institutions = $this->Institution->find('all',$params);
$prefix = '/img/'; // <- define this
foreach ($institutions as &$row) {
$row['Institution']['about'] = html_entity_decode($row['Institution']['about']);
$row['Institution']['picture'] = $prefix . $row['Institution']['picture'];
}
If this is always required it can be applied to all finds via an afterFind method in the institution class.
I think you should do it in the View. See this example.
Hash::map can be very useful here. By specifying path you can only modify slices of the set.

Categories