Laravel по-русски

Русское сообщество разработки на PHP-фреймворке Laravel.

Ты не вошёл. Вход тут.

#26 Re: Хорошие практики (FAQ) » Вопросы о вызове сервисов и их взаимодействии с репозиториями/моделями » 01.12.2017 12:11:05

Я ими не пользуюсь, т.к. гораздо удобнее элементы формы (например) грамотно назвать, чтобы не нужно было преоразование данных делать.

Тогда понятно.. Нет меня интересует не столько названия полей итд. Сколько создание объекта с еще какими то дополнительными действиями перед ним. Скажем создали объект через API на удаленном сервисе/платежном шлюзе, а затем в свою БД сохранили. И как бы все это что бы $user->some_child_objects()->createMethod($request->all()); ??

Это же типичная такая задача... Нужно что бы внутри createMethod был доступен $user родительский и данные с которыми создаем ну например из формы пришедшие... Или тут только сервис?

#27 Re: Хорошие практики (FAQ) » Вопросы о вызове сервисов и их взаимодействии с репозиториями/моделями » 01.12.2017 11:56:43

Так часто называют классы, которые переводят данные из одного формата в другой.

А можешь привести тут или написать в своем репозитории пример преобразователя конкретный и именно при создании модели и/или при создании дочерней модели через отношения?

#28 Re: Хорошие практики (FAQ) » Что валидировать в реквесте? » 01.12.2017 11:55:09

А если я хочу закрыть одну форму? Но ведь если закрывать в реквестах то мне вместо одного тогда придется создавать 2 request'а, в show form и save form. Это же не красиво..

#29 Re: Хорошие практики (FAQ) » Что валидировать в реквесте? » 01.12.2017 11:39:44

Алексей, а middleware закрыть скажем все форму к которой в некоторой ситуации нет доступа не верно? Ну скажем и показ формы и отправку закрыть прямо в конструкторе контроллера вызвав middleware ? Скажем если у юзера только одна подписка может быть, а она уже есть. То закрыть например даже админу форму добавки подписки для юзера с этим ID с помощью middleware ?

#30 Re: Хорошие практики (FAQ) » Что валидировать в реквесте? » 01.12.2017 11:24:40

Где это лучше проверять? Как-то пытаться добавить в SomeEditRequest?


Вроде бы в Request есть функция authorize() которая хорошее место что бы проверить то о чем говорите.

Кроме того проверить имеет ли вообще юзер доступ к данной форме можно проверить и в middleware. Тем более что им можно закрывать не один request скажем именно отправки данной формы, а удобно закрывать и несколько роутов или методов контроллера.

Поточнее когда лучше проверять права в Request а когда в Middleware адресую вопросы более опытным участникам форума!

#31 Хорошие практики (FAQ) » Нужна хорошая практика создания Eloquent объектов. » 01.12.2017 10:06:15

htclog81
Ответов: 3

Допустим нужно создать объект в простом случае это просто Subscription::create($arr); или допустим дочерний через отношение $users->subscriptions()->create($arr);

Ну, а что если случай посложнее и при создании объекта еще логика выполняется, как то данные подготавливаются.

Например, через сервис. Это мой код, но я структуру в laravel cashier пакете подглядел

