Laravel - minor data processing after getting Eloquent model instance - laravel

I'm just beginning to learn Laravel and the MVC way of thinking, so please bear with me. How can I approach this, and is there a standard in where to put the processed data?
I would fetch for example, a department_id (e.g. ABC) and personnel_number (e.g. 1234) from the database and I want to piece them together as one (e.g. ABC1234). So I want to prepare an object property code after an Eloquent Personnel is prepared.
Should I just alter the Eloquent object after it is created like:
$personnel = App\Personnel::find(1);
$personnel->code = $personnel->department_id . $personnel->personnel_number;
//or
$personnel->code(); //returns ABC1234
Or should I process the data in the Personnel model and fit everything into a new object? Like:
$personnel_data = new PersonnelData(App\Personnel::find(1));
//so I can access the personnel code using this, which is processed in constructor
$personnel_data->code;
//and access the model using this:
$personnel_data->model;
Or some other way?
There must be some general practice I can follow, because there are times when this is needed, e.g.:
site URL when you only store a part of it, e.g. Google Drive file URL when you only have the file ids
human readable time when you only store the timestamps
person's full name when you store their first name and last name separately
...
Is there a common/standard way to prepare these beforehand and not process them only when you need them?

If you just want a property that is a concatenated string of other properties on the same model, then overload the attribute in the model:
public function getCodeAttribute() {
return $this->department_id . $personnel->personnel_number;
}
Then you can just call $personnel->code

Related

How to create a GraphQL query that returns data from multiple tables/models within one field using Laravel Lighthouse

