Laravel two-way data transformer - php

I've been looking for a Laravel transformer that will format the fields both ways. Meaning I transform it when I return it to the client and then transform it too before saving it again to the database.
I know I can do this already using Fractal's Transformer but i'm looking for a way (either code or 3rd party library) for the transforming to be automatic. Right now i'm doing it like this for the save functionality:
$data = transform($request->all()); //transforms the input into database field names
$person = Person::create($data);
return response()->json(transform($person), 200); //before returning I transform it to field names needed by client
I'm using a legacy database so the fields I used in the frontend and the database doesn't match. It's also a big app so I think it would be better if there was a way to use a Trait or maybe something like an inheritance from the model level instead of doing the code above from a controller, repository, service.

Use accessors and mutators to get and save the data into DB, while use $maps property on Model to change the fields names for the front end.
class User extends Model
{
protected $maps = ['name_in_db' => 'name_on_frontend'];
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}
}

Related

Custom Field Name on Eloquent Model

I have a table posts and this table has some columns like pst_id, pst_title, pst_content, pst_category_id and like that. I wanna represent this fields with some better names on json output, actually I'm trying to remove the prefix pst_ from column names.
I tested multiple ways. At first I tried to make an alias for this columns on DB layer, for example Post::select(['pst_id as id'])->get(). This idea generally is awful, because it makes the column names inconsistent across of the software (each developer may have a convention to naming a field). So I insist to find a way for naming the columns on model layer.
The next solution was for using Accessors and Mutators. Although it covers the problem of previous way, but it's really hard to implement 20 methods for each model! 20x100 ~ 2000 methods!!! :/
The last solution which I tested was about using the mappable feature of https://github.com/jarektkaczyk/eloquence. It's really good, I can put all old fields to $hidden property and add new ones to $appends for showing on output. But this solution also have a problem. If I add all new fields to $appends, when I use select statement for choosing some columns, the non-selected columns will be showed on output with a null value :|. Well, I tried to override mappedselect and parseMappings methods on a base model for adding new names to $appends dynamically, but it doesn't satisfy me. In fact it becomes very tricky on using and I'm not sure that the team can accept it and use it easily.
So that's the problem: "Is there a way for renaming the name of columns on output for eloquent?". GoLang has a very good feature which is called Struct Tags. You can define some tags for your structure, for example like this:
type Post struct {
Pst_id int `json:"id"`
Pst_title string `json:"title"`
Pst_content string `json:"content"`
}
And when you produce a json for a Post structure with json.Marshal, based on tags, it gives you a json like this:
{
"id": 23,
"title": "Custom Field Tags for Eloquent",
"content": "I tried a lot of things, but they are hard. I'm a programmer so I'm lazy! What can I do?",
}
I think we don't have something like this in the php world, but is there any way to use the idea behinds doctrine's annotation for implementing something like tag structure in Go?
Any comments and idea are welcome!
First step would be to override a couple methods on those models. The first method is the getAttribute() which is called when you access an attributed of a model so you can access it. You would want to be able to access the attribute without the pst_ prefix so you would do:
public function getAttribute($key)
{
if(array_key_exists($prefixedKey = 'pst_'.$key, $this->attributes)) {
return $this->attributes[$prefixedKey];
}
return parent::getAttribute($key);
}
Then to make sure the keys don't have the prefix when casting to json you would override the attributesToArray() method which is what is called when outputting json and will also respect your $hidden, $visible, $casts and $dates arrays. That would be something like:
public function attributesToArray()
{
$attributes = parent::attributesToArray();
$mutated = [];
foreach ($attributes as $key => $value) {
$mutated[preg_replace('/^pst_/', '', $key)] = $value;
}
return $mutated;
}
To implement those you can extend the Model class with an abstract class that implements those methods and have your classes extend that base class or create a trait with those methods and have your classes implement that trait.
I would probably use Fractal Transformer from the League.
You basically create a mapping class and apply it to the collection.
The Transformer class would look like this
<?php
namespace App;
use App\Post;
use League\Fractal;
use League\Fractal\TransformerAbstract;
class PostTransformer extends TransformerAbstract{
public function transform(Post $post)
{
return [
'id' => (int) $post->pst_id,
'title' => $post->pst_name,
'content' => $post->pst_uuid,
];
}
}
Then in your controller or where ever you transform the collection.
$posts = Post::all();
$manager = new Manager();
$manager->setSerializer(new CursorSerializer());
$resource = new Collection($posts, new PostTransformer());
$formattedCollection = $manager->createData($resource);
The docs are pretty good and it is pretty straight forward to implement it.