Класс и трейт Пользователя (копирую не полностью там еще много всего)

   class User extends Authenticatable
   {
       use Billable;
trait Billable
{
    /**
    *
    *  @return Collection
    */
    public function subscriptions()
    {
        return $this->hasMany(Subscription::class, $this->getForeignKey())->orderBy('created_at', 'desc');
    }
	
     /**
     * Creating a new subscription.
     *
     * @param  string  $subscription
     * @param  Plan  $plan
     * @return SubscriptionBuilder
     */
    public function newSubscription($plan)
    {
        return new SubscriptionBuilder($this, $plan);
    }
    

Класс SubscriptionBuilder наверное не четко сервис, но скорее можно воспринимать как сервис, тк он включает в себя модель и используется для создание дочерней модели Subscription в зависимости от параметров, кроме того включает в себя функционал создания подписки в платежном шлюзе. Те можно сказать реализует фичу взять юзера, создать ему клиента, карту и подписку через API в платежном шлюзе и затем создать экземпляр дочерней модели Subscription.

Копирую полностью

<?php

namespace App\Classes\Models;

use App\Classes\Services\BraintreeService as GatewayService;
use Exception;
use Carbon\Carbon;
use Braintree_Subscription as GatewaySubscription;
use App\Classes\Models\Plan;
use Illuminate\Support\Facades\Config;

class SubscriptionBuilder 
{
	/**
     * Owner user
     *
     * @var User
     */
    protected $user;

    /**
     *
     * @var Plan
     */
    protected $plan; 
	
	/**
     *
     * @var bool
     */
    protected $addTrial = false;

    /**
     *
     * @var int
     */
    protected $trialDurationUnit;
    /**
     *
     * @var string
     */
    protected $trialDuration;
	

    /**
     * Create a new subscription builder instance.
     *
     * @param  mixed  $user
     * @param  string  $name
     * @param  Plan  $plan
     * @return void
     */
    public function __construct($user, $plan)
    {
        $this->plan = $plan;
        $this->user = $user;
		
    }

	/**
     * 
     *
     * @param  int  $trialDuration
     * @param  int  $trialDurationUnit
     * @return $this
     */
    public function addTrial($trialDuration, $trialDurationUnit)
    {
		$this->trialDuration = $trialDuration;
        $this->trialDurationUnit = $trialDurationUnit;
		$this->addTrial = true;
	
		return $this;
    }

	

    /**
     * Create a new subscription.
     *
     * @param  string|null  $token
     * @param  array  $customerOptions
     * @param  array  $subscriptionOptions
     * @return Subscription
     * @throws \Exception
     */
    public function create($token = null)
    {
		if ($token) {
			if ($this->user->gateway_id) {
				throw new \LogicException('When user have payment method add subscription only with this method');
			} else {
				$this->user->createGatewayCustomerWithPaymentMethod($token);
			}
		} 
		
		if (!$this -> addTrial) {
			$response = GatewaySubscription::create([
				'planId' => $this->plan->gateway_id,
				'price' => (string) round($this->plan->cost, 2),
				'paymentMethodToken' => $this->user->defaultCard->gateway_id,
				'trialPeriod' => false,
			]);
			
		} else {
			
			$response = GatewaySubscription::create([
				'planId' => $this->plan->gateway_id,
				'price' => (string) round($this->plan->cost, 2),
				'paymentMethodToken' => $this->user->defaultCard->gateway_id,
				'trialPeriod' => true,
				'trialDurationUnit' => $this-> trialDurationUnit,
				'trialDuration' => $this-> trialDuration,
			]);
			
		}
		
        if (! $response->success) {
            throw new Exception('Gateway failed to create subscription: '.$response->message);
        }
		
		if ($this->addTrial) {
			if ($this->trialDurationUnit == Subscription::TrialDurationUnitDay) {
				$trialEndsAt = Carbon::today()->addDays($this->trialDuration);
			} else if ($this->trialDurationUnit == Subscription::TrialDurationUnitMonth) {
				$trialEndsAt = Carbon::today()->addMonth($this->trialDuration);
			} else {
				throw new \LogicException('Need trial duration unit.');
			}
		}

		return $this->user->subscriptions()->create([
			'name' => \Config::get('services.subscription.name'),
            'gateway_id'   => $response->subscription->id,
            'plan_id' => $this->plan->id,
            'trial_ends_at' => $this -> addTrial ? $trialEndsAt : null,
			'card_id' => $this->user->defaultCard->id, 
        ]);
    }

}

В контроллере вызывается так:
Хотя могут быть разные понятно контексты и аргументы вызова.

$user->newSubscription($plan)->addTrial(Config::get('services.subscription.trial_duration'), Config::get('services.subscription.trial_duration_unit'))->create($request->payment_method_nonce);

Собственно все работает и даже особенно дублирования кода я не вижу.

Но теме не менее хочется на этом примере разобраться какие еще паттерны тут можно применить и что можно зарефакторить.

Например, возникает мысль, а можно ли отдать логику создания подписки в шлюзе и одновременно самой модели, собственно классу Subscription ? Причем так что бы вызывать этот метод создания через отношения

Например

$user->subscriptions->createWithGateway($plan, $trialPeriod, $trialDuration); 

Внутри метода будет наверное что то вроде того

class Subscription
{
function createWithGateway($plan, $trialPeriod, $trialDuration)
               if (!$trialDuration || !$trialDurationUnit) {
			$response = GatewaySubscription::create([
				'planId' => $plan->gateway_id,
				'price' => (string) round($plan->cost, 2),
				'paymentMethodToken' => $this->user->defaultCard->gateway_id,
				'trialPeriod' => false,
			]);
			
		} else {
			
			$response = GatewaySubscription::create([
				'planId' => $plan->gateway_id,
				'price' => (string) round($plan->cost, 2),
				'paymentMethodToken' => $this->user->defaultCard->gateway_id,
				'trialPeriod' => true,
				'trialDurationUnit' => $trialDurationUnit,
				'trialDuration' => $trialDuration,
			]);
			
		}
		
        if (! $response->success) {
            throw new Exception('Gateway failed to create subscription: '.$response->message);
        }
		
		if ($this->addTrial) {
			if ($trialDurationUnit == Subscription::TrialDurationUnitDay) {
				$trialEndsAt = Carbon::today()->addDays($this->trialDuration);
			} else if ($trialDurationUnit == Subscription::TrialDurationUnitMonth) {
				$trialEndsAt = Carbon::today()->addMonth($this->trialDuration);
			} else {
				throw new \LogicException('Need trial duration unit.');
			}
		}

		return self::create([
			'name' => \Config::get('services.subscription.name'),
            'gateway_id'   => $response->subscription->id,
            'plan_id' => $plan->id,
            'trial_ends_at' => $trialEndsAt ? $trialEndsAt : null,
			'card_id' => $this->user->defaultCard->id, 
        ]);
    }
}

Интересно какие еще есть хорошие практики создания объекта со сложными условиями и дополнительными действиями? Возможно преобразователи?

#32 Re: Хорошие практики (FAQ) » Вопросы по использованию сервис классов » 22.11.2017 16:05:05

1) А если например, сервис должен отправить мейл с кодом активации это из одного контроллера при регистрации, а затем проверить, что этот код введенный в ссылке из письма верен это конечно в другом контроллера. И общее во всем этом это таблица с кодами активации это вообще один сервис или два, или вообще лучше без сервиса и просто дергать в контроллерах этих нужные модельки?

