Laravel manage different API responses - php

Explanation
User scans barcode and system responses with a barcodable model (can be Article, Package or Inventory Shelf).
return new BarcodeResource($barcode);
Barcode resource resolves barcodable resource based on barcodable class. Each barcodable model return different JSON resouce.
// BarcodeResource.php
$modelResource = app()->makeWith(__NAMESPACE__ . '\\' . class_basename($this->barcodable) . 'Resource', [
'resource' => $this->barcodable
]);
return [
'code' => $this->code,
'model_type' => class_basename($this->barcodable),
'model_data' => $modelResource
];
In case of...
... Article, I'd like to print packages that contain those kind of articles
... Package, I'd like to print location (inventory shelf), included articles and child packages
... Inventory Shelf, I'd like to print all packages
Problem
I want to prevent infinity loops with recursive resources.
Article
>> Package
>> Article (infinity loop begins because package resource
returns articles in spesific package)
Package
>> Article
>> Package (loop...)
>> Inventory Shelf
>> Package (loop...)
>> Child package
Inventory Shelf
>> Package
>> Article
>> Inventory Shelf (loop...)
>> Child package
Eager loading and unsetting relations should be one solution, but how I can unset those in the correct phase? Is this even possible with one resources or should I make multiple resources (recursive/normal)?
Tries
Extra attribute containing relations
I tried this solution, but magically $this->relations attribute gets changed to integer 1 after couple recursions...
class PackageResource extends JsonResource
{
private $relations;
public function __construct($resource, array $relations = [])
{
parent::__construct($resource);
$this->relations = $relations;
}
public function toArray($request)
{
return [
'id' => $this->id,
'articles' => $this->when(in_array('articles', $this->relations), ArticleResource::collection($this->articles, $this->relations)),
'children' => PackageResource::collection($this->children, $this->relations),
];
}

My solution for a similar situation was as follows:
In the Resource files, I allways return relationships based on a request property with. This is attached to the request as follows:
I need the User with Orders and Profile, but I also need the Area for an order, than the request is something like this:
http://example.com/api/v1/user/234?with=user.orders,user.profile,orders.area
and in the Resource file something similar:
public function toArray($request)
{
$return = [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'location' => $this->location,
'active' => $this->isActive(),
'level' => $this->level,
];
if($request->has('with')){
$relationships = [
'orders'=>[OrderCollection::class, 'orders'],
'area'=>[Area::class, 'area', 'area.admin'],
'profile'=>[UserProfile::class, 'profile'],
];
$with = explode(',', $request->with);
foreach($relationships as $key => $relationship){
if( in_array("user.".$key, $with) ){
$return[$key] = new $relationship[0]($this->{$relationship[1]});
}
}
}
$return['created'] = $this->created_at->toDateTimeString();
return $return;
}
An other solution is to add an extra property to the resource class:
protected $with = "";
public function __construct(mixed $resource, $with="")
{
parent::__construct($resource);
}
Than, when you call that resource, you can filter it in the previous way. I just tested and it worked for me.
Hope that helps.

Related

How to change order of items in SilverStripe GridField

I use a GridField in SilverStripe to create HTML section items that are rendered on a page. This works so far but it always displays the sections in the order that I added them to the CMS or rather by the ID it gets when it's created.
So my question is: How can i change that order. I don't want to manually change the IDs but would rather do a simple drag and drop.
Edit: Could the use of Elemental be a solution to this problem?
Screenshot of the CMS view
The Page:
class HomePage extends Page
{
private static $has_many = [
'SectionObjects' => SectionObject::class
];
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Sections',
$grid = GridField::create('SectionObjects', 'Sections', $this->SectionObjects(), GridFieldConfig_RecordEditor::create())
);
$config = $grid->getConfig();
$dataColumns = $config->getComponentByType(GridFieldDataColumns::class);
$dataColumns->setDisplayFields([
'ID' => 'ID',
'LastEdited' => 'Changed'
]);
return $fields;
}
}
The Section object
class SectionObject extends DataObject{
private static $db = [
'Content' => 'Text',
'BgColor' => Color::class
];
private static $has_one = [
'HomePage' => HomePage::class
];
public function getCMSFields(){
return new FieldList(
TextareaField::create('Content','SectionContent'),
ColorField::create('BgColor', 'Hintergrundfarbe')
);
}
}
Definitely using the elemental module would allow you to have the kind of behaviour you want - but if you don't want to go to the effort of refactoring your code to comply with that module, you could use either the gridfieldextensions or sortablegridfield module to allow you to sort the gridfield (with drag and drop) to your heart's content.