Is it possible to get a collection of different model types for a model using eloquent?

If I have a model that needs to have a property that is an array of different models. Is there an eloquent method or way to handle this kind of problem?
eg.
I have a Feature model that needs a method that gets an array of objects that are from different models.
class Feature extends Model
{
public function getArrayOfDifferentObjects()
{
$array_of_objects=array();
???? ELOQUENT to get objects from different models ????
return $array_of_objects;
}
}
I have a feature_model_connections table with the following:
feature_id
featured_model_id
featured_model_type
The featured_model_type value would be a string denoting the model type.
The model_id would be a foreign key of the relevant model's table.
However I can't see how you would be able to use eloquent to return data for the getArrayOfDifferentObjects method in features model.
Any pointers would be much appreciated. Many thanks, J
What you are describing there, is basicly a Polymorphic Relations, which can handle these cases, and making fetching them easy, instead of i'm making a made up case, read the documentation, it is well written, under the section Polymorphic Relations. https://laravel.com/docs/5.1/eloquent-relationships#polymorphic-relations
Within your scope right now, you can do something like this.
public function getArrayOfDifferentObjects()
{
$objects = [];
$features = DB::table('feature_model_connections')
->select('feature_id', 'featured_model_id', 'featured_model_type')->get();
foreach($features as $feature)
{
$type = '\\App\\' . $feature->featured_model_type; //App is you app namespace
$model = $type::find($feature->featured_model_id);
if($model)
$objects[] = $model;
}
return $objects;
}
The basics of this, is you can define different types, with the app namespace seed, from there staticly call them, which will access the predefined type in your database table, then find the element and add it to the array. With that said, this is done as of the top of my head, no compile check, not ranned in Laravel, but it should pretty much get you the idea of what to do, with that said, if you can change your structure, go with the Polymorphic Relations, it is really awesome.

What is the proper way to save form data in Laravel (using an injected model)?

I'm trying to setup a simple form to save, but want to be sure we are using best practices, like DI.
In the controller file, I have
public function store()
{
//get form data
$data = Input::all();
$newclient = new Client($data);
$newclient->save();
return Redirect::route('clients.index');
}
But that really isn't dependency injection. (right?) I injected the model like this
public function __construct(\Client $clientmodel)
{
$this->clientmodel=$clientmodel;
}
How would I save the form data on the store function properly, using dependency injection?
Looking at the __constructor in Illuminate\Database\Eloquent\Model you can see it uses fill() to assign the passed in values.
public function __construct(array $attributes = array())
{
$this->bootIfNotBooted();
$this->syncOriginal();
$this->fill($attributes);
}
So you can just use fill() to do the same after the class has been instantiated:
$this->clientmodel->fill($data)
$this->clientmodel->save();
Or if you want to just save it anyways, you can use create():
$this->clientmodel->create($data);
If you are always creating a new object then you can use the Eloquent Model's create method like so:
public function store()
{
//get form data
$data = Input::all();
$this->clientmodel->create($data);
return Redirect::route('clients.index');
}
If it's possible that sometimes this route will be handling updates of an existing Client record then you should take a look at the firstOrCreate and firstOrNew methods.
What you did is just a good practice. That'd save you a lot of codes. However, there are more remainders.
Always iterate over each input to check which input is absent. Well, in your case, it's a simple form. But if it's an API, clients send various inputs and you need to know what to expect. Sometimes when an input is absent, it can cause serious problems like assigning the value of an undefined input to a model.
Always check whether an input is valid. Make sure every input is up to your standard. You can make various validations to make it happen.
Other than the above two points, you've walked pretty far on the right track.