Im trying to learn GraphQL with Laravel & Lighthouse and have a question Im hoping someone can help me with. I have the following five database tables which are also defined in my Laravel models:
users
books
user_books
book_series
book_copies
I'd like to create a GraphQL endpoint that allows me to get back an array of users and the books they own, where I can pull data from multiple tables into one subfield called "books" like so:
query {
users {
name
books {
title
issue_number
condition
user_notes
}
}
}
To accomplish this in SQL is easy using joins like this:
$users = User::all();
foreach ($users as $user) {
$user['books'] = DB::select('SELECT
book_series.title,
book.issue_number
book_copies.condition,
user_books.notes as user_notes
FROM user_books
JOIN book_copies ON user_books.book_copy_id = book_copies.id
JOIN books ON book_copies.book_id = books.id
JOIN book_series ON books.series_id = book_series.id
WHERE user_books.user_id = ?',[$user['id']])->get();
}
How would I model this in my GraphQL schema file when the object type for "books" is a mashup of properties from four other object types (Book, UserBook, BookCopy, and BookSeries)?
Edit: I was able to get all the data I need by doing a query that looks like this:
users {
name
userBooks {
user_notes
bookCopy {
condition
book {
issue_number
series {
title
}
}
}
}
}
However, as you can see, the data is separated into multiple child objects and is not as ideal as getting it all in one flat "books" object. If anyone knows how I might accomplish getting all the data back in one flat object, Id love to know.
I also noticed that the field names for the relationships need to match up exactly with my controller method names within each model, which are camelCase as per Laravel naming conventions. Except for my other fields are matching the database column names which are lower_underscore. This is a slight nitpick.
Ok, after you edited your question, I will write the answer here, to answer your new questions.
However, as you can see, the data is separated into multiple child objects and is not as ideal as getting it all in one flat "books" object. If anyone knows how I might accomplish getting all the data back in one flat object, Id love to know.
The thing is, that this kind of fetching data is a central idea of GraphQL. You have some types, and these types may have some relations to each other. So you are able to fetch any relations of object, in any depth, even circular.
Lighthouse gives you out of the box support to eloquent relations with batch loading, avoiding the N+1 performance problem.
You also have to keep in mind - every field (literally, EVERY field) in your GraphQL definition is resolved on server. So there is a resolve function for each of the fields. So you are free to write your own resolver for particular fields.
You actually can define a type in your GraphQL, that fits your initial expectation. Then you can define a root Query field e.g. fetchUsers, and create you custom field resolver. You can read in the docs, how it works and how to implement this: https://lighthouse-php.com/5.2/the-basics/fields.html#hello-world
In this field resolver you are able to make your own data fetching, even without using any Laravel/Eloquent API. One thing you have to take care of - return a correct data type with the same structure as your return type in GraphQL for this field.
So to sum up - you have the option to do this. But in my opinion, you have to write more own code, cover it with tests on you own, which turns out in more work for you. I think it is simpler to use build-in directives, like #find, #paginate, #all in combination with relations-directives, which all covered with tests, and don't care about implementation.
I also noticed that the field names for the relationships need to match up exactly with my controller method names within each model, which are camelCase as per Laravel naming conventions.
You probably means methods within Model class, not controller.
Lighthouse provides a #rename directive, which you can use to define different name in GraphQL for your attributes. For the relation directives you can pass an relation parameter, which will be used to fetch the data. so for your example you can use something like this:
type User {
#...
user_books: [Book!]! #hasMany(relation: "userBooks")
}
But in our project we decided to use snak_case also for relations, to keep GraphQL clean with consistent naming convention and less effort

Where to process data in Laravel

Back when I was using CodeIgniter I had functions in my models like
public function GetArticlesFormatted($inactive = FALSE)
and then in my controller I could have had
$articles->GetArticlesFormatted(true);
And so on.
How should I achieve the same with Laravel 5.4? The database of the app I'm building is already built and full and is a mess so most of the "automated" super-restrictive things that Laravel has don't work out of the box.
For example there is a Country Code that I'm retrieving and I need it as is, but in some instances I need it converted in a Country Name which I had in another table.
Right now I just have in my controller wherever I am retrieving data from the model:
$countryResult = Country::where('country_code', $item['country_code'])->first();
$countryArray = $countryResult->toArray();
$item['country'] = $countryArray['country_name'];
Can I somehow achieve that in a more elegant way?
I tried accessors but for some reason couldn't get anything to work for my purposes.
A select query can be used to limit selection to a particular column.
$countryName = Country::select('country_name')
->where('country_code', $item['country_code'])
->first();

How do I store static data in Laravel?

In our application we will give titles to users based on their points. So, if a user has 10-99 points, that user might get the "Novice" title, but a user with 100-199 points might get the "Regular User" title. I plan on eager loading a user's points using an attribute and relationship, and once I have those points I will use an attribute method to assign the title.
But how do I get the list of possible titles?
I could make a model, a migration, and a seed file, but I feel like these titles won't change much and certainly would never need to be updated in an API call. I could also hardcode an array of points and titles and do a quick lookup to see which title belongs to a user, but then I need to somehow deliver those titles to the user in an Attribute method. Or I could store them in a repository or the cache.
Can I access a repository from within a model? Is it better to store this sort of data in a DB anyways, regardless of how often it's updated or queried?
You could use entries in your .env file to store the entries and then use some logic in your php to select the correct .env entry.
LEVEL_1_TITLE=Novice
LEVEL_2_TITLE=Regular
...
if($user->points < 99){
$title = env('LEVEL_1_TITLE');
}
...
Or do the same thing from an array in a class that you create and just select the correct array entry based on the points.

When does laravel make a new query when getting relation

If i get users company name like this:
Auth::users()->company->name
And if i do it multiple times on same function, it only makes one query for relation and the rest it uses the value retrieved the first time.
I want to know, how deep this goes and is it possible to make it go deeper. If i, for example, use it in controller and then again on the view, does it make a new query? How can i make it so it would only make the same query once per request (page view)?
Clarification:
Question was not about eager loading. The question was about how to avoid making the SAME query more than once. Something that is used a lot, like, users company, users tag, users settings. Data that many parts of my application may need but making a new query every time in different parts of my app feels like wasting resources.
When using Eloquent, if you have a model related to another one, you can eager-load the related models in order to minimize the number of queries.
For example, if you have a Movie model related to a Genre model (a movie belongsTo a genre, a genre hasMany movies). If you have 10 movies:
$movies = Movie::all();
foreach ($movies as $m) echo $m->genre->name;
// 11 queries are gemerated (get all + 1 per item)
whereas :
$movies = Movie::with('genre')->get();
foreach ($movies as $m) echo $m->genre->name;
// 2 queries are gemerated (get all movies + get all genres for those movies)
Note that you can eager-load multiple and/or nested relations.
e.g.: with(['genre', 'actors', 'actors.profile'])
Edit following clarification:
You can then use a middleware which will load your common data. Register that middleware globally so that it can be executed for all requests.
If needed, make an additional facade and a kind of global data manager to make that data available later on.

MVC Linq to SQL Update Object in database

I have a table called Code in my LINQ to SQL datacontext. I also have a class called Codes in my Models folder. What I want to do is save the updated object Codes to my database table Code. Is this possible?
In my controller, I would pass the edited Object to my Model. My CodesRepository file contains this:
public Codes EditCode(Codes CodeToEdit)
{
private EventsDataContext _db = new EventsDataContext();
Codes C = new Codes();
C = CodeToEdit;
_db.Codes.InsertOnSubmit(C); //error here, something about invalid arguments
//InsertOnSubmit is for adding a new object, but I don't know the syntax
// for editing an existing object.
_db.SubmitChanges();
}
This is probably not the correct way of doing this so can someone point me in the right direction? Do I even need a class called Codes or do I need to somehow just use my database table? Thanks.
Solution: I decided to change from Linq to SQL to an Entity Framework and it works much better. This way, I don't have to define my Codes class since it comes straight from the database and I was able to delete the Codes class file.
You should use DataContext.Attach when you get an object back that corresponds to en existing row in the database. For Linq-to-sql's optimistic concurrency handling to work this requires that you either have the original, unsaved object available, or that you have a TimeStamp column in the database. The latter is preferred, as it only requires one extra field to be handled (probably through a hidden field in the web form).

Resources