Laravel Cache helper "driver" not implemented

I have a test that is calling a Controller, which literally handles a call to some logic service I mocked.
The test
public function test_it_can_list_all_folders()
{
$mockedLogicResponse = [
"id" => 1111,
"parent_id" => 2222,
"name" => "Some Name",
"children" => [
[
"id" => 3333,
"parent_id" => 5555,
"name" => "Ad-hoc"
],
[
"id" => 4444,
"parent_id" => 6666,
"name" => "Another thing"
]
]
];
Cache::shouldReceive('has')
->once()
->with('campaign_folders')
->andReturn(false);
$this->instance(
Logic::class,
Mockery::mock(Logic::class, function (MockInterface $mock) use ($mockedLogicResponse) {
$mock->shouldReceive('fetchData')
->once()
->andReturn($mockedLogicResponse);
})
);
// httpGet is just a wrapper for a call('GET', ..), it's tested and working fine
$response = $this->httpGet($route);
$response->assertOk(); //This goes well
$this->assertEquals($mockedLogicResponse, $response->json()); //This goes well too
}
Controller:
class LogicController extends Controller {
protected $logic;
public function __construct(Logic $logic)
{
$this->logic = $logic;
}
public function index(Request $request)
{
$id = $request->get('folder_id');
return $this->logic->fetchData($id);
}
}
Logic:
class Logic {
public function fetchData(string $id): array
{
if (Cache::has('folders')) {
return Cache::get('folders');
}
//This is returning correctly the data
$foldersList = $this->getFolders(...);
foreach ($foldersList[$folder['id']] as $folder) {
$res = [....];
// We perform some irrelevant logic
$children = ['children' => $res[$folder['id']]];
$fetchedFolders[] = array_merge($folder, $children);
}
Cache::put('folders', $fetchedFolders, 3600);
return $fetchedFolders;
}
}
The problem(s) are a few, for starters I'm receiving this:
Mockery\Exception\BadMethodCallException : Received Mockery_2_Illuminate_Cache_CacheManager::driver(), but no expectations were specified
It's good to point out that I am literally copying an example from the documentation here Laravel docs, so I can't see I'm missing any step.
Also, as the Cache is being called from the mocked logic, but the method is calling them (I dumped the result of the "has" Cache method)
As I also want to test (In another test) that the Cache::get() is begin called when requested the data for a second time, how can I clean the Cache (I set it for an hour), in order to test something like so:
Cache::shouldReceive('has')->twice();
Cache::shouldReceive('get')->once();
Cache::shouldReceive('put')->once();
Is there any step I am missing? If so, which ones?
UPDATE: After googling a bit, I found this solution, which in part solves my testing issues, but I'm concerned why the official documentation is not working, in order to use it instead of a custom solution.
Kind regards

Yii2 saving model with relationship property

