Laravel response transformations not working - php

I have the following
controller function
public function show()
{
$users_id = Request::segment(4);
// $this->user_bank_details_repository->setPresenter(new UserBankAccountPresenter);
$account = $this->user_bank_details_repository->findByField('users_id', $users_id, $columns = ['user_bank_details_id','bank_name','bank_account_number','bank_ifsc_code','beneficiary_name','bank_account_type','bank_branch','created_at']);
if(!$account)
{
return $this->response->noContent();
}
return $this->response->item($account, new UserBankAccountTransformer);
}
Transformer
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace App\Api\V1\Transformers;
use App\Entities\UserBankDetails;
use League\Fractal\TransformerAbstract;
class UserBankAccountTransformer extends TransformerAbstract {
public function transform(UserBankDetails $bank_details)
{
return [
'id'=> (int) $bank_details->user_bank_details_id,
'bank_name' => $bank_details->bank_name,
'bank_account_number' => (int) $bank_details->bank_account_number,
'bank_account_type' => $bank_details->bank_account_type,
'bank_beneficiary_name' => $bank_details->beneficiary_name,
'bank_branch'=> $bank_details->bank_branch
];
}
}
I am using repository pattern design and using dingo for my REST framework.
I always get the response as
{
"user_bank_details": [
{
"user_bank_details_id": 1,
"bank_name": "jbjb",
"bank_account_number": "939393933939",
"bank_ifsc_code": "ABCD0000047",
"beneficiary_name": "Gaf",
"bank_account_type": "savings",
"bank_branch": "Mad(E)",
"created_at": "2015-12-23 17:05:39"
}
]
}
instead of the transformed json. I dont get any errors as well. Tried using
return $this->response->item($account, new UserBankAccountTransformer::class);
But still the response is not getting transformed. I tried whatever I could but I dont get it worked :(

Even though this question is over a year ago, however for the sake of anyone who would have similar issue, the suspect here is the fact that you used: item instead of collection.
If the $account contains a collection, then it means this answer is correct: that is, this:
return $this->response->item($account, new UserBankAccountTransformer::class);
Should be:
return $this->response->collection($account, new UserBankAccountTransformer::class);
PS: I also Encountered this on Laravel 5.2.* thats why I feel this might help.

Related

Laravel 9 multiple tag flush not working when flushing individual tags

I've noticed an issue with my Laravel application, and i'm not quite sure if i'm just doing something stupidly wrong, or there is a genuine issue.
So i'm storing and fetching my cached data with multiple tags on my App\Models\User\User model like below
public function getSetting(String|Bool $key = false) : Mixed {
//Try
try {
return cache()->tags(["user_{$this->id}_cache", 'user_settings'])->remember("user_{$this->id}_settings", now()->addDays(1), function(){
return $this->settings()->pluck('value', 'key')->toArray();
});
} catch(\Exception $e){
//Logging errors here
}
}
This function simply grabs all of the users settings and returns an array.
I am using 2 cache tags because I want to cover both scenarios
The ability to be able to remove all cached items for a specific model (User)
The ability to be able to remove a specific type of cache across all models (Users)
The Laravel cache documentation simply states to pass the tag (or tags as an array) that you want to remove.
So my thinking is that if I want to clear user settings cache for all users, I should be able to run the following
cache()->tags('user_settings')->flush();
and if I want to remove all cache for a specific user, I should be able to run
cache()->tags('user_1_cache')->flush();
But for some reason, only the second example (using user_1_cache) works? If I run the first example and try to clear all cache with the tags user_settings, the function returns true but does not clear the cache?
Am I doing something stupidly wrong or just completely misunderstanding how the cache tags work?
Versions
PHP - 8.1
Laravel - 9.3.8
Cache driver - Redis
I reproduced your scenario here. It's working as stated in the docs.
class User extends Model
{
protected $fillable = ['id', 'name'];
public function cacheSettings()
{
return cache()->tags([$this->getUserCacheKey(), 'user_settings'])->remember("{$this->id}_settings", now()->addDay(), function () {
return $this->only('name');
});
}
public function getSettings()
{
return cache()->tags([$this->getUserCacheKey(), 'user_settings'])->get("{$this->id}_settings");
}
public function getUserCacheKey()
{
return "user_{$this->id}_cache";
}
}
These tests run with no problem:
public function test_cache_flush_all_users()
{
Cache::clear();
$alice = new User(['id' => 1, 'name' => 'alice']);
$john = new User(['id' => 2, 'name' => 'john']);
$alice->cacheSettings();
$john->cacheSettings();
Cache::tags('user_settings')->flush();
// both deleted
$this->assertNull($alice->getSettings());
$this->assertNull($john->getSettings());
}
public function test_cache_flush_specific_user()
{
Cache::clear();
$alice = new User(['id' => 1, 'name' => 'alice']);
$john = new User(['id' => 2, 'name' => 'john']);
$alice->cacheSettings();
$john->cacheSettings();
Cache::tags($alice->getUserCacheKey())->flush();
// only alice deleted
$this->assertNull($alice->getSettings());
$this->assertNotNull($john->getSettings());
}
Not having all details of your implementation, perhaps you can figure out what is causing the issue.

How to return an updated data after updating in Laravel Model?

I have a model Driver which have columns: name, branch, status_id, etc..Updating is actually fine and working, my problem is how can I return the updated one?
Here's what I tried so far, but it returns a boolean, resulting of returning an error in my console:
The Response content must be a string or object implementing __toString(), "boolean" given.
public function updateStatus(Driver $driver)
{
return $driver->update($this->validateStatus());
}
public function validateStatus()
{
return $this->validate(request(), [
'status_id' => 'required|min:1|max:3'
]);
}
I expect it should return the all the columns of a driver.
I've been to this link but it doesn't helped. Someone knows how to do this?
You can use tap() helper, which will return updated object after the update like so:
return tap($driver)->update($this->validateStatus());
More on that here: Tap helper
return as object instead of boolean type
public function updateStatus(Driver $driver)
{
$driver->update($this->validateStatus());
return $driver;// first way
// return tap($driver)->update($this->validateStatus()); //second way
}
public function validateStatus()
{
return $this->validate(request(), [
'status_id' => 'required|min:1|max:3'
]);
}
I think no need any model helper for that
in controller you can do like this
$driver = Driver::find(1);
$driver->name = "expmale";
$driver->save();
return $driver;
or other way
$driver = Driver::find(1);
$driver->update([
'name'=> "expmale"
]);
return $driver;
It's work for me
$notify = tap(Model::where('id',$params['id'])->select($fields))->update([
'status' => 1
])->first();
I know that there's already an answer for this, but ideally, you don't want to use the update method. It's just a model helper method that doesn't really add much. Internally it does what I have included below, except it returns the result of save().
You'd want to do something like this:
if ($driver->fill($this->validateStatus)->save()) {
return $driver;
}
throw new \RuntimeException('Update failed, perhaps put something else here);
The problem you're going to have with the accepted answer (and most of the others) is that you return the model without ever checking that it was actually updated, so you're going to run into issues down the line when it's not updating the actual database even though it's reporting that it is.

Yii2 Routing rules conflicts with each other

I have the following 2 rules:
'blog/<action>' => 'blog/default/<action>',
'blog/<slug:[0-9a-zA-Z\-.]+>' => 'blog/default/view',
Also I have the following actions:
public function actionCheckSlug($slug) {
}
public function actionCreate() {
}
public function actionView($slug) {
return $this->render("view");
}
When I try to access this URL for example (action URL):
/blog/check-slug?slug=test
It's working without any problems but when I try to access this URL for example (Slug URL):
/blog/test-test-test
I will get an error:
yii\base\InvalidRouteException: Unable to resolve the request: blog/default/test-test-test
Because the fist rules is being parsed instead of the second one.
I tried to reverse them for example but it didn't work (always one is not working), also tried others scenarios but no success
Any idea how to make it works?
i would suggest first of all not using the same url convention for both actions
'blog/<action>' => 'blog/default/<action>',
'blog/<slug:[0-9a-zA-Z\-.]+>' => 'blog/default/view',
could easily become
'blog/<action>' => 'blog/default/<action>',
'post/<slug:[0-9a-zA-Z\-.]+>' => 'blog/default/view',
or use 'blog/page-<slug:[0-9a-zA-Z\-.]+>', blog/post/ .. or really just any convention that doesn't clash with your existing structure
if that's not something just you wanna do, or cant? in your app, you can just use the slug to check for existing app structure.
public function view($slug){
$model = $this->findBySlug($slug);
return $this->render('view', ['model' => $model]);
}
private function findBySlug($slug){
if ($this->hasMethod('action' . Inflector::classify($slug))
// this should prevent recursion
&& $slug != $this->action->id){
$this->runAction($slug);
return null;
}
return Post::find()->where(['slug' => $slug])
}
note: this is just an example of how to (or how not to?). don't run my bad, untested code in any production environment

Behat - Using an example table String in an xpath selector array

I have been using Behat for a year or so at a level fine for the automation of most websites but I now need to start using it more for user generated content, I am relatively new to PHP and at the moment I am struggling how to use a String entered in an Example table in an x-path array:
Feature: Campaign
Scenario Outline: Pass campaign string to xpath array
Then I add a new campaign name of "<campaign>"
Examples:
|campaign |
|Automation|
The context file looks like this
/**
* #Then /^I add a new campaign name of "([^"]*)"$/
*/
public function iAddANewCampaignNameOf($campaign)
{
/**
* #var CreateCampaign $createCampaign
*/
$createCampaign= $this->getPage('CreateCampaign');
$createCampaign->campaignName($campaign);
}
Then I use the Page Object extension for the class Campaign.php
class CreateCampaign extends AutomationPage
{
protected $path = 'someURL';
public $campaign;
protected $elements = array(
'campaignHeader' => array('xpath' => "//*[#id='site-navigation-campaigns']"),
);
public function campaignName ($campaign)
{
$this->campaign = $campaign;
$this->getSession()->wait(5000);
$this->getElement('campaignName')->setValue($campaign);
}
So far so good, the tester can enter a campaign name of "Automation" - it gets passed through the context file and the campaign name is set in the browser.
What I am lacking is to be able to retain this $campaign name string and use it in another page so I can reference it in another array i.e. for selecting an existing campaign as follows:
SecondPageObjectPage.php
class ReferenceCampaign extends AutomationPage
{
protected $path = 'someURL';
protected $elements = array(
'referenceCampaign' => array('xpath' => "//*[contains(#id,'***HERE I NEED TO GET THE
$campaign value"),
);
public function editExistingCampaign ($campaign)
{
$this->getElement('referenceCampaign')->click();
}
}
I have tried my best to simplify things and I can explain further if any of this isnt clear - hopefully its just a simple PHP question and not really Behat specific
Thanks Ian
Your example is a much better way of doing things, I have only recently started using partial contains and it expands the flexibility of finding stubborn xpaths especially if you combine more than one, like the working example below:
public function editExistingCampaign ($campaign)
{
$this->getSession()->wait(5000);
$element = $this->find('xpath', '//*[contains(#id,"'.$campaign.'")]
[contains(#id,"actionbuttons")]');
if (isset($element)) {
$element->click();
} else {
throw new Exception('Element not found');
}
}
The only slight change was to add a ] at the end of the x-path
I'm sure it's a simple question, but I think that I am missing a point. If all you want is to get hold of the value that was used on the page then you need to review your code structure. First, you cannot pass method argument to the property definition in another class, but you can find the element inside editExistingCampaign.
class ReferenceCampaign extends AutomationPage
{
protected $path = 'someURL';
public function editExistingCampaign ($campaign)
{
$element = $this->find('xpath', '//*[contains(#id, "' . $campaign . '")]');
if (isset($element)) {
$element->click();
} else {
throw new Exception('Element not found');
}
}
}
I'm assuming you are using Symfony Page Object extension, which you should mention. I'm not sure if I've got the syntax right, but the idea is to find your element inside the method.

$this->forward looses the user's requested route?

I want to redirect admins to /admin and members to /member when users are identified but get to the home page (/).
The controller looks like this :
public function indexAction()
{
if ($this->get('security.context')->isGranted('ROLE_ADMIN'))
{
return new RedirectResponse($this->generateUrl('app_admin_homepage'));
}
else if ($this->get('security.context')->isGranted('ROLE_USER'))
{
return new RedirectResponse($this->generateUrl('app_member_homepage'));
}
return $this->forward('AppHomeBundle:Default:home');
}
If my users are logged in, it works well, no problem. But if they are not, my i18n switch makes me get a nice exception :
The merge filter only works with arrays or hashes in
"AppHomeBundle:Default:home.html.twig".
Line that crashes :
{{ path(app.request.get('_route'), app.request.get('_route_params')|merge({'_locale': 'fr'})) }}
If I look at the app.request.get('_route_params'), it is empty, as well as app.request.get('_route').
Of course, I can solve my problem by replacing return $this->forward('AppHomeBundle:Default:home'); by return $this->homeAction();, but I don't get the point.
Are the internal requests overwritting the user request?
Note: I'm using Symfony version 2.2.1 - app/dev/debug
Edit
Looking at the Symfony's source code, when using forward, a subrequest is created and we are not in the same scope anymore.
/**
* Forwards the request to another controller.
*
* #param string $controller The controller name (a string like BlogBundle:Post:index)
* #param array $path An array of path parameters
* #param array $query An array of query parameters
*
* #return Response A Response instance
*/
public function forward($controller, array $path = array(), array $query = array())
{
$path['_controller'] = $controller;
$subRequest = $this->container->get('request')->duplicate($query, null, $path);
return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
By looking at the Symfony2's scopes documentation, they tell about why request is a scope itself and how to deal with it. But they don't tell about why sub-requests are created when forwarding.
Some more googling put me on the event listeners, where I learnt that the subrequests can be handled (details). Ok, for the sub-request type, but this still does not explain why user request is just removed.
My question becomes :
Why user request is removed and not copied when forwarding?
So, controller actions are separated part of logic. This functions doesn't know anything about each other. My answer is - single action handle kind of specific request (e.g. with specific uri prarams).
From SF2 docs (http://symfony.com/doc/current/book/controller.html#requests-controller-response-lifecycle):
2 The Router reads information from the request (e.g. the URI), finds a
route that matches that information, and reads the _controller
parameter from the route;
3 The controller from the matched route is
executed and the code inside the controller creates and returns a
Response object;
If your request is for path / and you wanna inside action (lets say indexAction()) handling this route, execute another controller action (e.g. fancyAction()) you should prepare fancyAction() for that. I mean about using (e.g.):
public function fancyAction($name, $color)
{
// ... create and return a Response object
}
instead:
public function fancyAction()
{
$name = $this->getRequest()->get('name');
$color = $this->getRequest()->get('color');
// ... create and return a Response object
}
Example from sf2 dosc:
public function indexAction($name)
{
$response = $this->forward('AcmeHelloBundle:Hello:fancy', array(
'name' => $name,
'color' => 'green',
));
// ... further modify the response or return it directly
return $response;
}
Please notice further modify the response.
If you really need request object, you can try:
public function indexAction()
{
// prepare $request for fancyAction
$response = $this->forward('AcmeHelloBundle:Hello:fancy', array('request' => $request));
// ... further modify the response or return it directly
return $response;
}
public function fancyAction(Request $request)
{
// use $request
}

Categories