<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های راضیه صفری</title>
        <link>https://virgool.io/feed/@rziw</link>
        <description>یه بکند دولوپر :)</description>
        <language>fa</language>
        <pubDate>2026-06-17 01:12:33</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/516191/avatar/Ec3EgI.jpeg?height=120&amp;width=120</url>
            <title>راضیه صفری</title>
            <link>https://virgool.io/@rziw</link>
        </image>

                    <item>
                <title>از سیر تا پیاز پترن Strategy توی لاراول</title>
                <link>https://virgool.io/@rziw/%D8%A7%D8%B2-%D8%B3%DB%8C%D8%B1-%D8%AA%D8%A7-%D9%BE%DB%8C%D8%A7%D8%B2-%D9%BE%D8%AA%D8%B1%D9%86-strategy-%D8%AA%D9%88%DB%8C-%D9%84%D8%A7%D8%B1%D8%A7%D9%88%D9%84-xo8p37tw7osw</link>
                <description>سلام سلااااام . از اونجایی که کار جدیدم هنوز استارت نخورده و بخاطر درگیری های سال نو و ... اونها  یه وقفه ی چند هفته ای خووووب گیرم اومد واسه خودم کتابای نسبتا قدیمیم رو ریختم بیرون که یه مروری کنم و یسری چیزاشو که برام جذاب بود کنار هم بذارم و بیام اینجاااااا و بنویسم. شاید که واسه کسای دیگه هم جذاب باشه . خب میتونستم کتابا و مطالبای جدیدتر بخونم . اما یهو این تصمیمو گرفتم که چیزای گذشته رو دوباره مرور کنم . شاید بگین چرا خببب. راضیه میگه : چونکه چه توی مباحث مربوط به کار چه هر زمینه ی دیگه ای  ، وقتی برای بار چندم یه کتاب و مطلب رو میخونی با توجه به دید بازتر و کلا درک جدیدتری که نسبت به گذشتت توی اون زمینه پیدا کردی ، بازمممم و بازمممم چیزای جدید از اون مطلب و کتاب یاد میگیری و هربار عمیق تر و عمیق تر. حالا چی اوردمممم براتون؟؟؟؟؟ از عنوان پست م مشخصه :دی از دوتا کتاب : Dive into design patterns که سایت معروف https://refactoring.guru هم معرفیش کرده و یه تیکه هاییش رو گذاشته و کتاب Laravel Design Patterns And Best Practicesقبل از اینکه برید ادامه مطلب یه نکته بگم ببینید اصن حال میکنید برید ادامشو بخونید یا نه؟ اینکه چرا این پترن رو اصلا دارم توی لاراول بررسی میکنم ؟ مگه اینهمممه مثال ازش توی نت نیست ؟ چرا چرااا هست . اما کدوم مثال بهتر از چیزی که توی یه فریم وورک خفن یسری دولوپر خفن اومدن و به بهترین شکل پیادش کردن ؟ بنظر منکه همیشه بررسی و نگاه کردن دل و روده ی فریم وورک های حرفه ای و معروف یا یسری کدهای حرفه ای اوپن سورس معروف ،  &quot;یکی از &quot; بههههترین راه ها واسه یاد گرفتن کد استایل خوب و الهام گرفتن واسه ی دولوپمنت ه  . حالا راه های رسیدن به خدا خب زیاده مثلا جلسات کد ریویو با آدمای حرفه ای و متخصص و ... اما خب اینم یکیشه دیگه . البته گاها توی یسری مصاحبه ها هم ازش سوال میشه . پس اگه موافقین بریم که ببینیم داستان از چه قراره ^_^اهم . اول اولش یه مرور ریزززز به شکل و شمایل این پترن بندازیم باشه؟بفرمایین این دیاگرامش توی کتاب اول:یه لطفی کنید و سمپل عادیشو که هم بصورت سودو کد هست هم به زبان های مختلف از جمله php پیاده شده رو ببینید اول  حالا یا از این سایت:https://refactoring.guru/design-patterns/strategyیا توی همین ویرگول هم بگردین سمپل های فارسیش زیاده.یه توضیح کوتاهم میدم اما بازم تاکید میکنم برای جزییات این پترن رجوووع شود به لینک زیر :https://refactoring.guru/design-patterns/strategyتوضیح کوتاه و خودمونیش میشه که :  وقتی قراره برای  پیاده سازی یه راهکار توی برنامتون چندتا الگوریتم داشته باشین که بسته به شرایط ، بتونید براحتی و درواقع توی ران تایم انتخاب کنید که با کدوم الگوریتم (روش یا به اصطلاح استراتژی ) این قسمت از برنامتون اجرا بشه ، این پترن به کارتون میاد .البتهههه توجه فرااوان بفرمایین که این الگوریتما باید از یه خانواده باشن و درواقع بشه که بجای هم استفاده بشن . درواقع هرکدوم از این روش هارو یه کلاس Strategy میگن  .به گفته ی خود کتاب هم:Strategy is a behavioral design pattern that lets you define afamily of algorithms, put each of them into a separate class,and make their objects interchangeable.. بنظرتون یکم شبیه  composition نبودش؟ البته نه دیزاین پترنش . اون اصل Composition over inheritance . حالا شباهتشم باز تو همین ویرگول بگردین سمپل ازش هست بچه های دولوپر دیگه زحمت کشیدن و توضیحاشو گذاشتن دمشونم گرم و بد هم نیس که بدونید ;)خیله خببب مثال بارز strategy توی لاراول ؟ دقت کردین که توی env. یه کلید داریم به اسم CACHE_DRIVER که مقادیر متفاوتی میتونیم بهش بدیم از جمله فایل یا ردیس یا دیتابیس و ...بعدشم دقت کردین که همینکه شما اینجا درایور خودتون رو مشخص میکنید ، دیگه توی خود فریم وورک جاهایی که میخواین یه مقداری رو توش کش ذخیره کنید بسادگی میاین و مینویسین :Cache::put(&quot;key&quot;, &quot;value&quot;);و انتظار دارین که این تیکه کد براتون به ازای این کلید ، این مقدار رو ، براساس چیزی که توی اون فایل env وارد کردین، ذخیره کنه . ینی درواقع ذخیره ی دیتا روی کش ، میتونه روش های متفاوتی داشته باشه که شما توی env تعیین میکنید کدوم یکی مدنظرتونه و توی ران تایم ،توی کلاسی که به اصطلاح در اون دیاگرام Context گفته میشه ، مورد استفاده قرار میگیره تا کلاس مربوط به این استراتژی رو تشخیص بده و بره تا متد put رو ازش کال کنه.حالا اگه بخواین اینکارو روی دیتابیسی مث ردیس کنه ، یا روی فایل کنه ، این تیکه کدتون که دارین روی کش میریزین تغییری میکنه؟ (منظور همین Cach::put(....) هست ) . نخیر نخیییر ، واسه همینه که میگن : الگوریتما باید از یه خانواده باشن و درواقع بشه که بجای هم استفاده بشن. ینی چه بخوای الگوریتم ذخیره سازیش  یا روش ذخیره سازیش ردیس باشه یا هرچیز دیگه ، کد اصلیش همینیه که میبینید .  اینکه استراتژیش چی باشه (ممکنه جایی بجای کلمه ی استراتژی ، از کلمه ی روش و یا کلمه ی الگوریتم استفاده کنم که توی این مبحث منظور از همش یکیه) تاثیری روی فراخوانی متدی که قراره دیتا روی کش ذخیره کنه نداره . اع . پس کجا باید مشخص بشه؟ تووووووی کلاسی که به اصطلاح در اون کلاس دیاگرام بالا بهش میگن Context ست میشه . چجوری؟؟؟؟؟ خیله خب دیگه وقتشه که بریم واااارد لاراول بشیم ^_^  این وسط به یسری مفاهیم دیگه هم برمیخوریم که خب اونارو هم اشاره ی ریز میکنم و کوتاه میگم . مث فساد توی لاراول (با دیزاین پتن فساد فرق داره لطفا اشتباه نشود) و اورلودینگ توی پی اچ پی عزیز. پس فلن همین کد رو در نظر بگیرین:Cache:put(&quot;name&quot;, &quot;Raziyeh&quot;);اسم خودمو ریختم توی کش . توی IDE خودتون اگر که لطف کنید و Crtl + click  رو روی متد put بزنید میرید وارد کلاس  Illuminate\Support\Facades\Cache میشین . انتظار میره که توی این کلاس یه متد استاتیک به اسم put باشه نه؟ نیست که . خب شاید تو کلاس پدرش باشه که ازش ارث برده ینی کلاس Facade . ای بابا اونجا هم نبود :(   درعوضضضض تو این کلاس پدرش ینی کلاس  Facade  یه متد جذاب دیگه هست . در واقع یه مجیک متد جذاب به اسم __callStaticکه کار متد اورلودینگ واسه متدهای استاتیک رو برامون توی پی اچ پی انجام میده. (بنظرم کلا یکم اورلودینگ با توجه به مفهومش، تو php عجیبه نسبت به بقیه زبونا مث جاوا و اینا :دی ). مثلا اینجا که متد put نه توی کلاس اصلی وجود داشت و نه توی این کلاس پدرش ، توی ران تایم برامون این متد رو به اصطلاح resolve میکنه. چطوری؟ بریم ببینیم : public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot();

    if (! $instance) {
        throw new RuntimeException(&#039;A facade root has not been set.&#039;);
    }

    return $instance-&gt;$method(...$args);
}
خببببب شما خط اول رو داشته باش :   $instance = static::getFacadeRoot();ایشون به ما یه instance از کلاس CacheManager میده . چجوری؟اوه اوه واقعا بسختی. تو دلش یسری متد دیگه عاخه صدا میشه با کارای عجیب غریب :دی . تشریف بیارین از همینجا ینی همین کلاس Facade به متد  getFacadeRoot  ازینجا اول آرگومانش رو بگم :public static function getFacadeRoot()
{
    return static::resolveFacadeInstance(static::getFacadeAccessor());
}اگه اون کلاس فرزند / ینی Cache که از این Facade ارث برده ، متد getFacadeAccessor رو داشته باشه (که تو این مثال داره) خروجیشو بهمون پس میده (که تو این مثال ینی : &#x27;cache&#x27;) :protected static function getFacadeAccessor()
{
    return &#039;cache&#039;;
}پس با  &#x27;cache&#x27; میاد تو دل متد resolveFacadeInstance توی همون کلاس Facade .خببب . اینجاست که instance از CacheManager ساختع میشه. چطوری چطوری؟؟؟؟ شما بیا داخل متد resolveFacadeInstance و این تیکه رو خوووووب و قشنگ و با دقت نگاه کن : static::$app[$name];کل متدشم ایناها:protected static function resolveFacadeInstance($name)
{
    if (is_object($name)) {
        return $name;
    }

    if (isset(static::$resolvedInstance[$name])) {
        return static::$resolvedInstance[$name];
    }

    return static::$resolvedInstance[$name] = static::$app[$name];
}درواقع $app یه instance از Application هست که دسترسی به کانتینر لاراول رو فراهم میکنه. (کانتینر و اینا چیه؟ نه نه دیگه اینجا جاش نیست لطفا رجوع شود به داکیومنت خود لاراول که خیلی شیک و قشنگ توضیحش داده : https://laravel.com/docs/8.x/container)و از اونجایی که موقع boot up یا همون بالا اومدن پروژه ، پرووایدر ها ی لاراول لود میشن و متد رجیسترشون اجرا میشهههه ، پس اگه چیزی  توی یکی از این پرووایدرها و داخل متد رجیسترش به اصطلاح bind شده باشه، دیگه توی app instance بهش دسترسی داریم. که توی این مثال ، داخل CacheServiceProvider توی متد رجیسترش همچین چیزی وجود داره:$this-&gt;app-&gt;singleton(&#039;cache&#039;, function ($app) {
    return new CacheManager($app);
});که داره به ازای اسم cache برامون یه instance از کلاس CacheManager با پترن singleton میسازه . پسسس هرجا لازم باشه میتونیم با استفاده از app instance یا حتی app(&#x27;cache&#x27;) هلپر و ... / به این آبجکت مورد نظرمون ، با اسمی که توی پرووایدر به ازاش مشخص شده (در اینجا &#x27;cache&#x27;) دسترسی داشته باشیم. ینی دقیقا کاری که توی کلاس Facade و داخل متد resolveFacadeInstance انجام شده بود : static::$app[$name] (یکم بالاتر متدشو گذاشتم . البته خب توی کد لاراولتون هم میتونید این کلاسو اینجا ببینید:  Illuminate\Support\Facades\Facade.php)میتونید اصن خود static::$app رو هم اینجا تو کلاس Facade تو یکی از متداش مثلا  dd کنید تا خودتون با چشم خویش ببینید که هم اون پرووایدر ها  توش هست هم مثلا این استرینگ cache بعنوان یه alias توش وجود داره که گفتم میتونید بهش با روشای مختلف دسترسی داشته باشین.حالا بیایم برگردیم ببینیم آخرر این متد put کجاست و چجوری لاراول خیلی شیک و قشنگ با استفاده از پترن Strategy صداش میزنه. پس برمیگردیم سر کلاس Facade و متد __callStatic که سلسله مراتبی رفتیم تو دلش و دیدیم که درواقع اون خط اولش ینی $instance = static::getFacadeRoot();داره بهمون آبجکت از CacheManager($app) میده . و بعدشم آخرای همون متد :return $instance-&gt;$method(...$args);اجرا میشه. متد چی بود اینجا؟ put . آرگوممان هاش چیا بودن ؟ name و Raziyeh .خیله خب پس لابد الانه که داره از کلاس CacheManager متد put رو کال میکنه.کاش اونجااا دیگه همچین متدی باشه. اما وارد این کلاس که میشیم : Illuminate\Cache\CacheManager.phpمیبینیم خعیررررر . اینجا هم خبری نیست . پس کجاست؟‌:(   نیستش اینجام کلا الکی نگردیم دیگه . بجااااش دوباره یه مجیک متد دیگه هستش . به اسم:  __callpublic function __call($method, $parameters)
{
    return $this-&gt;store()-&gt;$method(...$parameters);
}که این یکی هم کار اورلودینگ واسه متدهای غیر استاتیک رو توی php انجام میده. داخلش متد store از همین کلاس  رو صدا میکنه . داخل این متد store دیگه ینی چه خبره؟public function store($name = null)
{
    $name = $name ?: $this-&gt;getDefaultDriver();

    return $this-&gt;stores[$name] = $this-&gt;get($name);
}خلاصه خط اولش اگه که اسم درایور خالی باشه میره و از فایل کانفیگ cache.php درمیاره که درایور مدنظر چی ست شده؟ مثلا اینجا ، ردیس ست شده توی تنظیمات env مون که فایل کانفیگ cache رو هم اگه باز کنید میبینید اولویتش با چیزیه که توی env ست شده ،خلاصه  توی خط بعدیش میفرستتش به این متد توی همون کلاس$this-&gt;get(&#x27;redis&#x27;)و بعدشم از تو متد get میاد و این متد resolve رو از همین کلاس صدا میزنه صدا میزنه که آهااان این متده جالبه:protected function resolve($name)
{
    $config = $this-&gt;getConfig($name);

    if (is_null($config)) {
        throw new InvalidArgumentException(&amp;quotCache store [{$name}] is not defined.&amp;quot);
    }

    if (isset($this-&gt;customCreators[$config[&#039;driver&#039;]])) {
        return $this-&gt;callCustomCreator($config);
    } else {
        $driverMethod = &#039;create&#039;.ucfirst($config[&#039;driver&#039;]).&#039;Driver&#039;;

        if (method_exists($this, $driverMethod)) {
            return $this-&gt;{$driverMethod}($config);
        } else {
            throw new InvalidArgumentException(&amp;quotDriver [{$config[&#039;driver&#039;]}] is not supported.&amp;quot);
        }
    }
}اصل اصل کارش اینجا این تیکه ست که :$driverMethod = &#x27;create&#x27;.ucfirst($config[&#x27;driver&#x27;]).&#x27;Driver&#x27;;اسم متد مورد نظر رو بر اساس چیزی که تو مراحل قبل از کانفیگ فایل کش بدست اورده پیدا میکنه (اینجا پس اسم متد میشه createRedisDriver) و بعدشم که صداش میکنه :return $this-&gt;{$driverMethod}($config);خیله خب پس تا اینجا بلخرررره رسیدیم به متد createRedisDriver. بریم ببینیم اینجا چه خبره؟؟؟protected function createRedisDriver(array $config)
{
    $redis = $this-&gt;app[&#039;redis&#039;];

    $connection = $config[&#039;connection&#039;] ?? &#039;default&#039;;

    return $this-&gt;repository(new RedisStore($redis, $this-&gt;getPrefix($config), $connection));
}بله بلهههه بلخره استراتژی مورد نظر تعیین میشه و خط آخرش هم متد repository از همین کلاس رو با آرگومانی که شامل کلاس استراتژی RedisStore هست صدا زده میشه. اما هنووووز متد put از این استراتژی صدا نزده شده . حالا تو خود متد repository هم نهایتا کلاس \Illuminate\Cache\Repository داره برمیگرده .خبببب . حالا این کلاس Repository چیه؟؟؟  دقیقا حکم اون کلاس Context رو توی اون شکل دیاگرامی که گذاشتم داره .  اگر این کلاسش رو باز کنید ملاحظه میفرمایین که توی construct ش ،پارامتری که میگیره،کلاس استراتژی ای هستش که بالاتر بلخره کشفش کردیم و  ست میشه توی پراپرتی store ش :public function __construct(Store $store)
{
    $this-&gt;store = $store;
}این تیکه ش هم همونطوری که از کلاس Context انتظار میره ، داره استراتژی مدنظر رو ست میکنه تا به وقتش با استفاده ازش نهااااااایتا اون متد put لامصب که میخاستیم کال بشه.الان میگم وقتش کی هست: اگه برررگردیم به متدای بالاتری از کلاس CacheManager که هی کلی چیزای دیگه توش کال شده بود ، ینی متد __call میبینیم که بعلهه  :public function __call($method, $parameters)
{
    return $this-&gt;store()-&gt;$method(...$parameters);
}این $this-&gt;store()ما شد همون آبجکت از کلاس ریپازیتوری با کانستراکتوری که کلاس RedisStore رو توی پراپرتی store ش ست میکنه و وقتی $this-&gt;store()-&gt;method(...$parameters)صدا زده میشه هم در واقعععع وارد کلاس RedisStrategy میشه و اینجاااااااااااااااس که بلخررره متد put ازش داره صدا زده میشه. یه لحظه صبر کنید . یه بار سریع مرور کنیم از چه متدایی رد شد تا به اینجا رسید:از کلاس CacheManager و متد __call رفت اول متد storeبعدش از متد store رفت متد get تو همون کلاسبعدش از متد get رفت متد resolve تو همون کلاسبعدش از تو متد resolve ، ازونجایی که درایور انتخابی ما ردیس بود(توی env مشخصش کرده بودم redis) رفتش متد createRedisDriver تو همون کلاسبعدش از متد createRedisDriver رفت متد repository توی همون کلاس که دیگه خداروشکر به همینجا ختم شد و خروجی نهایی این متدکال های تودر تو اینجا شد یه آبجکت از کلاس \Illuminate\Cache\Repositoryبا پارامتر کانستراکتورش که RedisStore شد و دیگههه این خروجی اومد بالا بالااا و رسید به دست متد اولیه که تو این کلاس کال شد ینی __callهیچی خاستم یه دور سلسله مراتبشو سریع بگم که چه راه طولانی ای پشت سر گذاشت :دیکه حالا اگه خود کلاس RedisStore رو هم باز کنید میبینید که یه Interface رو داره implement میکنه به اسم Store که دقیییقا حکم اون Strategy Interface رو داره که شکل دیاگرامشو اون بالای پست گذاشته بودم . و وارد Interface ش هم که بشید میتونید با IDEتون چک کنید ببینید چه کلاس هایی implement ش کردن که اینجا شامل کلاسایی مثل : FileStore, RedisStore, ArrayStore, ApcStore, DatabaseStore و ... هست که این هاااا هم درواقع همون کلاس های concrete هستند که توی شکل دیاگرام نشون میداد.عاقا تامام تامام . دوستم کنارم میگه داکیومنت لاراول رو اگه ترجمه میکردی الان تموم شده بود :))) ایشششالا که اینهمه نوشتم خوشتون اومده باشه و مفید واقع شه . فلن تا پست بعدی که نمیدونم کی باشه و کی وقت کنم باز بنویسم خدااانگهدار ... ;)</description>
                <category>راضیه صفری</category>
                <author>راضیه صفری</author>
                <pubDate>Mon, 10 Jan 2022 14:35:12 +0330</pubDate>
            </item>
                    <item>
                <title>لاگین لاراول با استفاده از پاسپورت و سوشالایت اپل</title>
                <link>https://virgool.io/CodeLovers/%D9%84%D8%A7%DA%AF%DB%8C%D9%86-%D9%84%D8%A7%D8%B1%D8%A7%D9%88%D9%84-%D8%A8%D8%A7-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-%D9%BE%D8%A7%D8%B3%D9%BE%D9%88%D8%B1%D8%AA-%D9%88-%D8%B3%D9%88%D8%B4%D8%A7%D9%84%D8%A7%DB%8C%D8%AA-%D8%A7%D9%BE%D9%84-euhhodnmmhzs</link>
                <description> اگر که از لاراول بعنوان فریم وورک بکندتون استفاده میکنید و api برنامتون رو در اختیار دیگران قرار میدین ، احتمالا از  passport و یا لایبرری های مختلف php jwt برای احراز هویت و ارتباط کلاینت ها با برنامتون استفاده میکنید.  توی این پست من فرض رو بر این میذارم که از passport برای پیاده سازی oauth2 و  api دادن توی برنامتون استفاده میکنید. از اونجایی که استفاده از پاسپورت بطور کاااااامل و جامع توی داکیومنت خود لاراول توضیح داده شده ، من بطور مختصر مراحل استفاده از پاسپورت رو میگم تااا برسیم به قسمت اصلی ینی استفاده ی همزمان از passport و laravel socialite که کاربرا بتونن هم بطور مستقیم (ینی با تعریف یوزرنیم و پسوورد بصورت عادی )توی سایت ما لاگین یا رجیستر شن ، هم اینکه بتونن با اکانتشون توی شبکه های اجتماعی و سرویس های مختلف (موردی که اینجا توضیح میدم اکانت اپل هست ) توی سایت ما لاگین یا رجیستر کنن و از اون به بعد به endpoint های ما بصورت احراز هویت شده و با  token ی که در اختیارشون قرار داده میشه، وصل بشن.خببببب اول اول همههه ، میریم که passport رو نصب کنیم :composer require laravel/passportبعد از نصب هم دستورای زیر رو میزنیم تا جداول مورد نیازمون ساخته بشه و پر بشه:php artisan migrate
php artisan passport:install‍‍‍‍‍و بعد trait های HasApiTokens, HasFactory, Notifiable رو توی مدل User تون use میکنید. و بعد توی فایل config/auth.php کد زیر رو اضافه میکنید:&#039;api&#039; =&gt; [
        &#039;driver&#039; =&gt; &#039;passport&#039;,
        &#039;provider&#039; =&gt; &#039;users&#039;,
 ],خب تا اینجا که همه مراحل با جزییات توی داکیومنت لاراول هست.بعدشششش، باید در جریان باشیم که ، درحال حاضر لاراول، ۴ نوع grant type  از oauth2 رو داره : Authorization CodeImplicitPasswordClientکه توی این صفحه خود داکیومنت لاراول بهشون اشاره کرده : https://laravel.com/docs/8.x/passport (برای جزییات بیشتر از هرکدوم از این grant type ها میتونید به خود سایت https://oauth.net/  مراجعه کنید. به ترتیب هم لینکش رو اینجا میذارم) : https://oauth.net/2/grant-types/authorization-code/https://oauth.net/2/grant-types/implicit/https://oauth.net/2/grant-types/password/4.  https://oauth.net/2/grant-types/client-credentials/که حالااا برای احراز هویت عادی با یوزرنیم و پسوورد توی سایتمون کدوم یکی مورد نیازه؟؟؟؟ بله بله password   .grant typeبعدش برای احراز هویت با سوشال مدیا ها در کنار احراز هویت عادی کدومش مورد نیازههه؟؟؟؟ آفرین هیچکدام  . بعدشم خب ما که تو این حالتا پسوردی نداریم که باش لاگین کنیم تو برنامه لاراولیمون . پس چیکار کنیمممم؟؟؟؟  :(  باید که یه grant type کاستوم برای خودمون بسازیم و یااا اینکه از یسری لایبرری های آماده استفاده کنیم که اونا واسمون اینکارو کنن و یاااا اینکه از همین passport grant type به یه شکل کلک طوری استفاده کنیم تا به کارمون بیاد .  حالا وایستین اول خود socialite لاراول رو نصب کنیم تا تو مرحله ی کد نویسیش همشو بگم :composer require laravel/socialiteبعدششش اگه که اون سوشالی که میخواین توی سایت لاراول بهش اشاره نشده (مثل الان ما که میخوایم از سوشال اپل استفاده کنیم) میتونید به لینک https://socialiteproviders.com/about/  مراجعه کنید و سوشال خودتون رو پیدا کنید و نصب کنید . الان پس برای کارمون ما اینو نصب میکنیم :composer require socialiteproviders/appleخیله خب الان دیگه نصب کردنیا تمام شد.کانفیگ این سوشالایت اپل رو هم دقیقا طبق مراحلی که اینجا گفته انجام میدیم:https://socialiteproviders.com/Apple/#installation-basic-usageحالا مواد لازم برای لاگین با اپل رو که باید از پنل دولوپر اپل بدستش بیارین و کنار خودتون داشته باشین رو میگم: client idteam idprivate keyصبوری کنید تا وقتش که برسه مورد استفاده ش رو هم بگممم. خلاصه که مراحل احراز هویت از اول اول وقتی که با پلتفورم اپل لاگین میکنید تاااا وقتی که میاین با برنامه ی لاراولی ما لاگین کنید به شکل زیر هست:دولوپر ios برای کاربرای برنامه ش گزینه ی لاگین با اپل رو میاره که بعد از کلیک روی اون گزینه،ازشون ایمیلی که باش توی اپل لاگین هستن و پسووردش رو میگیره و در صورت درست وارد کردن اطلاعات توسط کاربر و اتمام عملیات authentication به درستی، سرور اپل بنا به نوع درخواست و grant type که دولوپر ios ازش خواسته ، میتونه یه access token  و refresh token و جزییات token رو در اختیارمون قرار بده، و یا اینکه یه authorization code بهمون بده. اما اینجا کدومش بدرد ما میخوره؟ authorization code. چرا؟؟؟؟ چون اون توکنی که اپل پس میده، فقط خود اپلیکیشن ios ازش مطمينه که حاصل یه لاگین موفق از سمت سرور اپل بوده و فقط واسه خودش  بدرد میخوره. اما حالا که اپلیکیشن اپل تنها و مستقل نیست و داره از ما api میگیره پس باید از سمت برنامه ی ما توکن شناخته شده ای داشته باشه نه صرفا از سمت اپل . شاید بگین خیله خب همین توکن رو بیاد برای ما بفرسته ما هم همونو توی سیستم نگهداری کنیم و شناخته شدش کنیم و ازش استفاده کنیم. اما سوال اینجاس که : عاخه  از کجا مطمین باشیم که این توکن دقیقا همونیه که سرور اپل به اپلیکیشن ios پس داده و بعدش اپلیکیشن هم برای ما فرستاده؟؟؟ ینی هر توکنی بیاد ما به همین راحتی بپذیریم و اطلاعاتشو بخونیم و تو دیتابیس ذخیره ش کنیم؟؟؟؟ نخیییییییر .شاید راه های ابتدایی برای اینکار باشن اما اگه بخوام راه اصولیشو بگم اینه کههه : وقتی اون authorization code به دستمون رسید باید خودمون یجوری بریم از سرور اپل بپرسیم که عاقا این کد واقعنی از سمت شما اومدهههه؟؟؟؟؟ حالا این درخواست که از سرور اپل همچین چیزی بپرسیم چارچوب و پارامترای خاصی داره و  grant typeش Authorization Code هستش. بنااابر اینهمه توضیحااات (بعدا نیاین بگین پس توضیحاااااااااااات؟؟؟) ، در ابتدااای امر دولوپر ios از سرور اپل توی لاگین موفق باید درخواست authorization code بکنه و اون authorization code  رو به ما پس بده و ما همین Authorization Code  رو  پست کنیم به سرور اپل تایید بشه که اون کد واقعی از سمت خود سرور اپل و حاصل لاگین موفقی بوده (به این مرحله هم میگن Authorization. توجه کنید که Authorization  رو با Authentication اشتباه نگیرین)      چارچوب این ریکويست حتما باید به همین شکل باشه   :$data = [
    &#039;client_id&#039; =&gt; env(&amp;quotAPPLE_CLIENT_ID&amp;quot),
    &#039;client_secret&#039; =&gt; env(&amp;quotAPPLE_CLIENT_SECRET&amp;quot),
    &#039;code&#039; =&gt; $request-&gt;authorizationCode,
    &#039;grant_type&#039; =&gt; &#039;authorization_code&#039;,
];
و پستش میکنیم به سرور اپل :$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, &#039;https://appleid.apple.com/auth/token&#039;);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));

$response = json_decode(curl_exec&#40;$ch&#41;);
curl_close($ch);

return $response;اون مواد لازم رو یادتونه که گفتم باید از پنل دولوپر اپل برش دارین؟ الان اینجاها دیگه استفاده میشه. مقدار اول توی آرایه ی data یعنی client_id یکیشه که از پنل دولوپر اپل داریمش . مقدار دوم رو هم باید خودمون بسازیم. چطوری؟ با داشتن client_id و یه کلید خصوصی (که این کلید خصوصی رو هم گفته بودم از پنل دولوپر اپل برمیداریم.برداشتین  که انشالله؟) و بعدش ، یسری مقادیر رو کنار هم میذاریم (حالا توضیحشون میدم) و توسط اون کلید خصوصی که داریم امضاشون میکنیم. کد آماده ش به زبان Ruby هست :require &#039;jwt&#039;
key_file = &#039;key.txt&#039;
team_id = &#039;ُTEAM_ID&#039;
client_id = &#039;CLIENT_ID&#039;
key_id = &#039;KEY_ID&#039;
ecdsa_key = OpenSSL::PKey::EC.new IO.read key_file

headers = {
&#039;alg&#039;: &#039;ES256&#039;,
&#039;kid&#039; =&gt; key_id
}

claims = {
    &#039;iss&#039; =&gt; team_id,
 &#039;iat&#039; =&gt; Time.now.to_i,
 &#039;exp&#039; =&gt; Time.now.to_i + 86400*180,
 &#039;aud&#039; =&gt; &#039;https://appleid.apple.com&#039;,
 &#039;sub&#039; =&gt; client_id,
}

token = JWT.encode claims, ecdsa_key, &#039;ES256&#039;, headers

puts tokenمتغیر key_file  برابر آدرس اون اون فایلی هست که شامل کلید خصوصیه. اینجا من تو همین مسیری که این کد روبی هست قرارش میدم.متغیر team_id و client_id و key_id رو هم از پنل دولوپر اپل گرفتین و توی کد بالا جایگزین میکنید.و نهایتا کد بالا رو اجرا کنید تا توی خروجی shell بهتون اون secret_key ایجاد شده رو بده.(قطعا قبلشم که Ruby رو نصب کردین؟آفرین آفرین)یه توضیح کوچیکم بدم که این secret_key و این فرایند امضا کردن این پارامترها باهم به چه درد میخوره. اون وقتی که ما داریم authorization code رو به اپل میفرستیم ، سرور اپل هم باید مطمين شه این ریکویست از سمت یه درخواست دهنده ی معتبر(که  اینجا برنامه لاراولی ما هستش ) براش اومده و درواقع ارسال کننده رو شناسایی کنه و نهایتا فرآیند Authorize شکل بگیره و سرور اپل بهمون توکن مورد نیاز و جزییاتش رو بده. توی این توکن یسری اطلاعاتی encode شده از جمله ایمیل اون کاربر . الان با توجه به اینکه خیالمون راحته که توکن از سمت اپل اومده میتونیم ایمیلی رو که توی توکن encode شده رو با استفاده از socialite لاراول decode کنیم و توی دیتابیس جدول users مون ثبت کنیم. به این منظور من یه کلاس AppleAuthenticationService ساختم و توش اینکارو انجام میدم:socialUser = Socialite::driver(&#039;apple&#039;)-&gt;userFromToken($response-&gt;id_token);
$user = User::findByEmail($socialUser-&gt;email);

if ($user) {
    //Issue a token and return back it to user
} else {
    //Create user in database and then Issue a token and return back it to user
}دقت کردین که برای گرفتن اطلاعات کاربر از جمله ایمیل کاربر از توکن توسط socialite (همون خط اول کد بالا منظورمه) نیاز به هیچ کلیدی نداریم و اون کلیدهایی که پنل دولوپر اپل دراختیارمون قرار میده صرفا برای مرحله قبل(authorize شدن توسط سرور اپل) هست؟ این توکن الان رو اگه الگوریتم کدگذاریشو بدونیم (که اینجا ES256 هست)  از اونجایی هم که یه Bearer token هست براحتی میتونید حتی توی خود سایت jwt.io دیکریپتش کنید و محتویاتش رو ببینید. مهم هم نیست چون هدف اصلی از این فرایند شناسایی فرستنده بوده و اصولا اطلاعات حساسی توی توکن نیست (و البته قرارم نیست حتی از اون توکن برای ارتباط کلاینت با برناممون استفاده کنیم! فقط اطلاعات مورد نیازمونو ازش برمیداریم و استفاده میکنیم و تماام )و یه چیز دیگه هم که باید دقت کنیم اینه که : ما توی این داستان لاگین و رجیستر با شبکه های اجتماعی، پسووردی نداریم . نهایت اطلاعاتی هم که از توکن میگیریم مثلا ایمیل طرف و اسم و فامیلشه . پس تو این مرحله باید که یه migration برای اصلاح جدول یوزر بسازیم و فیلد پسوورد رو nullable کنیم تا موقع ایجاد یوزر به مشکلی برنخوریم.(کدش رو دیگه اینجا قرار نمیدم)حالا پسسس . اون یوزر ios بنده خدا اینهمه زحمت کشیده رفته اونور لاگین کرده به ما authorization code رسونده بعد ما باهاش توکنو گرفتیم بعد از توکنه اطلاعاتیو ک لازم داشتیم گرفتیم. حالا پس چجورییی با چه توکنی بیاد و با برنامه ما ارتباط برقرار کنه؟؟ ( دقیقا اون تیکه ای که توی کد بالا کامنت گذاشتما که: Issue a token . دقیقا منظورم اون تیکه هست که چطوری انجام میشه؟؟؟ صبر کنید کم کم به این تیکه هم میرسیم)خیله خب اشکال نداره ما همینکه تو این مرحله مطمین شدیم یوزر قشنگمون اطلاعات کاربری اپل ش واقعا درست و معتبر بوده ، ایمیلشو تو دیتابیس ذخیره میکنیم و بلخره خودمون براش یه توکن میسازیم بهش میدیم که دیگه  باش بتونه با برنامه ما ارتباط برقرار کنه. چجوری؟؟؟ اینجا خودش ۳ تا راه داره که یکی یکی میگم :۱. یه grant_type کاستوم خودمون بسازیم که برامون کارای ثبت و ارسال توکن و جزییاتش رو انجام بده۲. از همون  password grant_type یطوری استفاده کنیم که به کارمون بیاد۳. از یسری لایبرری استفاده کنیم تا خودش برامون این grant_type کاستوم رو ایجاد کنه.راه اول :برای این کار یه کلاس کاستوم برای خودمون ایجاد میکنیم که این کلاس باید خودش از کلاس AbstractGrant  (که توی نصب لایبرری پاسپورت و oauth2 توی اون مراحل اولیه که توضیح دادم ،به فولدر vendor مون اضافه شده  و کلا فرايندهای ساخت توکن و ... رو انجام میده) ،  ارث ببره و ما یسری متدهاش رو به اون شکلی که به کار ما میاد override کنیم. من این کلاس رو توی فولدر app/Auth/Grants  به این شکل میسازم:&lt;?php

namespace App\Auth\Grants;

use RuntimeException;
use Laravel\Passport\Bridge\User;
use League\OAuth2\Server\RequestEvent;
use Psr\Http\Message\ServerRequestInterface;
use League\OAuth2\Server\Grant\AbstractGrant;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;

class SocialGrant extends AbstractGrant
{
    /**
     * @param RefreshTokenRepositoryInterface $refreshTokenRepository
     */
    public function __construct(
        RefreshTokenRepositoryInterface $refreshTokenRepository
    ) {
        $this-&gt;setRefreshTokenRepository($refreshTokenRepository);

        $this-&gt;refreshTokenTTL = new \DateInterval(&#039;P1M&#039;);
    }

    /**
     * {@inheritdoc}
     */
    public function respondToAccessTokenRequest(
        ServerRequestInterface $request,
        ResponseTypeInterface $responseType,
        \DateInterval $accessTokenTTL
    ) {
        $client = $this-&gt;validateClient($request);
        $scopes = $this-&gt;validateScopes($this-&gt;getRequestParameter(&#039;scope&#039;, $request));
        $user = $this-&gt;validateUser($request, $client);

        // Finalize the requested scopes
        $scopes = $this-&gt;scopeRepository-&gt;finalizeScopes($scopes, $this-&gt;getIdentifier(), $client, $user-&gt;getIdentifier());

        // Issue and persist new tokens
        $accessToken = $this-&gt;issueAccessToken($accessTokenTTL, $client, $user-&gt;getIdentifier(), $scopes);
        $refreshToken = $this-&gt;issueRefreshToken($accessToken);

        // Inject tokens into response
        $responseType-&gt;setAccessToken($accessToken);
        $responseType-&gt;setRefreshToken($refreshToken);

        return $responseType;
    }

    protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client)
    {
        $username = $this-&gt;getRequestParameter(&#039;username&#039;, $request);
        if (is_null($username)) {
            throw OAuthServerException::invalidRequest(&#039;username&#039;);
        }

        $user = $this-&gt;getUserEntityByUserOtp(
            $username,
            $this-&gt;getIdentifier(),
            $client
        );

        if ($user instanceof UserEntityInterface === false) {
            $this-&gt;getEmitter()-&gt;emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));

            throw OAuthServerException::invalidCredentials();
        }

        return $user;
    }

    private function getUserEntityByUserOtp($username, $grantType, ClientEntityInterface $clientEntity)
    {
        $provider = config(&#039;auth.guards.api.provider&#039;);

        if (is_null($model = config(&#039;auth.providers.&#039;.$provider.&#039;.model&#039;))) {
            throw new RuntimeException(&#039;Unable to determine authentication model from configuration.&#039;);
        }

        $user = (new $model)-&gt;where(&#039;username&#039;, $username)-&gt;first();

        if (is_null($user)) {
            return;
        }

        return new User($user-&gt;getAuthIdentifier());
    }

    /**
     * {@inheritdoc}
     */
    public function getIdentifier()
    {
        return &#039;social&#039;;
    }
} خیله خب الان grant type مورد نظرمون با اسم دلخواه که من اسمشو گذاشتم social ساخته شد. مرحله ی بعد اینه که این grant type رو به پروژمون بشناسونیمش و درواقع enable ش کنیم. چطوری؟ میایم توی کلاس AuthServiceProvider و این خط رو توی متد boot بالا سر Passport:routes(); اضافه میکنیم:app(AuthorizationServer::class)-&gt;enableGrantType(
    $this-&gt;makeSocialGrant(), Passport::tokensExpireIn()
);و همچنان توی همین کلاس این متد رو هم آخر کلاس اضافه میکنیم:protected function makeSocialGrant()
{
    $grant = new SocialGrant(
        $this-&gt;app-&gt;make(RefreshTokenRepository::class)
    );

    $grant-&gt;setRefreshTokenTTL(Passport::refreshTokensExpireIn());

    return $grant;
}خیله خب بلخره grant type مورد نظرمون هم درست شد و آماده ی استفاده هست.الان پس با خیال راحت میریم سراغ قسمتی که بتونیم بعد از ایجاد یوزر توی دیتابیسمون (یا اگه دیدیم قبلا وجود داشته)بهش توکن پس بدیم. پس اون کلاس AppleAuthenticationService رو که یه تیکه از کدش رو بالاترا قرار دادم تکمیلش میکنم:socialUser = Socialite::driver(&#039;apple&#039;)-&gt;userFromToken($response-&gt;id_token);
$user = $this-&gt;userRepository-&gt;findByEmail($socialUser-&gt;email);

if ($user) {
    $tokenData = $this-&gt;issueToken($user,  $client);
} else {
    $user = $this-&gt;userRepository-&gt;store($userDataArray);//$userDataArray means data which we got from token
     $tokenData = $this-&gt;issueToken($user,  $client);
}شاید سوال بشه که متغیر client از کجا اومد ؟ اون زمانی که داریم کارای passport رو میکنیم و براش migrate زدیم  و بعد براش دستور php artisan passport:install رو زدیم . یه رکورد توی جدول oauth_clients برامون ثبت شده و برای کارمون اون رکورد رو از جدولش فچ کردیم که حاصلش شده client$. خیله خب حالا ببینیم که داخل متد issueToken چی میگذره:$data = [
    &#039;grant_type&#039; =&gt; &#039;social&#039;,
    &#039;client_id&#039; =&gt; $client-&gt;id,
    &#039;client_secret&#039; =&gt; $client-&gt;secret,
    &#039;username&#039; =&gt; $user-&gt;username,
    &#039;provider&#039; =&gt; &#039;users&#039;
];

$proxy = Request::create(&#039;oauth/token&#039;, &#039;POST&#039;, $data);
$oauth = app()-&gt;handle($proxy);
$data = json_decode($oauth-&gt;getContent(), true);هورااااااااا ! روش اول تمام و توسط این کد ، توکن مورد نیازمون ایجاد شده، و بخشی از اون که لازمه ،  توی جداول oauth_access_tokens و oauth_refresh_tokens ثبت شده و توی خروجی این درخواست هم توی متغیر data ، اون توکن و رفرش توکن و ایناش وجود داره که باید در اختیار کلاینت ios تون بدین. (البته اگه که دقیقا اطلاعات رو طبق چیزی که اینجا توضیح دادم وارد کرده باشین. درغیر این صورت ممکنه خطاهای مختلف بده. پس محض احتیاط بهتره که این request تون رو هم حالتای خطاش رو بررسی و هندل کنید (اونم کدش رو اینجا قرار ندادم اما حتمنی اینکارو بکنید.)خیله خببب بریم سراغ روش دوم:گفته بودم که توی این روش، میتونیم از همون password grant type که توی لاگین عادی، توسط یوزرنیم و پسوورد، توی پاسپورت انجام میشه استفاده کنیم. فقط اما باید به یسری نکات توی این روش خیلی توجه کنیم:اینجا دیگه نیازی نیستش که فیلد پسوورد رو توی جدول users، nullable کنیم.توی فایل  env. باید یه مقدار مثلا به اسم SOCIAL_DEFAULT_PASSWORD تعریف کنیم و برابر با یه پسووردی قرارش بدیم.موقع ایجاد یوزر توی جدول ،همین پسوورد رو براش هش کنیم و ست کنیم و حواسمون هم باشه موقع لاگین با سوشال مورد نظرمون هم، همین پسوورد رو براش ست کنیم.درواقع کد درخواست توکن ش (ینی اینبار داخل متد issueToken مون که تو روش ۱ ساختیمش به جای اون کدای روش ۱، به این شکل هست)هم خیلی شبیه لاگین معمولی پاسپورت و به این شکل میشه :$params = [
    &#039;grant_type&#039; =&gt; &#039;password&#039;,
    &#039;client_id&#039; =&gt; $client-&gt;id,
    &#039;client_secret&#039; =&gt; $client-&gt;secret,
    &#039;username&#039; =&gt; $user-&gt;username,
    &#039;password&#039; =&gt; env(&#039;SOCIAL_DEFAULT_PASSWORD&#039;),
    &#039;scope&#039; =&gt; &#039;*&#039;,
];

$proxy = Request::create(&#039;oauth/token&#039;, &#039;POST&#039;, $params);
$oauth = app()-&gt;handle($proxy);
$data = json_decode($oauth-&gt;getContent(), true);

return $data;و دقیقا نتیجه ی کد و خروجیش شبیه روش ۱ هست که بالا توضیح دادم.و اما روش سوم: استفاده از یسری لایبرری ها مثل https://github.com/coderello/laravel-passport-social-grant  هست که خودش برامون grant_type کاستوم و به اسم social میسازه و طریقه ی استفادش رو هم توی گیت هابش توضیح داده.و تماااام... دمتون گرم که تا اینجای پست رو اومدین و خوندین . امیدوارم که براتون مفید باشه و به کارتون بیاد. البته توجه کنید که من بیشتر سعی کردم مفاهیم کلی و ضروری رو توضیح بدم و از نوشتن کدهای مربوط به جزییات متفرقه صرفنظر کردم.  درکل اگه که جایی مشکلی داشتین یا خواستین که کدهارو تمامش رو داشته باشین بهم اطلاع بدین تا توی گیتهابم براتون کد کاملش رو بذارم. خوشحالم میشم که نظراتون رو هم بنویسین چه بسا کمک کنه جایی از کار رو بهبودش بدم.راستی خودم هم برای پیاده سازی این کد و نوشتن این مطلب از این منابع استفاده کردم :https://medium.com/@arifulislam_ron/create-custom-grant-token-in-laravel-passport-1ff0cc255dc5https://laracasts.com/discuss/channels/laravel/using-socialitesocial-login-with-laravel-passporthttps://itnext.io/laravel-api-authentication-for-social-networks-oauth2-social-grant-3ec1085b58b6</description>
                <category>راضیه صفری</category>
                <author>راضیه صفری</author>
                <pubDate>Sun, 03 Jan 2021 19:20:25 +0330</pubDate>
            </item>
            </channel>
</rss>