2) Подписку можно отменить из ЛК юзера и из Админки, логика удаления очень похожа. Ну просто типа $user->subscription->cancel() хотя ну мало ли могут быть нюансы разные для юзера и админа. Скажем еще что то перед этим проверить или после этого обновить. Это сервис желателен или достаточно опять же в двух контроллерах просто делать что нужно и дергать  $user->subscription->cancel() ?

Как четко понять что нужен именно сервис?

#33 Re: Хорошие практики (FAQ) » Вопросы по стилю » 22.11.2017 11:36:42

Создай папку Traits в каталоге app, и туда ложи все трейты


У меня так и есть. Просто не удобно каждый раз туда лазить за трейтом. Проще вместе с моделями. Подряд открываешь и их и трейты

#34 Re: Хорошие практики (FAQ) » Вопросы по стилю » 21.11.2017 21:36:58

А хорошо ли положить трейты прямо в ту папку где модели? Типа BillableTrait.php

#35 Re: Хорошие практики (FAQ) » Вопросы по стилю » 21.11.2017 18:15:53

Нет, но наверняка есть много инструментов вроде этого.

Добавил в закладки как разберемся с докером попробую.

#36 Re: Хорошие практики (FAQ) » Вопросы по стилю » 21.11.2017 18:10:51

Кстати а знаешь ли редактор или IDE у которого php код автоматом форматируется корректно?

#37 Re: Хорошие практики (FAQ) » Вопросы по стилю » 21.11.2017 18:08:57

Хорошо понятно. После и перед фигурными скобками строку не пропускаем

#38 Re: Хорошие практики (FAQ) » Вопросы по стилю » 21.11.2017 14:54:54

Пропуски строк между блоками кода

Как из вариантов вернее?

1.

    public function resume(Request $request)
    {
        $user = Auth::user();
        try {
            $user->subscription->resume();
        }  catch (\Throwable $e) {
           return redirect()->route('home.subscription')->with('status', $e->getMessage());
        }
        return redirect()->route('home.subscription')->with('status', 'Subscription resume');
    }

2.

	
    public function resume(Request $request)
    {
     
        $user = Auth::user();
        
        try {
            $user->subscription->resume();
        }  catch (\Throwable $e) {
           return redirect()->route('home.subscription')->with('status', $e->getMessage());
        }
        
        return redirect()->route('home.subscription')->with('status', 'Subscription resume');
    }