iam trying to save model from form, that have relationship defined via junction table, but since the property is relationship object it is read-only and it fails on validation.
Model relationship:
public $payer
/**
* #return \yii\db\ActiveQuery
*/
public function getPayerRelationship()
{
return $this->hasMany(PartyRelationship::className(), ['contract_id' => 'id'])->where(['relationship' => 'P']);
}
public function getPayers(){
return $this->hasMany(ContractingParty::className(), ['id' => 'contracting_party_id'])
->via('payerRelationship');
}
public function getContractors() { // could be a static func as well
$model = ContractingParty::find()->asArray()->all();
return ArrayHelper::map($model, 'id', 'subject_name');
}
Form view:
<?= $form->field($model, 'payers')->widget(Select2::classname(), [
'data' => $model->getContractors(),
'language' => 'en',
'options' => ['placeholder' => '-- Select company --'],
'pluginOptions' => [
'allowClear' => true,
'multiple' => true,
],
'showToggleAll' => false
]) ?>
It wont validate or save, because of read-only property payers. I tried to use different property in $form->field($model, 'payer'... (instead of payers), then validation works and even saving works, but trouble is, that editing have no preselected values of that model, because they are in model->payers. And i have no idea, what iam supposed to pass here instead of this relationship object (or property of model in general).
Maybe iam plainly blind, but in manual there is a lot of information about getting data from db, but almost no info about saving.
(btw. i saw this post: Yii2 Invalid Call: Setting read-only property - but that didnt give me any new piece of information at all).
Is my form design wrong, or model design (Meaning i should just create form field using two models)? Thanks
Adding setters to model:
public function setPayer(){
$payer_id_array = array();
$payer_array = ArrayHelper::toArray($this->payers);
foreach ($payer_array as $value){
$payer_id_array [] = $value['id'];
}
$this->payer = $payer_id_array;
}
public function setRecipient(){
$recipient_id_array = array();
$recipient_array = ArrayHelper::toArray($this->recipients);
foreach ($recipient_array as $value){
$recipient_id_array [] = $value['id'];
}
$this->recipient = $recipient_id_array;
}
and manually into controller (action create and update):
$model->setPayer();
$model->setRecipient();
seems to fix the conflict between the names of relation and property passed into the field.

Get all relationships from Eloquent model

Having one Eloquent model, is it possible to get all its relationships and their type at runtime?
I've tried taking a look at ReflectionClass, but I couldn't find anything useful for this scenario.
For example, if we have the classic Post model, is there a way to extract relationships like this?
- belongsTo: User
- belongsToMany: Tag
To accomplish this, you will have you know the names of the methods within the model - and they can vary a lot ;)
Thoughts:
if you got a pattern in the method, like relUser / relTag, you can filter them out
or loop over all public methods, see if a Relation object pops up (bad idea)
you can define a protected $relationMethods (note: Laravel already uses $relations) which holds an array with method.
After calling Post->User() you will receive a BelongsTo or 1 of the other objects from the Relation family, so you can do you listing for the type of relation.
[edit: after comments]
If the models are equipped with a protected $with = array(...); then you are able to look into the loaded relations with $Model->getRelations() after a record is loaded. This is not possible when no record is loaded, since the relations aren't touched yet.
getRelations() is in /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
But currently it doesn't show up in the api at laravel.com/api - this is because we got newer version
Like Rob stated. It is a bad idea to loop through every public method and check out if a relation is returned.
Barryvdh uses a Regex based approach in his very popular Laravel-ide-helper:
https://github.com/barryvdh/laravel-ide-helper/blob/master/src/Console/ModelsCommand.php
You just have to filter the properties you receive after calling getPropertiesFromMethods like this (untested example):
class classSniffer{
private $properties = [];
//...
public function getPropertiesFromMethods($model){
//the copied code from the class above (ModelsCommand#getPropertiesFromMethods)
}
public function getRelationsFrom($model){
$this->getPropertiesFromMethods($model);
$relations = [];
foreach($this->properties as $name => $property){
$type = $property;
$isRelation = strstr($property[$type], 'Illuminate\Database\Eloquent\Relations');
if($isRelation){
$relations[$name] = $property;
}
}
return $relations;
}
}
Is there a cleaner way of doing that without touching the Models?
I think we have to wait for PHP7 (Return Type Reflections) or for a new Reflection Service from Taylor ^^
I've been working on the same thing lately, and I don't think it can effectively be done without Reflection. But this is a little resource-intensive, so I've applied some caching. One check that's needed is to verify the return type, and pre-php7, that can only be done by actually executing each method. So I've also applied some logic that reduces the number of likely candidates before running that check.
/**
* Identify all relationships for a given model
*
* #param object $model Model
* #param string $heritage A flag that indicates whether parent and/or child relationships should be included
* #return array
*/
public function getAllRelations(\Illuminate\Database\Eloquent\Model $model = null, $heritage = 'all')
{
$model = $model ?: $this;
$modelName = get_class($model);
$types = ['children' => 'Has', 'parents' => 'Belongs', 'all' => ''];
$heritage = in_array($heritage, array_keys($types)) ? $heritage : 'all';
if (\Illuminate\Support\Facades\Cache::has($modelName."_{$heritage}_relations")) {
return \Illuminate\Support\Facades\Cache::get($modelName."_{$heritage}_relations");
}
$reflectionClass = new \ReflectionClass($model);
$traits = $reflectionClass->getTraits(); // Use this to omit trait methods
$traitMethodNames = [];
foreach ($traits as $name => $trait) {
$traitMethods = $trait->getMethods();
foreach ($traitMethods as $traitMethod) {
$traitMethodNames[] = $traitMethod->getName();
}
}
// Checking the return value actually requires executing the method. So use this to avoid infinite recursion.
$currentMethod = collect(explode('::', __METHOD__))->last();
$filter = $types[$heritage];
$methods = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC); // The method must be public
$methods = collect($methods)->filter(function ($method) use ($modelName, $traitMethodNames, $currentMethod) {
$methodName = $method->getName();
if (!in_array($methodName, $traitMethodNames) //The method must not originate in a trait
&& strpos($methodName, '__') !== 0 //It must not be a magic method
&& $method->class === $modelName //It must be in the self scope and not inherited
&& !$method->isStatic() //It must be in the this scope and not static
&& $methodName != $currentMethod //It must not be an override of this one
) {
$parameters = (new \ReflectionMethod($modelName, $methodName))->getParameters();
return collect($parameters)->filter(function ($parameter) {
return !$parameter->isOptional(); // The method must have no required parameters
})->isEmpty(); // If required parameters exist, this will be false and omit this method
}
return false;
})->mapWithKeys(function ($method) use ($model, $filter) {
$methodName = $method->getName();
$relation = $model->$methodName(); //Must return a Relation child. This is why we only want to do this once
if (is_subclass_of($relation, \Illuminate\Database\Eloquent\Relations\Relation::class)) {
$type = (new \ReflectionClass($relation))->getShortName(); //If relation is of the desired heritage
if (!$filter || strpos($type, $filter) === 0) {
return [$methodName => get_class($relation->getRelated())]; // ['relationName'=>'relatedModelClass']
}
}
return false; // Remove elements reflecting methods that do not have the desired return type
})->toArray();
\Illuminate\Support\Facades\Cache::forever($modelName."_{$heritage}_relations", $methods);
return $methods;
}
I have the same needs on my project. My solution is using get_class function to check type of relation. example:
$invoice = App\Models\Invoice::with('customer', 'products', 'invoiceProducts', 'invoiceProduct')->latest()->first();
foreach ($invoice->getRelations() as $relation => $items) {
$model = get_class($invoice->{$relation}());
$type = explode('\\', $model);
$type = $type[count($type) - 1];
$relations[] = ['name' => $relation, 'type' => $type];
}
dd($relations);
example result:
array:4 [▼
0 => array:2 [▼
"name" => "customer"
"type" => "BelongsTo"
]
1 => array:2 [▼
"name" => "products"
"type" => "BelongsToMany"
]
2 => array:2 [▼
"name" => "invoiceProducts"
"type" => "HasMany"
]
3 => array:2 [▼
"name" => "invoiceProduct"
"type" => "HasOne"
]
]
I need it for duplicate an model item including the relation
composer require adideas/laravel-get-relationship-eloquent-model
https://packagist.org/packages/adideas/laravel-get-relationship-eloquent-model
Laravel get relationship all eloquent models!
You don't need to know the names of the methods in the model to do this. Having one or many Eloquent models, thanks to this package, you can get all of its relationships and their type at runtime
I know its bit late, but I have been visiting this question multiple times so thought to share my observations to help those who visits this question in future.
Here is the method i used to extract the relationships from an eloquent model class.
/**
*
* Returns all the relationship methods defined
* in the provided model class with related
* model class and relation function name
*
* #param string $modelClass exampe: App\Models\Post
* #return array $relattions array containing information about relationships
*/
protected function getModelRelationshipMethods(string $modelClass)
{
//can define this at class level
$relationshipMethods = [
'hasMany',
'hasOne',
'belongsTo',
'belongsToMany',
];
$reflector = new ReflectionClass($modelClass);
$path = $reflector->getFileName();
//lines of the file
$lines = file($path);
$methods = $reflector->getMethods();
$relations = [];
foreach ($methods as $method) {
//if its a concrete class method
if ($method->class == $modelClass) {
$start = $method->getStartLine();
$end = $method->getEndLine();
//loop through lines of the method
for($i = $start-1; $i<=$end-1; $i++) {
// look for text between -> and ( assuming that its on one line
preg_match('~\->(.*?)\(~', $lines[$i], $matches);
// if there is a match
if (count($matches)) {
//loop to check if the found text is in relationshipMethods list
foreach ($matches as $match) {
// if so add it to the output array
if (in_array($match, $relationshipMethods)) {
$relations[] = [
//function name of the relation definition
'method_name' => $method->name,
//type of relation
'relation' => $match,
//related Class name
'related' => (preg_match('/'.$match.'\((.*?),/', $lines[$i], $related) == 1) ? $related[1] : null,
];
}
}
}
}
}
}
return $relations;
}
If you dd() or dump() the returned $relations for the App/Post model, The output will be something like this
^ array:3 [
0 => array:3 [
"method_name" => "user"
"relation" => "belongsTo"
"related" => "User::class"
]
1 => array:3 [
"method_name" => "tag"
"relation" => "belongsToMany"
"related" => "Tag::class"
]
2 => array:3 [
"method_name" => "comments"
"relation" => "hasMany"
"related" => "Comment::class"
]
]

