Method refactoring? - php

In the TokenRepository you can see 3 similar methods. It create new entry to the tokens table but each method has different fields.
How can I refactor this? Should I merge 3 methods into 1 method or should I use strategy pattern?
TokenRepository Class:
class TokenRepository
{
public function createTokenDigitalOcean(User $user, $name, $accessToken, $refreshToken = null)
{
return $user->tokens()->create([
'name' => $name,
'provider' => 'digital_ocean',
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
]);
}
public function createTokenLinode(User $user, $name, $key)
{
return $user->tokens()->create([
'name' => $name,
'provider' => 'linode',
'linode_key' => $key,
]);
}
public function createTokenAws(User $user, $name, $key, $secret)
{
return $user->tokens()->create([
'name' => $name,
'provider' => 'aws',
'aws_key' => $key,
'aws_secret' => $secret,
]);
}
}
I have 3 classes like DigitalOceanProvider, LinodeProvider and AwsProvider. For example of using LinodeProvider and AwsProvider class.
class LinodeProvider
{
public function callback()
{
$this->tokenRepo->createTokenLinode($user, $name, $key);
}
}
class AwsProvider
{
public function callback()
{
$this->tokenRepo->createTokenAws($user, $name, $key, $secret);
}
}

This may be a bit overkill, but in order to make life a bit easier in the future, you could create separate implementations of each that extend an abstract class. This way you can unify and define the interface and easily add new token types.
<?php namespace Foo\Tokens;
abstract class Token
{
protected $name = '';
protected $key = '';
protected $provider = '';
public function __construct($name, $key)
{
$this->name = $name;
$this->key = $key;
}
public function data()
{
return [
'name' => $this->name,
'provider' => $this->provider,
'token' => $this->key
];
}
}
Next, we create our Digital Ocean token class. This class can either use the default implementation or redefine it.
<?php namespace Foo\Tokens;
use Foo\Tokens\Token;
class DigitalOceanToken extends Token
{
protected $provider = 'digital_ocean';
public function __construct($name, $key, $refreshToken = null)
{
parent::__construct($name, $key);
$this->refreshToken = $refreshToken;
}
public function data()
{
return [
'name' => $this->name,
'provider' => $this->provider,
'key' => $this->key,
'refreshToken' => $this->refreshToken
];
}
}
The TokenRepository now merely cares about attaching a given token to a user.
<?php namespace Foo;
use User;
use Foo\Tokens\Token;
class TokenRepository
{
public function createToken(User $user, Token $token)
{
return $user->tokens()->create(
$token->data()
);
}
}
And your service providers are as simple as...
<?php
use Foo\Tokens\AwsToken;
class AwsProvider
{
public function callback()
{
$this->tokenRepo->createToken(
$user, new AwsToken($name, $key, $secret)
);
}
}
This isn't working code, as I've not attempted to run it however it's just another idea of how you can organize and assign responsibility. Hope it helps, and welcome feedback from others.

According to me you should implement it like this:
class TokenRepository
{
public function createTokenForVendor(User $user, $inputs)
{
return $user->tokens()->create($inputs);
}
}
and inside your callback:
class VendorProvider
{
public function callback()
{
switch($tokenType) {
case 'DigitalOcean':
$inputs = [
'name' => $name,
'provider' => 'digital_ocean',
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
];
break;
case 'Linode':
$inputs = [
'name' => $name,
'provider' => 'linode',
'linode_key' => $key,
];
break;
case 'Aws':
$inputs = [
'name' => $name,
'provider' => 'aws',
'aws_key' => $key,
'aws_secret' => $secret,
];
break;
}
$this->tokenRepo->createTokenForVendor($user, $inputs);
}
}
Hoping you should do some code structure revamp.
Hope this helps!

Related

I have a problem on API Resources Laravel (Maximum stack depth exceeded)