Symfony2 - Create a Doctrine filter to select current user data

I'm building a Saas / Multitenant application using Symfony 2. I've created a Doctrine event-subscriber to add and update the owner of a row, the user who created it, the user who modified it, timestamps and so.
And now I need to implement some kind of filter so when a user is logged in, he only can see data from his company. My first though was using a Doctrine preLoad event, but this event doesn't exist... As far as I know, I must use Doctrine filters, isn't it? If so, how can this filter access user data to read the company id? Must I inject it using the Dependency Injection? Is there any standard way to accomplish my goal?
UPDATE
What I'm looking for is to create some kind of Doctrine plugin/hook so everytime I call any function that fetch data from the database (find, findOneBy, etc), and the entity I'm fetching implements a particular interface, an extra 'AND company_id=:id' SQL sequence is added to the generated query, so neither the controller or the model receives data from other companies.
For this you can use a DoctrineFilter
Link from official doc Doctrine2 SQL Filters
namespace Rwmt\Bundle\RwmtBundle\DoctrineFilters;
use Doctrine\ORM\Mapping\ClassMetaData,
Doctrine\ORM\Query\Filter\SQLFilter;
class MultiTenantFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
// Check if the entity implements the MultiTenant interface
if (!$targetEntity->reflClass->implementsInterface('Rwmt\Bundle\RwmtBundle\Entity\MultiTenant')) {
return "";
}
return $targetTableAlias.'.tenant_id = ' . $this->getParameter('tenantId');
}
}
And to set the parameter tenantId used in the filter you must enable the filter and set the parameter
$filter = $em->getFilters()->enable('multi_tenant');
$filter->setParameter('tenantId', $tenant->getId(), 'integer');
As for the MultiTenant interface is just something for the entities to implement
namespace Rwmt\Bundle\RwmtBundle\Entity;
use Rwmt\Bundle\RwmtBundle\Entity\Tenant;
interface MultiTenant
{
public function setTenant(Tenant $tenant);
public function getTenant();
}
Filtering in Doctrine2 is simple. Just assign a filter function to a variable, then send that variable as a parameter through the filter() method included in the ArrayCollection class.
$closure = function($list_item) use($user) {
return $list_item->belongsToSameCompanyThatEmploys($user) === true;
};
$filtered_array_collection = $arrayYouWantToFilter->filter($closure);
In this example, you'd have to have previously defined a method belongsToSameCompanyThatEmploys($user) in list_item's class that returns true if it belongs to the same company that the user works for.
UPDATE You need to also indicate that the filter function should use the local variable user, since it won't otherwise due to having its own scope.

Hide columns when serializing via toArray()

I have a simple problem where I often return CRUD type Ajax requests with array serialized versions of Doctrine 1.2 models. I'd love to be able to simply return the toArray() method after the execute() result, however, this will display data about my models that I don't wish to expose. A simple example is on my user model the password and salt get displayed. While I realize those are already hashed values, it's something I'd rather not return as a JSON response.
I've poured over the Doctrine 1.2 manual, but did not find anything that offered the type of functionality I'm looking for. I realize I can iterate over the result to manually unset() the columns I wish to hide, but I'm hoping a more native solution is out there that I've overlooked.
Why don't you build your own toArray() ?
If you want to do that, you will have to extends the sfDoctrineRecord class that inherit from all Base* class. It is describe in the doc.
You have to put the configureDoctrine() inside config/ProjectConfiguration.class.php.
Then you will have a class like that:
class myDoctrineRecord extends sfDoctrineRecord
{
}
So you can easily add your custom toArray() here:
class myDoctrineRecord extends sfDoctrineRecord
{
public function toArray($deep = true, $prefixKey = false, array $excludeFields = array())
{
// do every thing like the original toArray
// but when a column match one entry in $excludeFields, don't add it
}
}
So, when using the toArray() method with an array of fields for the third parameters, they will be excluded from the result.

Categories