ZF2 - Access collection values in the view - collections

I would like to access an array of values that has been populated in my fieldset.
In the controller I populate and array and add it to my model before calling the .phtml. I know the data is there (print_r/var_dump shows it) but I don't know how to access it.
Example of the var_dump:
["object":protected]=>
object(Instrument\Model\EFMultiEquip)#337 (5) {
["iadid"]=>
string(7) "ABI3130"
["featureset"]=>
NULL
["availfeatures"]=>
array(1) {
["availfeatureset"]=>
array(10) {
[0]=>
array(4) {
["ibacode"]=>
string(3) "C3I"
["ibadesc"]=>
string(16) "Jouan centrifuge"
["ibacost"]=>
string(4) "5.00"
["ibafeatime"]=>
string(1) "0"
}
I need to access the "availfeatureset" array. I've tried all kinds of gets but this data just eludes me. If any of you have an idea on how to go about accessing the data I'd appreciate it.
Vaughn

Related

Create or Read item in DynamoDb

I'm trying to read an item with ID of X from DynamoDB (Using Appsync graphql) and I want it to create a default item if there is none.
This seems like it should be a normal use case. But the solutions I've tried have all been pretty bad:
I tried to create a Pipeline resolver that would first get the item, then in a second function create an item if there was no item in the result from the previous function. This had with returning the read item.
I tried making a PutAction with the condition that an item with this ID doesn't work. This does what I need it to, but I can't change the response from an error warning, no matter what I do to the response mapping template.
So how does one efficiently create a "read - or create if it does not exist" resolver for DynamoDb?
It turns out that I was close to the solution.
According to this documentation: https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-dynamodb.html#aws-appsync-resolver-mapping-template-reference-dynamodb-condition-handling
Create a putItem resolver that conditionally checks if there is an item with the same unique identifier (in DynamoDB that's usually a primary key and a sort key combination)
If the resolver determines the read object to not be different from the intended new object a warning will not be sent. So we can simply remove ALL fields from the comparison.
Example:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"id" : { "S" : "${ctx.args.id}" }
},
"condition" : {
"expression" : "attribute_not_exists(id)",
"equalsIgnore": [ "__typename", "_version", "_lastChangedAt", "_createdAt", "name", "owner"]
},
"attributeValues": {
"name": { "S" : "User Username" }
}
}

AppSync Resolver only works when I hard code the input. context.arguments does not work

Edit for clarity: There are no error messages, it simply returns an empty list if the input string is from the context.arguments, suggesting that it simply isn't getting the input variable out on the query tester (setting it up incorrectly brings up that famous typing error of course). I've also made this into a pipeline with the exact same result. Looking around, people suggest making an intermediate object, but surely I'm just getting my input variables out wrong somehow.
I'm working on a project in AWS Appsync using DynamoDB and I've run into a problem with the context.arguments input.
Basically the code all works if I hardcode the string for the book id into the query (full context to follow), but if I use the context.arguments, it simply refuses to work properly, returning an empty array for the "spines".
I have the following types in my schema:
type Book {
id: ID!
title: String
spines: [Spine]
}
type Spine {
id: ID!
name: String
bookId: ID!
}
I use the following query:
type Query {
getBook(id: ID!): Book
query getBook($bookId: ID!){
getBook(id: $bookId){
title
id
spines {
name
bookId
}
}
}
With the following input (assume this is a relevant guid):
{
"bookId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
}
And this resolver for the spines object:
{
"version" : "2017-02-28",
"operation" : "Query",
"index" : "bookId-index",
"query" : {
"expression": "#bookId = :bookId",
"expressionNames" : {
"#bookId" : "bookId"
},
"expressionValues" : {
":bookId" : { "S" : "${context.arguments.id}" }
}
}
}
}
I made sure my data set contained false positives too (spines for other books) so that I know when my query brings back the correct data.
This works if I hardcode a guid as string instead of using context.arguments, and gets exactly what I'm looking for for each book guid.
For example, replacing the expression values with this works perfectly:
"expressionValues" : {
":bookId" : { "S" : "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" }
}
Why does "${context.arguments.id}" not get the input variable here the same way as it seems to in other queries?
Thanks to #IonutTrestian for pointing me in the right direction.
$ctx.args was empty, but I decided to go up the chain to see what was in the entire context, so $util.error($util.toJson($ctx)).
The json object I found included a little object called "Source", which contained the query return for the Book object.
Long story short, $ctx.source.id when applied to my query worked a charm.
I also know a bit more about debugging DynamoDB resolvers in case I encounter problems like this in future. Thank you so much!

AWS AppSync Query to shape response data (Similar to Group By in SQL)

I have one DynamoDB table with all the data I need for the client, however, I want to shape the data the client receives to reduce client-side manipulation.
My Schema:
type StateCounty {
id: ID!
StateName: String
CountyName: String
FIPSST: Int
FIPSCNTY: Int
Penetration: String
Date: String
}
and to return a custom query I have the type:
type Query {
getStateCountybyState(StateName: String): StateCountyConnection
}
This works - and with a simple query
query getStateCountybyState {
getStateCountybyState (StateName: "Delaware") {
items {
StateName
CountyName
Date
}
}
}
the results are returned as expected:
{
"StateName": "Delaware",
"CountyName": "Kent",
"Date": "02-01-2017"
},
{
"StateName": "Delaware",
"CountyName": "Sussex",
"Date": "02-01-2016"
},
{
"StateName": "Delaware",
"CountyName": "New Castle",
"Date": "02-01-2018"
}
etc.
I would like to return the data in the following format:
{
"StateName": "Delaware" {
{ "CountyName": "Kent",
"Date": "02-01-2017"
},
{
"CountyName": "Sussex",
"Date": "02-01-2016"
},
{
"CountyName": "New Castle",
"Date": "02-01-2018"
}
}
}
I have tried adding GroupCounty: [StateCountyGroup] to the schema:
type StateCounty {
id: ID!
StateName: String
CountyName: String
FIPSST: Int
FIPSCNTY: Int
Penetration: String
Date: String
GroupCounty: [StateCountyGroup]
}
and then a reference to that in the query
query getStateCountybyState {
getStateCountybyState (StateName: "Delaware") {
items {
StateName
CountyName
Date
GroupCounty: [StateCountyGroup]
}
}
}
I think my issue is within the resolver - currently, it is configured to use the StateName as a key, but I am not sure how to pass the StateName from the primary query to the subquery.
Resolver:
{
"version" : "2017-02-28",
"operation" : "Query",
"query" : {
"expression" : "StateName = :StateName",
"expressionValues" : {
":StateName" : { "S" : "${context.arguments.StateName}" },
}
},
"index" : "StateName-index-copy",
"select" : "ALL_ATTRIBUTES",
}
Any guidance appreciated - I have gone through the documentation several times, but cannot find an example.
UPDATE
I tried the suggestion below from Richard - and it is definitely on the right track, however, despite multiple variations on the theme, I either return null or the following error (I eliminated some of the county objects returned in the error for brevity):
"message": "Unable to convert set($myresponse = {\n \"Delaware\":
[{SSA=8000, Eligibles=32295, FIPS=10001, StateName=Delaware, SSACNTY=0,
Date=02-01-2016, CountyName=Kent, Enrolled=3066, Penetration=0.0949,
FIPSCNTY=1, FIPSST=10, SSAST=8, id=6865},
{SSA=8010, Eligibles=91332, FIPS=10003, StateName=Delaware, SSACNTY=10, Date=02-01-2016, CountyName=New Castle, Enrolled=10322, Penetration=0.113, FIPSCNTY=3, FIPSST=10, SSAST=8, id=6866},
{SSA=0, Eligibles=10, FIPS=10, StateName=Delaware, SSACNTY=0, Date=02-01-2018, CountyName=Pending County Designation, Enrolled=0, Penetration=0, FIPSCNTY=0, FIPSST=10, SSAST=0, id=325},
{SSA=8000, Eligibles=33371, FIPS=10001, StateName=Delaware, SSACNTY=0, Date=02-01-2017, CountyName=Kent, Enrolled=3603, Penetration=0.108, FIPSCNTY=1, FIPSST=10, SSAST=8, id=3598},
{SSA=8020, Eligibles=58897, FIPS=10005, StateName=Delaware, SSACNTY=20, Date=02-01-2016, CountyName=Sussex, Enrolled=3760, Penetration=0.0638, FIPSCNTY=5, FIPSST=10, SSAST=8, id=6867}) \nnull\n\n to class java.lang.Object."
}
]
}
From reading the above, it sounds like your original query is returning the correct results that you want but not in the response format that you would prefer, as you would like the "StateName" to be a top-level JSON key with the value being a JSON object of the state which you passed in as an argument. Is that accurate? If so then why not use the same query that already works but with a different response template. Something like:
#set($myresponse = {
"$ctx.args.StateName": $ctx.result.items
})
$util.toJson($myresponse)
Note that $myresponse isn't exactly the same as you had above as your example with "stateName" : "Delaware" { ... } wasn't completely valid JSON so I didn't want to make an assumption on what a good structure would be, but the point remains if you're already getting the proper results from your query I would just try to change the structure of your GraphQL results.
Now if I misread the above and you're NOT getting the proper results from the query, the other way that I could read your statement of "primary query to the subquery" is that you're trying to apply an additional "filter" to your query results. If that is the case then you need something like this:
{
"version" : "2017-02-28",
"operation" : "Query",
"query" : {
"expression" : "StateName = :StateName",
"expressionValues" : {
":StateName" : { "S" : "${context.arguments.StateName}" },
}
},
"index" : "StateName-index-copy",
"select" : "ALL_ATTRIBUTES",
"filter" : {
"expression" : "#population >= :population",
"expressionNames" : {
"#population" : "population"
},
"expressionValues" : {
":population" : $util.dynamodb.toDynamoDBJson($ctx.args.population)
}
}
}
I used an example here where maybe your query also needed to filter by the population size in each county. This may not be representative of what you're looking for but hopefully it helps.
EDITED WITH MORE INFORMATION 4/16/18
I've written up more information on this in a step-by-step manner, to go through the concepts in pieces.
The key here is not just the response template, but also the fields that you're requesting to be returned (as this is the nature of GraphQL). Let's walk through this by way of example. Now that you're returning an individual item with GraphQL (since your response template is converting an array to a single item) so you'll need to change the expected GraphQL query response type. Suppose you have a GraphQL type in your schema like this:
type State {
id: ID!
population: String!
governor: String!
}
type Query {
allStates: [State]
}
If you just convert the response in the template as above you'll see an error like "type mismatch error, expected type LIST" if you run something like this:
query {
allStates{
id
population
}
}
That's because your response is no longer returning the individual items. Instead you'll need to change the GraphQL response type [State] to match what your template conversion is doing State like so:
type State {
StateName: String
}
type Query {
allStates: State
}
Now if your resolver request template is doing something that returns a list of items (like a DynamoDB scan or Query) you can convert the list to a single item in the response template like so:
#set($convert = {"StateName" : $ctx.result.items })
$util.toJson($convert)
Then run the following GraphQL query:
query {
allStates{
StateName
}
}
And you'll get a single object containing an array of your results back:
{
"data": {
"allStates": {
"StateName": "[{id=1, population=10000, governor=John Smith}]"
}
}
}
However while this might be pointing out the errors you are having, this is returning a StateName and from your original question I think you are looking to do a bit more by combining records in the response for some optimization, along with some potential filtering. One way to do this would be to create an array (or you could create a map {}) and populate it based on some conditional. For example modify your query to have a StateName as an argument:
type Query {
allStates(StateName: String!): Post
}
Then you can filter on this in the resolver response template, by using a #foreach and an #if() conditional, then calling .add() only if items in the response are for the state which you requested:
#set($convert = {"StateName" : [] })
#foreach($item in $ctx.result.items)
#if($item["StateName"]=="$ctx.args.StateName")
$util.qr($convert.get("StateName").add("$item"))
#end
#end
$util.toJson($convert)
So now you could run something like this:
query {
allStates(StateName:"Texas"){
StateName
}
}
And this will give you back just the results for that specific state which you passed as an argument. But you'll notice the selection set of the query is StateName. You could introduce a bit more flexibility by having the possible states listed in your GraphQL type:
type State {
StateName: String
Seattle: String
Texas: String
}
Now you alter your resolver response template to use the argument for building up the return array since it can specify this in the selection set:
#set($convert = {"$ctx.args.StateName" : [] })
#foreach($item in $ctx.result.items)
#if($item["StateName"]=="$ctx.args.StateName")
$util.qr($convert.get("$ctx.args.StateName").add("$item"))
#end
#end
$util.toJson($convert)
So I can run this query:
query {
allPosts(StateName:"Seattle"){
Seattle
}
}
And I get back my result. Note though that passing Seattle as the argument but requesting back Texas:
query {
allPosts(StateName:"Seattle"){
Texas
}
}
This will not work as the response object you created in your map was Seattle: [...] but you had Texas as the selection set.
The final thing that you might want to do is have multiple states returned, which you could do by building up one giant map keyed by the state name, or maybe it's done using the arguments or the selection set through adding state names to the return type as demonstrated above. That's up to you so I'm not sure how you'll want that but hopefully this demonstrates how you can manipulate the responses to meet your needs.