i have a problem when i use API Resources inside another API Resources class like this:
if (! Route::is('job.*')) {
$data['sites']= SiteResource::collection($this->sites);
$data['jobs'] = JobResource::collection($this->jobs);
}
but when I remove the class the problem disappears like this :
if (! Route::is('job.*')) {
$data['sites']= $this->sites;
$data['jobs'] = $this->jobs;
}
this is -> image for error
this is my code :
class CustomerResource extends JsonResource
{
public function toArray($request)
{
$data = [
'id' => $this->id,
'name' => $this->name,
'billing_details' => $this->billing_details,
'billing_info' => [
'address' => $this->billing->address,
'street_num' =>$this->billing->street_num,
'country' =>$this->billing->country->name,
'city' =>$this->billing->city,
'postal_code' =>$this->billing->postal_code,
'credit_limit' =>$this->billing->credit_limit,
'payment_term_id' =>$this->billing->payment_term_id,
'send_statement' =>$this->billing->send_statement
],
'contacts' => $this->contacts,
'sitecontact' => $this->sitecontact,
];
if (! Route::is('job.*')) {
$data['sites']= SiteResource::collection($this->sites);
$data['jobs'] = JobResource::collection($this->jobs);
}
return $data;
}
}
I called CustomerRessource class on JobRessource class which leads to an infinite loop between them
JobRessource class
if (! Route::is('job.*')) {
$data['sites']= SiteResource::collection($this->sites);
$data['jobs'] = JobResource::collection($this->jobs);
}
I fixed it by using this condition on JobRessource
if (Route::is('job.*')) {
$data['customer' ] = new CustomerResource($this->customer);
}
JobRessource with condition
#N69S thank you for your comment

Laravel test class property not persisting

I'm trying to set a sensorId property that will be used by every method in my feature test. The problem is that it is not persisting between tests.
Here is my test class:
class LogApiPostTest extends TestCase
{
public $sensorId;
public function setUp(): void
{
parent::setUp();
$this->sensorId = 'SEN' . rand(10000000, 999999999);
}
public function test_creates_sensor()
{
$response = $this->post('/api/logs', $this->data([
//
]));
$response->assertStatus(200);
$response->assertSeeText('Sensor created');
}
public function test_updates_sensor()
{
$response = $this->post('/api/logs', $this->data([
//
]));
$response->assertStatus(200);
$response->assertSeeText('Sensor updated');
}
public function test_creates_car()
{
$response = $this->post('/api/logs', $this->data([
'CarID' => 'B',
]));
$response->assertStatus(200);
$response->assertSeeText('Car created');
}
public function test_updates_car()
{
$response = $this->post('/api/logs', $this->data([
'CarID' => 'B',
]));
$response->assertStatus(200);
$response->assertSeeText('Car updated');
}
public function test_is_unauthorized()
{
$response = $this->post('/api/logs', $this->data([
'token_id' => 'thisShouldntWork',
]));
$response->assertUnauthorized();
}
public function data($merge = [])
{
return array_merge([
'token_id' => config('api.token'),
'sensorID' => $this->sensorId,
'CarID' => 'A',
'LogType' => 'CarLog',
'Time' => time(),
'DrctnFlr' => '-02',
'MCSS' => 'STB',
'OpMode' => 'INA',
'DoorStats' => '][**',
'DoorCmd' => 'C6:C1>',
'OCSS' => 'GTN02',
'Load' => '002%',
], $merge);
}
}
I just want the sensorId property to persist for all tests in this class.
When there is a dependency between tests, you must use the #depends annotation.
This allows to ensure the execution order of tests is correct and also to pass values between tests. Instead of making sensorId to persist, just pass it from test to test.
https://phpunit.readthedocs.io/en/9.5/writing-tests-for-phpunit.html#test-dependencies

What is the best design pattern to consume REST API