Zend Framework 2 - Hydrator strategy for Doctrine relationship not working

As mentioned here I'm building a custom hydration strategy to handle my related objects in a select box in a form.
My form looks like this:
$builder = new AnnotationBuilder($entityManager);
$form = $builder->createForm(new MyEntity());
$form->add(new MyFieldSet());
$hydrator = new ClassMethodsHydrator();
$hydrator->addStrategy('my_attribute', new MyHydrationStrategy());
$form->setHydrator($hydrator);
$form->get('my_attribute')->setValueOptions(
$entityManager->getRepository('SecEntity\Entity\SecEntity')->fetchAllAsArray()
);
When I add a new MyEntity via the addAction everything works great.
I wrote fetchAllAsArray() to populate my selectbox. It lives within my SecEntityRepository:
public function fetchAllAsArray() {
$objects = $this->createQueryBuilder('s')
->add('select', 's.id, s.name')
->add('orderBy', 's.name ASC')
->getQuery()
->getResult();
$list = array();
foreach($objects as $obj) {
$list[$obj['id']] = $obj['name'];
}
return $list;
}
But in the edit-case the extract() function doesn't work. I'm not at the point where I see something of hydrate() so I'll leave it out for now.
My hydrator strategy looks like this:
class MyHydrationStrategy extends DefaultStrategy
{
public function extract($value) {
print_r($value);
$result = array();
foreach ($value as $instance) {
print_r($instance);
$result[] = $instance->getId();
}
return $result;
}
public function hydrate($value) {
...
}
The problem is as follows:
Fatal error: Call to a member function getId() on a non-object
The print_r($value) returns loads of stuff beginning with
DoctrineORMModule\Proxy__CG__\SecEntity\Entity\SecEntity Object
following with something about BasicEntityPersister and somewhere in the mess are my referenced entities.
The print_r($instance) prints nothing. It's just empty. Therefore I guess is the error message legit... but why can't I iterate over these objects?
Any ideas?
Edit:
Regarding to #Sam:
My attribute in the entity:
/**
* #ORM\ManyToOne(targetEntity="Path/To/Entity", inversedBy="whatever")
* #ORM\JoinColumn(name="attribute_id", referencedColumnName="id")
* #Form\Attributes({"type":"hidden"})
*
*/
protected $attribute;
My new selectbox:
$form->add(array(
'name' => 'attribute',
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'attributes' => array(
'required' => true
),
'options' => array(
'label' => 'MyLabel',
'object_manager' => $entityManager,
'target_class' => 'Path/To/Entity',
'property' => 'name'
)
));
My final hope is that I'm doing something wrong within the controller. Neither my selectbox is preselected nor the value is saved...
...
$obj= $this->getEntityManager()->find('Path/To/Entity', $id);
$builder = new \MyEnity\MyFormBuilder();
$form = $builder->newForm($this->getEntityManager());
$form->setBindOnValidate(false);
$form->bind($obj);
$form->setData($obj->getArrayCopy());
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$form->bindValues();
$this->getEntityManager()->flush();
return $this->redirect()->toRoute('entity');
}
}
I still haven't come around to write the tutorial for that :S
I don't know if this is working with the annotationbuilder though! As the DoctrineModule\Form\Element\ObjectSelect needs the EntityManager to work. The options for the ObjectSelect are as follows:
$this->add(array(
'name' => 'formElementName',
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'attributes' => array(
'required' => true
),
'options' => array(
'label' => 'formElementLabel',
'empty_option' => '--- choose formElementName ---',
'object_manager' => $this->getEntityManager(),
'target_class' => 'Mynamespace\Entity\Entityname',
'property' => 'nameOfEntityPropertyAsSelect'
)
));
In this case i make use of $this->getEntityManager(). I set up this dependency when calling the form from the ServiceManager. Personally i always do this from FactoryClasses. My FormFactory looks like this:
public function createService(ServiceLocatorInterface $serviceLocator)
{
$em = $serviceLocator->get('Doctrine\ORM\EntityManager');
$form = new ErgebnishaushaltProduktForm('ergebnisform', array(
'entity_manager' => $em
));
$classMethodsHydrator = new ClassMethodsHydrator(false);
// Wir fügen zwei Strategien, um benutzerdefinierte Logik während Extrakt auszuführen
$classMethodsHydrator->addStrategy('produktBereich', new Strategy\ProduktbereichStrategy())
->addStrategy('produktGruppe', new Strategy\ProduktgruppeStrategy());
$hydrator = new DoctrineEntity($em, $classMethodsHydrator);
$form->setHydrator($hydrator)
->setObject(new ErgebnishaushaltProdukt())
->setInputFilter(new ErgebnishaushaltProduktFilter())
->setAttribute('method', 'post');
return $form;
}
And this is where all the magic is happening. Magic, that is also relevant to your other Thread here on SO. First, i grab the EntityManager. Then i create my form, and inject the dependency for the EntityManager. I do this using my own Form, you may write and use a Setter-Function to inject the EntityManager.
Next i create a ClassMethodsHydrator and add two HydrationStrategies to it. Personally i need to apply those strategies for each ObjectSelect-Element. You may not have to do this on your side. Try to see if it is working without it first!
After that, i create the DoctrineEntity-Hydrator, inject the EntityManager as well as my custom ClassMethodsHydrator. This way the Strategies will be added easily.
The rest should be quite self-explanatory (despite the german classnames :D)
Why the need for strategies
Imo, this is something missing from the DoctrineEntity currently, but things are still in an early stage. And once DoctrineModule-Issue#106 will be live, things will change again, probably making it more comfortable.
A Strategy looks like this:
<?php
namespace Haushaltportal\Stdlib\Hydrator\Strategy;
use Zend\Stdlib\Hydrator\Strategy\StrategyInterface;
class ProduktbereichStrategy implements StrategyInterface
{
public function extract($value)
{
if (is_numeric($value) || $value === null) {
return $value;
}
return $value->getId();
}
public function hydrate($value)
{
return $value;
}
}
So whenever the $value is not numeric or null, meaning: it should be an Object, we will call the getId() function. Personally i think it's a good idea to give each Element it's own strategy, but if you are sure you won't be needing to change the strategy at a later point, you could create a global Strategy for several elements like DefaultGetIdStrategy or something.
All this is basically the good work of Michael Gallego aka Bakura! In case you drop by the IRC, just hug him once ;)
Edit An additional resource with a look into the future - updated hydrator-docs for a very likely, soon to be included, pull request

Categories