Laravel 5 , understand the services and containers - laravel-5

I would like to apply the best laravel practices regarding the services + containers.
Usually when I need to use a method anywhere in my app, I do that :
class PersonneController extends Controller {
private $personne_service;
private $role_service;
public function __construct() {
$this->personne_service = new PersonneService();
$this->role_service = new RoleService();
}
/**
* Renvoi la liste des personnes d'une école de la personne connectée
* #return
*/
public function getListePersonnes(Request $request, $id_ecole = null)
{
if ($id_ecole == null){
$id_ecole = Auth::user()->id_ecole;
}
$liste_personnes = $this->personne_service->getListePersonnesMultiRecherches($request->all());
See the last line to use a method in an other class. It works fine and I have understood the mechanic !
Now I would like to do the same thing using services and containers. Here what I do :
step 1 : create a service provider
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\PersonneService;
class PersonneServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind('PersonneService', function ($app) {
return new App\Services\PersonneService();
});
}
}
step 2 : declare this container into my app/config file :
'providers' => [
// ....
App\Providers\PersonneServiceProvider::class,
],
step 3 : i use this method anywhere in my app. For example in a controller :
(see the commented lines vs the previous code)
class PersonneController extends Controller {
// private $personne_service;
private $role_service;
public function __construct() {
// $this->personne_service = new PersonneService();
$this->role_service = new RoleService();
}
/**
* Renvoi la liste des personnes d'une école de la personne connectée
* #return
*/
public function getListePersonnes(Request $request, $id_ecole = null)
{
if ($id_ecole == null){
$id_ecole = Auth::user()->id_ecole;
}
// $liste_personnes = $this->personne_service->getListePersonnesMultiRecherches($request->all());
$personne_service = app()->make('PersonneService');
$liste_personnes = $personne_service->getListePersonnesMultiRecherches($request->all());
It does not work. I have this error :
Class PersonneService does not exist
in Container.php (line 729)
My questions :
what are the problems ? (I read the official doc + a lot of tutorials on this topic)
is it really a good practice to do that ? or do you think that my first technich is well too ?
Thanks for your feedbacks. Merci
Dominique

Related

I got constructor error installing flysystem-google-drive to laralel 8

I want to add https://github.com/nao-pon/flysystem-google-drive into Laravel 8 app, but I got error :
Hypweb\Flysystem\GoogleDrive\GoogleDriveAdapter::__construct(): Argument #1 ($service) must be of type Google_Service_Drive, Illuminate\Foundation\Application given, called in /ProjectPath/vendor/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php on line 208
In file config/app.php I added 2 lines in “Providers” :
\Hypweb\Flysystem\GoogleDrive\GoogleDriveAdapter::class,
App\Providers\GoogleDriveServiceProvider::class,
and added file app/Providers/GoogleDriveServiceProvider.php with lines:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class GoogleDriveServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
\Storage::extend('google', function($app, $config) {
$client = new \Google_Client();
$client->setClientId($config['clientId']);
$client->setClientSecret($config['clientSecret']);
$client->refreshToken($config['refreshToken']);
$service = new \Google_Service_Drive($client);
$options = [];
if(isset($config['teamDriveId'])) {
$options['teamDriveId'] = $config['teamDriveId'];
}
// I SET FULL PATH TO DRIVER
$adapter = new \Hypweb\Flysystem\GoogleDrive\GoogleDriveAdapter($service, $config['folderId'], $options);
return new \League\Flysystem\Filesystem($adapter);
});
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
}
I added 'google' block into of config/filesystems.php
and added all parameters in .env file
Why I got this error and how it can be fixed ?
Thanks in advance!

Laravel 8 Fortify - 2FA only when the user logs in from a new device

I am implementing two-factor authentication (2FA) in my Laravel 8 application.
The 2FA is applied every time the user logs in. However, I don't really feel that 2FA is necessary every time, I even find it annoying. As a solution I am thinking of applying it only when the user connects from a new device. Is there someone who has already done it or who can give me a hint of the changes that would be necessary?
I have got it. Here are the steps I have followed:
In the config file fortify.php I have added
'pipelines' => [
'login' => [
App\Actions\Fortify\RedirectIfTwoFactorAuthenticatable::class,
Laravel\Fortify\Actions\AttemptToAuthenticate::class,
Laravel\Fortify\Actions\PrepareAuthenticatedSession::class,
]
]
I have added the field two_factor_cookies to the User class.
I have customized the RedirectIfTwoFactorAuthenticatable class of
Fortify:
<?php
namespace App\Actions\Fortify;
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable as DefaultRedirectIfTwoFactorAuthenticatable;
use Laravel\Fortify\TwoFactorAuthenticatable;
class RedirectIfTwoFactorAuthenticatable extends DefaultRedirectIfTwoFactorAuthenticatable
{
/**
* Handle the incoming request.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return mixed
*/
public function handle($request, $next)
{
$user = $this->validateCredentials($request);
if (optional($user)->two_factor_secret &&
in_array(TwoFactorAuthenticatable::class, class_uses_recursive($user)) &&
$this->checkIfUserDeviceHasNotCookie($user)) {
return $this->twoFactorChallengeResponse($request, $user);
}
return $next($request);
}
/**
* This checks if the user's device has the cookie stored
* in the database.
*
* #param \App\Models\User\User $user
* #return bool
*/
protected function checkIfUserDeviceHasNotCookie($user)
{
$two_factor_cookies = json_decode($user->two_factor_cookies);
if (!is_array($two_factor_cookies)){
$two_factor_cookies = [];
}
$two_factor_cookie = \Cookie::get('2fa');
return !in_array($two_factor_cookie,$two_factor_cookies);
}
}
In the FortifyServiceProvider I have added a customized TwoFactorLoginResponse.
<?php
namespace App\Providers;
use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use App\Http\Responses\FailedPasswordResetLinkRequestResponse;
use App\Http\Responses\FailedPasswordResetResponse;
use App\Http\Responses\LockoutResponse;
use App\Http\Responses\LoginResponse;
use App\Http\Responses\LogoutResponse;
use App\Http\Responses\PasswordResetResponse;
use App\Http\Responses\RegisterResponse;
use App\Http\Responses\SuccessfulPasswordResetLinkRequestResponse;
use App\Http\Responses\TwoFactorLoginResponse;
use App\Http\Responses\VerifyEmail;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse as FailedPasswordResetLinkRequestResponseContract;
use Laravel\Fortify\Contracts\FailedPasswordResetResponse as FailedPasswordResetResponseContract;
use Laravel\Fortify\Contracts\LockoutResponse as LockoutResponseContract;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
use Laravel\Fortify\Contracts\LogoutResponse as LogoutResponseContract;
use Laravel\Fortify\Contracts\PasswordResetResponse as PasswordResetResponseContract;
use Laravel\Fortify\Contracts\RegisterResponse as RegisterResponseContract;
use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse as SuccessfulPasswordResetLinkRequestResponseContract;
use Laravel\Fortify\Contracts\TwoFactorLoginResponse as TwoFactorLoginResponseContract;
use Laravel\Fortify\Fortify;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->registerResponseBindings();
}
/**
* Register the response bindings.
*
* #return void
*/
protected function registerResponseBindings()
{
$this->app->singleton(LoginResponseContract::class, LoginResponse::class);
$this->app->singleton(LogoutResponseContract::class, LogoutResponse::class);
$this->app->singleton(TwoFactorLoginResponseContract::class, TwoFactorLoginResponse::class);
$this->app->singleton(RegisterResponseContract::class, RegisterResponse::class);
$this->app->singleton(LockoutResponseContract::class, LockoutResponse::class);
$this->app->singleton(SuccessfulPasswordResetLinkRequestResponseContract::class, SuccessfulPasswordResetLinkRequestResponse::class);
$this->app->singleton(FailedPasswordResetLinkRequestResponseContract::class, FailedPasswordResetLinkRequestResponse::class);
$this->app->singleton(PasswordResetResponseContract::class, PasswordResetResponse::class);
$this->app->singleton(FailedPasswordResetResponseContract::class, FailedPasswordResetResponse::class);
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Fortify::ignoreRoutes();
Fortify::loginView(function () {
return view('auth.login');
});
Fortify::twoFactorChallengeView('auth.two-factor-challenge');
Fortify::confirmPasswordView(function (Request $request) {
if ($request->ajax()) {
return view('auth.confirm-password-form');
} else {
return view('auth.confirm-password');
}
});
Fortify::requestPasswordResetLinkView(function () {
return view('auth.forgot-password');
});
Fortify::resetPasswordView(function ($request) {
return view('auth.reset-password', ['request' => $request,'token' => $request->route('token')]);
});
Fortify::registerView(function () {
return view('auth.register');
});
Fortify::verifyEmailView(function () {
return view('auth.verify');
});
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
/*RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->email.$request->ip());
});*/
RateLimiter::for('two-factor', function (Request $request) {
return Limit::perMinute(5)->by($request->session()->get('login.id'));
});
}
}
Finally, the TwoFactorLoginResponse:
<?php
namespace App\Http\Responses;
use Illuminate\Http\JsonResponse;
use Laravel\Fortify\Contracts\TwoFactorLoginResponse as TwoFactorLoginResponseContract;
class TwoFactorLoginResponse implements TwoFactorLoginResponseContract
{
/**
* Create an HTTP response that represents the object.
*
* #param \Illuminate\Http\Request $request
* #return \Symfony\Component\HttpFoundation\Response
*/
public function toResponse($request)
{
$user = \Auth::user();
$this->storeCookieIfNotInDB($user);
$role = $user->role;
if ($request->wantsJson()) {
return new JsonResponse('', 204);
}
if ($role == "0") {
return redirect()->route('user.home');
} else {
return redirect()->route('admin.home');
}
}
/**
* Store the cookie if it is not in the database.
*
* #param \App\Models\User\User $user
* #return void
*/
protected function storeCookieIfNotInDB($user)
{
$two_factor_cookies = json_decode($user->two_factor_cookies);
if (!is_array($two_factor_cookies)){
$two_factor_cookies = [];
}
$two_factor_cookie = \Cookie::get('2fa');
if (!in_array($two_factor_cookie,$two_factor_cookies)) {
$two_factor_cookie = md5(now());
$two_factor_cookies[] = $two_factor_cookie;
if (count($two_factor_cookies) > 3) {
array_shift($two_factor_cookies);
}
$user->two_factor_cookies = json_encode($two_factor_cookies);
$user->save();
$lifetime = 60 * 24 * 365; //one year
\Cookie::queue('2fa',$two_factor_cookie,$lifetime);
}
}
}
Upon login, it will look for the cookie 2fa. If its content is stored in the database, it will not be necessary to enter the code again. To prevent unlimited cookie content from being saved in the DB you can add a maximum limit (I have set it 3).
Thanks to Maarten Veerman for the inital help.
According to this line: https://github.com/laravel/fortify/blob/82c99b6999f7e89f402cfd7eb4074e619382b3b7/src/Http/Controllers/AuthenticatedSessionController.php#L80
you can create a pipelines.login entry in your fortify config file.
The solution would be to:
create the config entry
copy the pipeline setup in the above file, line 84.
create a custom AttemptToAuthenticate class, make sure the pipeline config points to your new class.
make the new class extend the default fortify AttemptToAuthenticate class.
overwrite the handle function, add your logic in the new function, where you check for a cookie on the device.

Laravel Components not getting my methods in shared host

I've just migrated a project that was working great on my localhost to a shared hosting and my components suddently are not getting the methods that i gave them and i'm getting errors in my views like so :
Undefined variable: CatPromo
this is my Component :
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use App\Categories;
class promo extends Component
{
/**
* Create a new component instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* #return \Illuminate\View\View|string
*/
public function render()
{
return view('components.promo');
}
public function CatPromo()
{
$Categories = Categories::all();
return $Categories;
}
}
Update : I removed the App\View\Components\promo.php to see if it can help me by throwing an error and it seems that he don't even detect the controller.
The documentation says: You should define the component's required data in its class constructor.
public function __construct($CatPromo)
{
// use as variable
$this->CatPromo = $CatPromo;
}
// use as method
public function CatPromo()
{
$Categories = Categories::all();
return $Categories;
}
And in blade template:
#foreach($CatPromo() as $key => $Categorie)

Laravel 6 Use a custom Hasher during migration from CakePHP 2.x

We are migrating applications from CakePHP 2.X but we need to implement our mobile API's before the migration. I have followed all the items I could find but they all seem to be for v5 or less. No matter what I do Hash::make() still results in a Bcrypt password.
I really want to 2 birds one stone with having this allow sha1() login and update to Bcrypt upon login but we havent implemented on CakePHP 2.x successfully. So I need to get the Hasher working or a workaround. I know I can just Hash manually in the model but that doesnt allow Auth to work.
Any help would be appreciated
app.php config file
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
//Illuminate\Hashing\HashServiceProvider::class,
App\Providers\CustomHashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
CustomHashServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Hashing\HashServiceProvider;
use App\Libs\CustomHash\CustomHasher as CustomHasher;
class CustomHashServiceProvider extends HashServiceProvider
{
public function register()
{
$this->app->singleton('hash', function () {
return new CustomHasher;
});
}
}
CustomHasher.php
<?php
namespace App\Lib\CustomHash;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
class CustomHasher implements HasherContract {
/**
* Hash the given value.
*
* #param string $value
* #return array $options
* #return string
*/
public function make($value, array $options = array()) {
//I have custom encoding / encryption here//
//Define your custom hashing logic here//
return sha1(env('SEC_SALT').$value);
}
/**
* Check the given plain value against a hash.
*
* #param string $value
* #param string $hashedValue
* #param array $options
* #return bool
*/
public function check($value, $hashedValue, array $options = array()) {
return $this->make($value) === $hashedValue;
}
/**
* Check if the given hash has been hashed using the given options.
*
* #param string $hashedValue
* #param array $options
* #return bool
*/
public function needsRehash($hashedValue, array $options = array()) {
return false;
}
public function info($hashedValue): array {
return $hashedValue;
}
}
UPDATE
I refactored based on #Mdexp answer to this .... but I found out the Configs are ignored unless added in app.php on Lumen
New app.php
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\Sha1HashServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
Sha1HashServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class Sha1HashServiceProvider extends ServiceProvider {
public function register() {
//
}
public function boot() {
$this->app->make('hash')->extend('sha1', function () {
// Just create the driver instance of the class you created in the step 1
return new \App\Lib\Sha1Hash\Sha1Hasher;
});
}
}
Sha1Hasher.php
<?php
namespace App\Lib\Sha1Hash;
use Illuminate\Hashing\AbstractHasher;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use RuntimeException;
class Sha1Hasher extends AbstractHasher implements HasherContract {
public function __construct(array $options = []) {
}
public function make($value, array $options = []) {
$hash = sha1(env('SEC_SALT').$value);
if ($hash === false) {
throw new RuntimeException('Sha1 hashing not supported.');
}
return $hash;
}
public function check($value, $hashedValue, array $options = []) {
return ($this->make($value) == $hashedValue)?true:false;
}
public function needsRehash($hashedValue, array $options = array()): bool {
return false;
}
}
I would use the default HashServiceProvider and register a new driver into it. It would also make the switch back from sha1 to bcrypt even quicker once you completed the transitioning phase.
1) You have have to create a class which extends the Illuminate\Hashing\AbstractHasher or at least implements the Illuminate\Contracts\Hashing\Hasher. Take a look at the current Bcrypt driver implementation as a reference on GitHub.
The CustomHasher class you provided should work just fine as a driver, I would just rename it to avoid confusion with naming.
2) Now you can register the hash drivers in a service provider like:
public function boot()
{
$this->app->make('hash')->extend('sha1', function () {
// Just create the driver instance of the class you created in the step 1
return new YourCustomSha1Hasher();
});
}
3) Then in your config/hashing.php file, set the driver to 'sha1' (must be equal to the first parameter of the extend function call.
4) It should work straight out of the box, and to choose a different hashing driver, just change the config/hashing.php configuration file with the driver that you want to use for hashing.
Note: the whole code hasn't been tested, but I looked through the source code to come up with this solution that should work. Just comment anything isn't working as expected so I can fix my answer.