I want to consume a Rest API in Laravel (an MVC framework) but I resort to use __call and was wonder if there is a better design pattern for this.
I know this a bad choice and I'm looking for an alternative pattern but here is my Repository class:
namespace App\Repositories;
use App\Models\OnlinePayment;
use App\Models\Order;
use App\Models\Transaction;
use App\Models\User;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use SoapClient;
class Bank
{
protected $http;
protected $user;
public function __construct()
{
$this->http = new Client;
}
protected function index()
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/report';
$data = [
'user_gender' => $this->user->gender ?? 1,
'user_name' => $this->user->name,
'user_family' => $this->user->family ?? 'خالی',
'user_mobile' => $this->user->mobile,
'user_type' => $this->user->type->name,
];
$options = $this->options($data);
$res = $this->http->request('GET', $url, $options);
$response = json_decode($res->getBody(), true);
return $response;
}
protected function indexData($request)
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers';
$options = $this->options($request->all());
$res = $this->http->request('GET', $url, $options);
$response = response()->json(json_decode($res->getBody(), true), $res->getStatusCode());
return $response;
}
protected function show($national_id)
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers/' . $national_id;
$options = $this->options([]);
$res = $this->http->request('GET', $url, $options);
if ($res->getStatusCode() == 404) {
abort(404);
}
$response = json_decode($res->getBody(), true);
return $response;
}
protected function store($request)
{
$http = new Client;
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers';
$this->user = auth()->user();
$data = array_merge(
[
'customer_national_id' => $request->national_id,
'customer_gender' => $request->gender,
'customer_name' => $request->name,
'customer_family' => $request->family,
'customer_phone' => $request->phone,
'customer_mobile' => $request->mobile,
'customer_city_id' => $request->city_id,
], [
'user_name' => $this->user->nanfig() is a hidden dependency. The settings should also be passed via the construcme,
'user_family' => $this->user->family ?? 'خالی',
'user_mobile' => $this->user->mobile,
'user_type' => $this->user->type->name,
'user_gender' => $this->user->gender ?? 1,
]
);
$res = $http->request('POST', $url, [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . config('Bank.token'),
],
'json' => $data,
'http_errors' => false
]);
if (! in_array($res->getStatusCode(), [200, 422])) {
$error = ValidationException::withMessages([
'name' => 'خطای ' . $res->getStatusCode() . ' در تعویض کالا'
]);
throw $error;
}
$response = response()->json(json_decode($res->getBody(), true), $res->getStatusCode());
return $response;
}
protected function options($data)
{
$options = [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . config('Bank.token'),
],
'json' => $data,
'http_errors' => false
];
return $options;
}
public function __call($method, $arguments) {
if (method_exists($this, $method)) {
if (! isset($arguments[0]) || ! $arguments[0] instanceof User) {
$this->user = auth()->user();
} else {
$this->user = $arguments[0];
unset($arguments[0]);
}
return call_user_func_array(array($this, $method), $arguments);
}
}
}
then create an instance of it in controller constructor:
public function __construct()
{
$this->Bank = new Bank();
}
and use it in controller like this:
$response = $this->Bank->indexData($user, $request);
or this:
$response = $this->Bank->indexData($request);
I think the shown class is not a Repository class because a Repository is only responsible for reading and writing the date from a data source. Your class does too much and violates all basic MVC principles.
Some thinks I would fix:
A repository is not responsible for creating the response view data (like JSON)
A repository is not responsible for creating a response object
A repository is independent of the request/response
The method name index makes no sense because a repository is not a Controller action. Don't mix the model layer with the controller layer.
config() is a hidden dependency. The settings should also be passed via the constructor.
Instead use better separation:
Create a class BankApiClient
Dont use magic methods like __call
Instead use public methods like: getUserByNationalId(int $nationalId): UserData
and so on...
Let the controller action create / render the json response with the results of the BankApiClient.
__call is an magic method of php which allow to execute protected method outside of the object instance, this is a rupture of the class visibility.
If you want to call a method from outside it must be public
public function __construct()
{
$this->bank = new Bank()
}
Use Auto injection of the dependency
public function __construct(Bank $bank)
{
$this->bank = $bank;
}

Laravel validation method not allowed

I'm trying to set validation on my controller method, but on validation failure I'm getting error that method is not allowed http exception.
My controller:
namespace App\Http\Controllers\Web;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Services\InvoicesService;
class InvoiceController extends Controller
{
private $invoice;
public function __construct(InvoicesService $invoice) {
$this->invoice = $invoice;
}
public function startNewInvoice($id, $customer)
{
$ticket = $this->invoice->getTicketByInvoice($id);
$ticket = $ticket->Ticket;
return view('form', ['InvoiceId' => $id,'CustomerInfo' => $customer, 'records' => null, 'recordState' => null, 'ticket' => $ticket, 'invoices' => null]);
}
public function generateInvoice(Request $request)
{
//dd($request);
$this->validate($request, [
'CustomerNumber' => 'required|numeric'
]);
$invoiceId = $request->input('Invoice');
$customer = array('CustomerCode' => $request->input('CustomerNumber'),'CustomerName' => $request->input('CustomerName'),'CustomerAddress' => $request->input('CustomerAddress'),
'CustomerVATCode' => $request->input('CustomerVatNumber'));
$hash = $this->invoice->generateInvoice($invoiceId, $customer);
$newInvoice = $this->invoice->newInvoice($request->input('CustomerNumber'), $hash->Id);
return $this->startNewInvoice($newInvoice->Id, $customer);
}
}
Any help would be really appreciated

Laravel Fractal transformer, how to pass and get extra variable

I'm using Dingo API to create an API in Laravel 5.2 and have a controller returning data with
return $this->response->paginator($rows, new SymptomTransformer, ['user_id' => $user_id]);
However, I don't know how to retrieve user_id value in the SymptomTransformer! Tried many different ways and tried looking into the class but I'm relatively new to both Laravel and OOP so if anyone can point me to the right direction, it'd be greatly appreciated.
Below is my transformer class.
class SymptomTransformer extends TransformerAbstract
{
public function transform(Symptom $row)
{
// need to get user_id here
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
}
You can pass extra parameter to transformer constructor.
class SymptomTransformer extends TransformerAbstract
{
protected $extra;
public function __construct($extra) {
$this->extra = $exta;
}
public function transform(Symptom $row)
{
// need to get user_id here
dd($this->extra);
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
}
And call like
return $this->response->paginator($rows, new SymptomTransformer(['user_id' => $user_id]));
You can set extra param via setter.
class SymptomTransformer extends TransformerAbstract
{
public function transform(Symptom $row)
{
// need to get user_id here
dd($this->test_param);
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
public function setTestParam($test_param)
{
$this->test_param = $test_param;
}
}
And then:
$symptomTransformer = new SymptomTransformer;
$symptomTransformer->setTestParam('something');
return $this->response->paginator($rows, $symptomTransformer);
If you are using Dependency Injection, then you need to pass params afterwards.
This is my strategy:
<?php
namespace App\Traits;
trait TransformerParams {
private $params;
public function addParam() {
$args = func_get_args();
if(is_array($args[0]))
{
$this->params = $args[0];
} else {
$this->params[$args[0]] = $args[1];
}
}
}
Then you implement the trait in your transformer:
<?php
namespace App\Transformers;
use App\Traits\TransformerParams;
use App\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
use TransformerParams;
public function transform(User $user)
{
return array_merge([
'id' => (int) $user->id,
'username' => $user->username,
'email' => $user->email,
'role' => $user->roles[0],
'image' => $user->image
], $this->params); // in real world, you'd not be using array_merge
}
}
So, in your Controller, just do this:
public function index(Request $request, UserTransformer $transformer)
{
$transformer->addParam('has_extra_param', ':D');
// ... rest of the code
}
Basically, the trait is a bag for extra params.

Categories