3.

      public function resume(Request $request)
      {
     
            $user = Auth::user();
        
            try {
    
	        $user->subscription->resume();
        
	    }  catch (\Throwable $e) {
        
	        return redirect()->route('home.subscription')->with('status', $e->getMessage());
        
	    }
        
            return redirect()->route('home.subscription')->with('status', 'Subscription resume');
    
	}

#39 Re: Laravel 5.x » как организовать продажу файлов (цифрового контента) » 21.11.2017 13:31:00

Кажется такие вещи на коленке не делаются. Первое что приходит на ум cdn нужен на котором файлы хранить на другом сервере чем морда сайта. И его дергать по API с морды на которой ларавель. А на морде регистрация, авторизация, подписка на платные услуги и личный кабинет в котором хранятся купленные картинки

#40 Re: Хорошие практики (FAQ) » Комментарии методов » 20.11.2017 16:36:51

В общем как правильно сделать? И что бы меньше писать..

#41 Re: Хорошие практики (FAQ) » Комментарии методов » 20.11.2017 16:36:37

В этом случае какой-нибудь phpDocumentor


Мы им не пользуемся..

#42 Re: Хорошие практики (FAQ) » Комментарии методов » 20.11.2017 16:12:21

что такое dates и что такое guarded.

Это есть в документации в отличии например от содержимого собственных методов

#43 Re: Laravel 5.x » Проблема с выводом исключения » 19.11.2017 11:04:58

Правильно ли я понимаю, что в 7-ке достаточно писать так:

try
{
   // Code that may throw an Exception or Error.
}
catch (Throwable $t)
{
   // Executed only in PHP 7, will not match in PHP 5
}

И тогда поймаются и php ошибки и исключения которые скажем в коде пакета выбрасываются как throw new SomeException() ??

#44 Re: Laravel 5.x » Проблема с выводом исключения » 19.11.2017 09:33:46

try {...} catch (\Exception $e) {

Это очевидно, я же так и написал сейчас...

Да только сейчас читаю..

#45 Re: Laravel 5.x » Проблема с выводом исключения » 19.11.2017 09:33:24

try {...} catch (\Exception $e) {

Это очевидно, я же так и написал сейчас...

#46 Хорошие практики (FAQ) » Комментарии методов » 18.11.2017 17:14:53

htclog81
Ответов: 5

Везде в том числе в исходниках ларавель, вижу и пишу в своем коде комменты типа:

	/**
    * The attributes that aren't mass assignable.
    *
    * @var array
    */
    protected $guarded = [];
	
	
  
	/**
     * Indicates plan changes should be prorated.
     *
     * @var bool
     */
    protected $prorate = true;

    /**
     * Get the user that owns the subscription.
     */
    public function user()
    {
        return $this->owner();
    }

Но мне кажется писать их у каждого очевидного метода и поля класса лишнее, тормозит разработку и засоряет файлы. Итак ясно что такое dates что такое guarded, что за методы с отношениями, валидациями итд итп, тем более все это повторяется в множестве классов. А может и не писать? Как тут принято? Можно ли писать только у существенных и собственных методов..

#47 Re: Laravel 5.x » Проблема с выводом исключения » 18.11.2017 17:05:10

Хотя бы тип исключения показать... И строку и стек вызовов. В дальнейшем можно сделать условие что бы это показывалось только на тестовом сервере, а на боевом писалось в лог.

#49 Laravel 5.x » Проблема с выводом исключения » 18.11.2017 16:51:44

htclog81
Ответов: 5
	try {
		  $user->newSubscription(Config::get('services.subscription.name'), $plan->braintree_plan)
		  ->create($request->payment_method_nonce, [
			'email' => $user->email,
		  ]);
		  
		} catch (\Exception $e) {
			return redirect()->route('home.upgrade')->with('status', $e->getMessage());
		}

Внутри  try происходит исключение. Но в $e->getMessage() оно в каком то урезанном не информативном виде. только содержимое поле #msg а как более подробно вывести?

#50 Re: Хорошие практики (FAQ) » Соглашения об именовании » 18.11.2017 14:25:17

А имеет ли смысл класть все контроллеры/вьюхи которые относятся к ЛК пользователя и к админке в отдельные папки home admin я так сделал, но теперь кажется что толку то нет..

Подвал раздела