Struggling with .find() vs. .findOne() in my Meteor app

I realize that this might be very close to other posts, but I just can't get this to stick in my head! :( I need some help in trying to understand how to use .find() or should I be using .findOne()? (so confused) for a collection of mine.
Goal:
I want to get all of the documents out of the People collection and then for each document I want to create a new <option> where the .name is put in for text and the collection ._id is the value.
Here's some code:
The Collection results from Mongo
db.people.find()
{ "_id" : "1", "name" : "John" }
{ "_id" : "2", "name" : "Mike" }
{ "_id" : "3", "name" : "George" }
{ "_id" : "4", "name" : "Jane" }
My Template Helper :
Template.view_Admin_Staff.people = function() {
console.log( 'people : ', People.find() );
return People.find();
};
My Template :
<select id="ddStaffID" name="staff">
<option value="">-- Select One --</option>
{{#each people}}
<option value="{{_id}}">{{name}}</option>
{{/each}}
</select>
My console.log found in the Helper returns undefined. What in the world am I missing?
I am going to assume that your collection is People = new Meteor.Collectin('people'); And your template name for in the html is
When you did the console log in your template helper, you are logging the cursor itself, not the documents. You can find out more about cursor at (https://www.eventedmind.com/tracks/feed-archive/how-do-client-cursors-work).
To see are you returning the correct data, you could .fetch method on a cursor. Fetch will return an array of objects based on your query. In your case, the query is empty, the fetch method should return everything that is available in the client's db.
To answer your question in the title. both find and findOne are methods available on the Collection object.
find return a cursor. findOne return AN object, if there is a record matched to your query.
Bloody-h3ll! Warning to all us n00bs out there...one must subscribe to your publications in order for one to see and work with your publications. sigh
The code in my initial post is working as expected. I just simply forgot to subscribe to my exposing data in my publication in my client-side route. </foreheadSlap> I'm new enough to have doubted my query skills and didn't even thing to troubleshoot any further back in the code. Thank you #Bozhao and #DavidWeldon for your quick replies.

Single integer as key in firebase (Firebase array behavior)

If I insert data into node/a/0 in firebase.
the result will treat a is an array a[0].
The same way, if I set my data in node/a/1 the first array will become null
"a" : [ null, {
"-J-22j_Mb59l0Ws0H9xc" : {
"name" : "chok wee ching"
}
} ]
But it will be fine if node/a/2
"a" : {
"2" : {
"-J-25xjEXUqcpsC-5LOE" : {
"name" : "chok wee ching"
}
}
}
my guess, firebase automatically treat single 0 and 1 as an array.
How can I prevent this?
Firebase has no native support for arrays. If you store an array, it really gets stored as an "object" with integers as the key names.
However, to help people that are storing arrays in Firebase, when you call .val() or use the REST api to read data from Firebase, if the data looks like an array, Firebase will render it as an array.
In particular, if all of the keys are integers, and more than half of the keys between 0 and the maximum key in the object have non-empty values, then Firebase will render it as an array. It's this latter part of the heuristic that you are running into. Your second example only sets a value for 2, not 0 and 1, so less than half of the keys have values, and therefore Firebase renders it as an object.
You can't currently change or prevent this behavior (though it's a common source of confusion so we'll likely make some tweaks here in the future). However it's usually very easy to deal with the behavior once you understand it. If you need further help, perhaps you can expand your question to explain what you need to accomplish and how this behavior is preventing it.
I've encountered the same problem, but actually wanted to have a numeric key array in Swift ([Int:AnyObject]). I've written this function to make sure to always have an array (without null values):
func forceArray(from: Any?) -> [Int:AnyObject] {
var returnArray = [Int:AnyObject]()
if let array = from as? [String:AnyObject] {
for (key, value) in array {
if let key = Int(key) {
returnArray[key] = value
}
}
return returnArray
}
if let array = from as? [AnyObject] {
for (key, value) in array.enumerated() {
if !(value is NSNull) {
returnArray[key] = value
}
}
return returnArray
}
return returnArray
}
Result:
["0":1, "1":2] becomes: [0:1, 1:2]
{"0":1, "6":2} becomes: [0:1, 6:2]
["0":1, "1": null, "2":2] becomes: [0:1, 2:2]
Hope this is helpful for someone!
Yes, storing 0 and 1 as the key will have an issue as Firebase will think it is an array.
My simple workaround is using:
String(format:"%03d", intValue)
So that the resulting key will be "000" and "001", and they can be converted back to Int with ease.
TL;DR;
Workaround for REST API: add a meaningless filter like this one orderBy="$.key"&startAt="0" which actually filters-out all items with negative key): https://workaround-arrays-bagohack.firebaseio.com/matchesHeuristic.json?orderBy=%22$key%22&startAt=%220%22&print=pretty
Explanation
This is a known and unfortunate (IMHO) behaviour of Firebase, documented deep down in their support knowledge base. Quote from Best Practices: Arrays in Firebase:
Firebase has no native support for arrays. If you store an array, it
really gets stored as an "object" with integers as the key names.
// we send this ['hello', 'world']
// Firebase stores this {0:'hello', 1: 'world'}
However, to help people that are storing arrays
in Firebase, when you call .val() or use the REST api to read data, if
the data looks like an array, Firebase will render it as an array.
In particular, *if all of the keys are integers, and more than half of
the keys between 0 and the maximum key in the object have non-empty
values, then Firebase will render it as an array. This latter part is
important to keep in mind.
You can't currently change or prevent this behavior. Hopefully
understanding it will make it easier to see what one can and can't do
when storing array-like data.
So I've set up a small repro for you. Original data:
{
"matchesHeuristic": {
"1": {
"id": "foo",
"value": "bar"
},
"2": {
"id": "w",
"value": "tf"
}
},
"notMatchesHeuristic": {
"1": {
"id": "foo",
"value": "bar"
},
"365": {
"id": "w",
"value": "tf"
}
}
}
As returned by Firebase REST API: https://workaround-arrays-bagohack.firebaseio.com/.json?print=pretty
{
"matchesHeuristic" : [ null, {
"id" : "foo",
"value" : "bar"
}, {
"id" : "w",
"value" : "tf"
} ],
"notMatchesHeuristic" : {
"1" : {
"id" : "foo",
"value" : "bar"
},
"365" : {
"id" : "w",
"value" : "tf"
}
}
}
as you can see matchesHeuristic object is transformed into an array with a null value at index 0 (because it matches the heuristic defined in Firebase docs) whereas notMatchesHeuristic is left intact. This is especially "nice" if you have dynamic data like we do - so we don't know untill runtime if it will match heauristic or not.
Workaround (REST API)
However this portion of the docs doesn't seem to hold:
You can't currently change or prevent this behavior. Hopefully
understanding it will make it easier to see what one can and can't do
when storing array-like data.
You can actually workaround this by requesting items searched by key, so
this https://workaround-arrays-bagohack.firebaseio.com/matchesHeuristic.json?print=pretty is broken
this is intact https://workaround-arrays-bagohack.firebaseio.com/matchesHeuristic.json?orderBy=%22$key%22&startAt=%220%22&print=pretty (add a meaningless filter like this one orderBy="$.key"&startAt="0" which actually filters-out all items with negative key):
{"1":{"id":"foo","value":"bar"},"2":{"id":"w","value":"tf"}}
NB: interestingly, seems Firebase support guys don't know about thi workaround (at least they didn't suggest it when we asked them about this behavior).

Resources