Created
April 8, 2025 18:55
-
-
Save vianneychin/a4297104d4cb6a00597021e9251c544c to your computer and use it in GitHub Desktop.
Some of my favorite Laravel code that I've written recently:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// A custom upsert method that I've implemented that both handles mass insert in one query as opposed to many. | |
// The Laravel upsert only returns a boolean so I've added a custom flag that allows you to return the data that was just upserted. | |
/** | |
* Handles upserting CommentCollections with option to return upserted data | |
* | |
* @param array $collectionsToUpsert Array of comment collections data to upsert | |
* @param bool $returnUpsertedData Whether to return the upserted records | |
* @param int|null $projectId Project ID to filter results when returning data | |
* @return Exception|Collection|bool | |
*/ | |
public static function handleUpsert( | |
array $collectionsToUpsert, | |
bool $returnUpsertedData = false, | |
int|null $projectId = null | |
): Exception | Collection | bool { | |
$upsertSuccessful = CommentCollection::upsert( | |
$collectionsToUpsert, | |
uniqueBy: ["post_id", "project_id", "user_id"], | |
update: [ | |
"title", | |
"views_count", | |
"likes_count", | |
] | |
); | |
if ($upsertSuccessful && $returnUpsertedData) { | |
$postIds = array_column($collectionsToUpsert, 'post_id'); | |
$collections = CommentCollection::whereIn('post_id', $postIds) | |
->where('project_id', $projectId) | |
->get(); | |
return $collections; | |
} | |
return $upsertSuccessful; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// Example of some service providers I've written for external APIs for easy reusability. | |
namespace App\Services; | |
use App\Models\InstagramAccountPost; | |
use Exception; | |
use Illuminate\Database\Eloquent\Builder; | |
use Illuminate\Support\Facades\Http; | |
use InfluencerTools\User; | |
use Illuminate\Support\Str; | |
use Throwable; | |
class UserInstagramPostsService | |
{ | |
private $instagramPostsLimit = 25; | |
public function __construct(private User $user) {} | |
/** | |
* @param array $keywords | |
* @param string|null $pullFromDate | |
* @return Builder|bool | |
* @throws Exception | |
* @throws Throwable | |
*/ | |
public function storeInstagramPostsByKeyword( | |
array $keywords, | |
string $pullFromDate = null | |
) { | |
$nextPageToken = null; | |
do { | |
$response = $this->getPostData(nextCursor: $nextPageToken); | |
$posts = $response->data ?? []; | |
$nextPageToken = $response->paging->cursors->after ?? null; | |
$postsToUpsert = []; | |
if ($this->hasReachedPaginationThresholdFor($posts)) { | |
$nextPageToken = null; | |
} | |
foreach ($posts as $post) { | |
$postedAt = $this->convertToMySqlTimestamp($post->timestamp); | |
if ($pullFromDate && $postedAt < $pullFromDate) { | |
$nextPageToken = null; | |
break; | |
} | |
if ($this->captionContainsAnyKeyword($post->caption, $keywords)) { | |
$postsToUpsert[] = [ | |
'post_id' => $post->id, | |
'caption' => $post->caption, | |
'posted_at' => $postedAt, | |
'comments_count' => $post->comments_count, | |
'video_views' => $post->media_views ?? null, | |
'likes_count' => $post->like_count, | |
'permalink' => $post->permalink, | |
'user_instagram_account_id' => $this->user->instagram->id | |
]; | |
} | |
} | |
} while ($nextPageToken); | |
$instagramPosts = InstagramAccountPost::handleUpsert( | |
postsToUpsert: $postsToUpsert, | |
returnUpsertedQuery: true | |
); | |
return $instagramPosts; | |
} | |
public function getPostData($nextCursor = null) | |
{ | |
$id = $this->user->instagram->profile_id; | |
$accessToken = $this->user->instagram->access_token; | |
// Skip check if user has a manually inputted instagram | |
if (!$this->user->instagram->accountIsManuallyAdded) { | |
throw new Exception('No access token found.'); | |
} | |
$params = [ | |
'fields' => 'id,caption,media_type,media_url,permalink,timestamp,comments_count,like_count,video_views', | |
'access_token' => $accessToken, | |
'limit' => $this->instagramPostsLimit | |
]; | |
if ($nextCursor) { | |
$params['after'] = $nextCursor; | |
} | |
try { | |
$response = Http::get("https://graph.facebook.com/v13.0/{$id}/media", $params); | |
return $response->object(); | |
} catch (Throwable $th) { | |
report($th); | |
} | |
} | |
/** | |
* @param string $caption | |
* @param array $keywords | |
* @return bool | |
*/ | |
public function captionContainsAnyKeyword(string $caption, array $keywords): bool | |
{ | |
return collect($keywords)->some(fn($keyword) => Str::contains(haystack: $caption, needles: $keyword)); | |
} | |
/** | |
* @param string $timestamp | |
* @return string | |
*/ | |
public function convertToMySqlTimestamp(string $timestamp): string | |
{ | |
$date = new \DateTime($timestamp); | |
$date->setTimezone(new \DateTimeZone('America/Los_Angeles')); | |
return $date->format('Y-m-d H:i:s'); | |
} | |
/** | |
* @param mixed $posts | |
* @return bool | |
* | |
* If we have 20 or less, then don't go to next page. | |
*/ | |
public function hasReachedPaginationThresholdFor($posts) | |
{ | |
return count($posts) < ($this->instagramPostsLimit - 5); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// Example query that I've built that is blazingly fast for live-time data changing with Vue/React. This is re-used in several places as well. | |
/** | |
* @param Project $project | |
* @return QueryBuilder | |
* | |
* @throws InvalidArgumentException | |
* @throws BindingResolutionException | |
*/ | |
public function commentCollectionsQueryBuilder(): QueryBuilder | |
{ | |
return DB::table('comment_collections') | |
->leftJoin('platform_comments', 'comment_collections.id', '=', 'platform_comments.comment_collection_id') | |
->leftJoin('platforms', 'comment_collections.platform_id', '=', 'platforms.id') | |
->leftJoin('preferences', 'platform_comments.preference_id', '=', 'preferences.id') | |
->where('comment_collections.project_id', '=', $this->id) | |
->select( | |
'comment_collections.id', | |
DB::raw('COUNT(CASE WHEN platform_comments.parent_id IS NULL THEN 1 END) as comments_count'), | |
DB::raw('COUNT(CASE WHEN platform_comments.parent_id IS NOT NULL THEN 1 END) as replies_count'), | |
'source', | |
'title', | |
'likes_count', | |
'views_count', | |
'platforms.slug as platform', | |
'live_comments_enabled_at', | |
'scheduled_per_days', | |
'scheduled_per_hours', | |
'scheduled_per_minutes', | |
'stop_updating_at', | |
DB::raw('CASE WHEN stop_updating_at IS NOT NULL AND stop_updating_at < NOW() THEN true ELSE false END as should_stop_updating'), | |
'is_disabled', | |
DB::raw('COUNT(CASE WHEN preferences.sentiment_type = "positive" THEN 1 END) as positive_count'), | |
DB::raw('COUNT(CASE WHEN preferences.sentiment_type = "negative" THEN 1 END) as negative_count'), | |
DB::raw('COUNT(CASE WHEN preferences.sentiment_type = "neutral" THEN 1 END) as neutral_count'), | |
) | |
->groupBy('comment_collections.id'); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// Just some basic tests | |
namespace Tests\Support; | |
use Tests\TestCase; | |
class UserProfileTest extends TestCase | |
{ | |
private AuthUserFactory $users; | |
public function tearDown(): void | |
{ | |
$this->users->cleanup(); | |
parent::tearDown(); | |
} | |
public function setUp(): void | |
{ | |
parent::setUp(); | |
$this->users = new AuthUserFactory(); | |
} | |
public function test_user_can_view_their_own_profile(): void | |
{ | |
$this->be($this->users->regularUser); | |
$currentUserProfilePath = route( | |
name: 'profile', | |
parameters: [ | |
'user' => $this->users->regularUser | |
], | |
absolute: false | |
); | |
$response = $this->get($currentUserProfilePath); | |
$response->assertStatus(200); | |
} | |
public function test_user_can_view_other_users_profile_in_same_referral_community(): void | |
{ | |
$this->be($this->users->regularUser); | |
$viewOtherUserProfilePath = route( | |
name: 'user.profile', | |
parameters: [ | |
'user' => $this->users->regularUserTwo | |
], | |
absolute: false | |
); | |
$response = $this->get($viewOtherUserProfilePath); | |
$response->assertStatus(200); | |
} | |
public function test_user_cannot_view_external_referral_user(): void | |
{ | |
$this->be($this->users->regularUser); | |
$viewOtherUserProfilePath = route( | |
name: 'user.profile', | |
parameters: [ | |
'user' => $this->users->externalReferralRegularUser | |
], | |
absolute: false | |
); | |
$response = $this->get($viewOtherUserProfilePath); | |
$response->assertStatus(403); | |
} | |
public function test_super_admin_can_view_any_profile(): void | |
{ | |
// Test viewing internal referral community user profile | |
$this->be($this->users->superAdminUser); | |
$viewOtherUserProfilePath = route( | |
name: 'user.profile', | |
parameters: [ | |
'user' => $this->users->regularUser | |
], | |
absolute: false | |
); | |
$response = $this->get($viewOtherUserProfilePath); | |
$response->assertStatus(200); | |
// Test viewing external referral community user profile | |
$this->be($this->users->superAdminUser); | |
$viewOtherUserProfilePath = route( | |
name: 'user.profile', | |
parameters: [ | |
'user' => $this->users->externalReferralRegularUser | |
], | |
absolute: false | |
); | |
$response = $this->get($viewOtherUserProfilePath); | |
$response->assertStatus(200); | |
// Test viewing their own profile | |
$currentUserProfilePath = route( | |
name: 'profile', | |
parameters: [ | |
'user' => $this->users->superAdminUser | |
], | |
absolute: false | |
); | |
$response = $this->get($currentUserProfilePath); | |
$response->assertStatus(200); | |
} | |
public function test_admin_user_cannot_view_external_referral_profile() | |
{ | |
$this->be($this->users->adminUser); | |
$viewOtherUserProfilePath = route( | |
name: 'user.profile', | |
parameters: [ | |
'user' => $this->users->externalReferralRegularUser | |
], | |
absolute: false | |
); | |
$response = $this->get($viewOtherUserProfilePath); | |
$response->assertStatus(403); | |
} | |
public function test_admin_can_view_internal_referral_profile() | |
{ | |
$this->be($this->users->adminUser); | |
$viewOtherUserProfilePath = route( | |
name: 'user.profile', | |
parameters: [ | |
'user' => $this->users->regularUser | |
], | |
absolute: false | |
); | |
$response = $this->get($viewOtherUserProfilePath); | |
$response->assertStatus(200); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment