Understand a concept, is Service Provider the correct use case? - laravel

I have created a service provider that takes data from GITHUB API and stores it in a table in a database. Quite a simple thing but I'm wondering if is this how I'm should be using Service Providers?
The second question is about extending this, in reality I want to add more platforms and API's that do this (I currently have one other platform working) but I currently have it set up as a separate service provider. There's a lot of similarities, only some differences in what the API data returns - I feel like I should be abiding by DRY but think this could get complicated when more and more API's get added (def not KISS).
I really want to confirm this before I spend more time extending this platform so appreciate any advice!
<?php
namespace App\Providers;
use GuzzleHttp\Client;
use App\Projects;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Auth;
class GithubServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* #return void
*/
public function register()
{
//
$this->app->singleton('github_client', function ($app) {
try {
$client = new Client();
$code = $_GET['code'] ?? false;
$res = $client->post('https://github.com/login/oauth/access_token', [
'headers' => [
'Accept' => 'application/json',
],
'query' => [
'client_id' => env('GITHUB_ID'),
'client_secret' => env('GITHUB_SECRET'),
'code'=>$code
]
]
);
} catch(\GuzzleHttp\Exception\RequestException $e) {
$response = $e->getResponse();
throw new \App\Exceptions\CustomException($response->getStatusCode());
}
$username = Auth::user()->username;
$user_id = Auth::user()->id;
if($res->getBody() && !isset(json_decode($res->getBody())->error)):
$access_token = json_decode($res->getBody())->access_token;
$projects = json_decode($client->get('https://api.github.com/user/repos', [
'headers' => [
'Authorization'=> 'token ' . $access_token
]
])->getBody());
$i = 0;
foreach($projects as $project):
$i++;
// dd($project->images);
Projects::updateOrCreate(
[
'platform' => 'github',
'user_id' => $user_id,
'project_id' => $project->id
],
[
'platform' => 'github',
'sorting' => $i,
'user_id' => $user_id,
'title' => $project->name,
'description' => strip_tags($project->description),
'images' => '',
'url' => $project->html_url,
'project_id' => $project->id
]
);
endforeach;
endif;
});
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
}

A service provider adds services to the service (IoC) container.
In your case, it seems you want code that gets data from third-party providers in a consistent manner. So you probably want to define an interface for this, and then have individual classes that implement that interface (i.e. GithubProjectRepository, BitbucketProjectRepository, etc).
You’d probably use a service provider to register configured instances of GitHub, Bitbucket’s, etc SDKs. This would be instantiated and configured from your config files. You can then type-hint these SDKs in your class’s constructors and the container will give you configured instances instead of having to new-up all over your application.

Related

How do I run a phpunit test on Redis pub/sub?

I'm building a messenger system with Redis publishing on the Laravel end and subscribing on a node server. I would like to test what is stored in the redis pub method using PHPUnit, but I have no idea where to start.
Controller
class MessageController extends Controller
{
public function store(Conversation $conversation, Request $request)
{
$user = Auth::user();
$message = Message::create([
'body' => $request->input('message'),
'conversation_id' => $conversation->id,
'sender_id' => $user->id,
'type' => 'user_message'
]);
$redis = Redis::connection();
$data = new MessageResource($message);
$redis->publish('message', $data);
}
}
Current Test
/** #test */
public function a_user_can_send_a_message()
{
$this->actingAs($user = User::factory()->create());
$message = Message::factory()->make(['sender_id' => $user->id]);
$conversation Conversation::factory()->create();
$response = $this->json('POST', '/api/message/'. $conversation->id, ['message' => $message->body])
->assertStatus(201);
$response->assertJsonStructure([
'data' => [
'body',
'sender',
]
]);
}
Essentially what I'm trying to see is if message has been published on Redis. I'm unsure how to do this, and I think you would probably need to clear the message from Redis after, would you not?
Your test should be like this:
public function test_a_user_can_send_a_message()
{
$redisSpy = Redis::spy();
$redisSpy->shouldReceive('connection')->andReturnSelf();
$this->actingAs($user = User::factory()->create());
$message = Message::factory()->make(['sender_id' => $user->id]);
$conversation = Conversation::factory()->create();
$this->postJson("/api/message/{$conversation->id}", ['message' => $message->body]);
$this->assertDatabaseCount('messages', 1);
$redisSpy->shouldHaveReceived('publish')
->with('message', new MessageResource(Message::first()));
}
As you can see, I have added Redis::spy(); this is going to allow is to "spy" what is called from Redis. You can still mock methods, and we have to do so, because you use Redis::connect(); and then $redis->publish(...), so we will return the spy when connect is called, that is why we do shouldReceive('connection')->andReturnSelf().
At the end of the code, we check that $redis->publish was called with parameters 'message' and a resource with the desired message. Both must match for this assertion to pass, else you will see a mock error.

dynamic mail configuration using Laravel

In my Laravel application, I am trying to send mail notification based on the company_id of the logged in user:
I have this:
$mail=DB::table('mail_settings')->first();
$config = array(
'driver' => $mail->driver,
'host' => $mail->host,
'port' => $mail->port,
'from' => array('address' => $mail->from_address, 'name' => $mail->from_name),
'encryption' => $mail->encryption,
'username' => $mail->username,
'password' => $mail->password,
'sendmail' => '/usr/sbin/sendmail -bs',
'pretend' => false
);
Config::set('mail',$config);
Models
class Company extends Model
{
protected $table = 'companies';
protected $fillable = [
'id',
'organization_name'
];
}
class User extends Authenticatable
{
protected $fillable = [
'name',
'company_id',
'email',
];
}
Is there any way to override default mail configuration (in app/config/mail.php) on-the-fly (e.g. configuration is stored in database) before mailer transport is created?
Thanks
Is there any way to recreate laravel swiftmailer transport so it can pick up updated config values?
The Mailer class is created in the Illuminate\Mail\MailManager class's resolve() method. If you want to dynamically create a mailer, you need to adapt this function in your Controller to use your $config array and return a Mailer from which you could chain the usual methods.
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Mailer [{$name}] is not defined.");
}
// Once we have created the mailer instance we will set a container instance
// on the mailer. This allows us to resolve mailer classes via containers
// for maximum testability on said classes instead of passing Closures.
$mailer = new Mailer(
$name,
$this->app['view'],
$this->createSwiftMailer($config),
$this->app['events']
);
if ($this->app->bound('queue')) {
$mailer->setQueue($this->app['queue']);
}
// Next we will set all of the global addresses on this mailer, which allows
// for easy unification of all "from" addresses as well as easy debugging
// of sent messages since these will be sent to a single email address.
foreach (['from', 'reply_to', 'to', 'return_path'] as $type) {
$this->setGlobalAddress($mailer, $config, $type);
}
return $mailer;
}

Laravel Error: Object of class Torann\GeoIP\Location could not be converted to string

I am getting error on send Location Data To Database Using Laravel GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31'),
Error:
Object of class Torann\GeoIP\Location could not be converted to string
This Is My Auth LoginController. How to Insert GeoIP Loacation data into database. Please Help me
If i remove this code 'current_location' => GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31'), i am no longer getting this error, every data inserted into database but i add this code i am getting this error
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Jenssegers\Agent\Agent;
use Carbon\Carbon;
use App\User;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Closure;
use GeoIP;
use Location;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
function authenticated(Request $request, $user)
{
// Chrome, IE, Safari, Firefox, ...
$agent = new Agent();
$browser = $agent->browser();
// Ubuntu, Windows, OS X, ...
$platform = $agent->platform();
$user->update([
'last_signin' => Carbon::now()->toDateTimeString(),
'ip_address' => $request->getClientIp(),
'browser_login' => $agent->browser(),
'browser_version' => $agent->version($browser),
'device_login' => $agent->platform(),
'device_version' => $agent->version($platform),
'current_location' => GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31'),
'language' => $agent->languages(),
'root' => $agent->robot(),
'https' => $request->server('HTTP_USER_AGENT'),
]);
}
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest',['except'=>['logout', 'userLogout', 'profile']]);
}
public function userLogout()
{
Auth::guard('web')->logout();
return redirect('/');
}
}
Auth Route :
//User Auth Route Function
Auth::routes();
This is happending because GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31') returns an instance of Torann\GeoIP\Location and you are trying to save it as a String.
Checking the documentation of this object it has this shape:
\Torann\GeoIP\Location {
#attributes:array [
'ip' => '232.223.11.11',
'iso_code' => 'US',
'country' => 'United States',
'city' => 'New Haven',
'state' => 'CT',
'state_name' => 'Connecticut',
'postal_code' => '06510',
'lat' => 41.28,
'lon' => -72.88,
'timezone' => 'America/New_York',
'continent' => 'NA',
'currency' => 'USD',
'default' => false,
]
}
You have to choose a way to represent this location as a String, a possible way can be to save the latitude and the longitude separately.
If you need to use only one column at the DB, you can check some GeoHashing implementations skthon/geogash.
You Might be trying to use getLocation method from wrong instance.
1.) Try as below way :
"use Torann\GeoIP\GeoIPFacade as GeoIP"
$location = GeoIP::getLocation();
2.) Or try as Geoip package documentation suggest here (http://lyften.com/projects/laravel-geoip/doc/methods.html)
from this instance \Torann\GeoIP\GeoIP and then use geoip()->getLocation('27.974.399.65');
This seems to be an issue in the current_location field and how it is typed in your database. From what I read, I guess your field is defined a string, and when trying to save your record to the database, it fails since the data you're trying to save is an Location object.
I would recommend changing your current_location column in your database to make it a json type.
Then you'd be able to insert your data as:
$user->update([
'last_signin' => Carbon::now()->toDateTimeString(),
'ip_address' => $request->getClientIp(),
'browser_login' => $agent->browser(),
'browser_version' => $agent->version($browser),
'device_login' => $agent->platform(),
'device_version' => $agent->version($platform),
'current_location' => json_encode(GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31')->toArray()),
'language' => $agent->languages(),
'root' => $agent->robot(),
'https' => $request->server('HTTP_USER_AGENT'),
]);

laravel custom validator messages translation from another location

How can I load translation from other location than resources/lang/*/validation.php to vendor/package/src/translation/*/validation.php?
I have created translation file on path vendor/package/src/translation/*/validation.php:
<?php
return [
'custom' => [
'search_text' => [
'string' => 'A nice message.',
'not_regex' => 'Regex failed.',
],
],
'attributes' => [
'search_text' => 'Search text',
],
];
I have booted my own Validator in service provider:
$this->app->validator->resolver( function( $translator, $data, $rules, $messages = array(), $customAttributes = array() ) {
return new MyValidator( $translator, $data, $rules, $messages, $customAttributes );
} );
and I have created ofc the validator class. But I have no idea how i can concate validator and translation from custom location to work. The output should be overloaded by the custom file if any intersection will appear in both files.
Thanks for help. :)
If your package contains translation files, you may use the loadTranslationsFrom method to inform Laravel how to load them, and should add the following to your service provider's boot method:
public function boot()
{
$this->loadTranslationsFrom(__DIR__.'/path/to/translations', 'name');
}
Package translations are referenced using the package::file.line syntax convention.
echo trans('name::file.line');

iDeal not listed in omnipay-rabobank library - [Laravel Framework]

I'm currently using the Omnipay extension library to simply handle my Rabobank omnikassa transactions. Now when i use the code below i get a selection of all credit card methods but IDEAL and MINITIX are not listed on the page. Not sure what i'm doing wrong, first time i'm using an external library to handle my payments. Rabobank Omnikassa should display all available payment methods on default.
The Library:
https://github.com/thephpleague/omnipay-rabobank
My Code: iDealController
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use Omnipay\Omnipay;
class iDealController extends Controller
{
public function loadPage()
{
$sOrderId = 'WEB' . time(); // This should be unique - Order id
$sTransactionReference = $sOrderId . date('His'); // This should be unique - Identifier of transaction
$amount = 10.00;
$gateway = Omnipay::create('Rabobank');
$request = $gateway->purchase(array(
'testMode' => true,
'merchantId' => '002020000000001',
'keyVersion' => '1',
'secretKey' => '002020000000001_KEY1',
'amount' => $amount,
'returnUrl' => 'http://localhost:8888/',
'automaticResponseUrl' => 'http://localhost:8888/',
'currency' => 'EUR',
'transactionReference' => $sTransactionReference,
'orderId' => $sOrderId,
'customerLanguage' => "EN"
)
);
$data = $request->getData();
$response = $request->sendData($data);
if ($response->isSuccessful()) {
// payment was successful: update database
print_r($response);
} elseif ($response->isRedirect()) {
// redirect to offsite payment gateway
$response->redirect();
} else {
// payment failed: display message to customer
echo $response->getMessage();
}
return view('omnikassa');
}
}
When i add: 'PaymentMethod' => 'IDEAL' to the request array it gives the following error: Technical problem : code=03 message=None of the merchant's payment means is compliant with the transaction context. So definitely something is going wrong.

Resources