Laravel 5.2 RethinkDB Service Provider Preventing Creation of Migration Files

I followed the installation to install this service provider, but each time I try to create a migration file I get this error:
[ErrorException]
Argument 2 passed to duxet\Rethinkdb\Console\Migrations\MigrateMakeCommand::__construct() must be an instance
of Illuminate\Foundation\Composer, instance of Illuminate\Support\Composer given, called in D:\projects\app\vendor\duxet\laravel-rethinkdb\src\RethinkdbServiceProvider.php on line 41 and defined
I'm on line 41 and can see that the second parameter is $composer, which equals $composer = $app['composer'];. I thought maybe changing Illuminate\Support\Composer to Illuminate\Foundation\ServiceProvider might just fix the issue, but doing this throws another error saying that:
[Symfony\Component\Debug\Exception\FatalErrorException]
Class 'Illuminate\Foundation\ServiceProvider' not found
Anyone running into this issue?
Update
Where the original error is occurring (I marked Line 41):
<?php
namespace duxet\Rethinkdb;
use duxet\Rethinkdb\Console\Migrations\MigrateMakeCommand;
use duxet\Rethinkdb\Eloquent\Model;
use duxet\Rethinkdb\Migrations\MigrationCreator;
use Illuminate\Support\ServiceProvider;
class RethinkdbServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application events.
*
* #return void
*/
public function boot()
{
Model::setConnectionResolver($this->app['db']);
Model::setEventDispatcher($this->app['events']);
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app->resolving('db', function ($db) {
$db->extend('rethinkdb', function ($config) {
return new Connection($config);
});
});
$this->app->singleton('command.rethink-migrate.make', function ($app) {
$creator = new MigrationCreator($app['files']);
$composer = $app['composer'];
return new MigrateMakeCommand($creator, $composer); <= line 41
});
$this->commands('command.rethink-migrate.make');
}
public function provides()
{
return ['command.rethink-migrate.make'];
}
}
The MigrateMakeCommand class:
<?php
namespace duxet\Rethinkdb\Console\Migrations;
use duxet\Rethinkdb\Migrations\MigrationCreator;
use Illuminate\Database\Console\Migrations\MigrateMakeCommand as LaravelMigration;
use Illuminate\Foundation\Composer;
class MigrateMakeCommand extends LaravelMigration
{
/**
* The console command signature.
*
* #var string
*/
protected $signature = 'make:rethink-migration {name : The name of the migration.}
{--create= : The table to be created.}
{--table= : The table to migrate.}
{--path= : The location where the migration file should be created.}';
/**
* Create a new migration install command instance.
*
* #param duxet\Rethinkdb\Migrations\MigrationCreator $creator
* #param \Illuminate\Foundation\Composer $composer
*
* #return void
*/
public function __construct(MigrationCreator $creator, Composer $composer)
{
parent::__construct($creator, $composer);
}
}

Resources