📦 popular_packages — Batch Query of Popular Composer Packages
This example demonstrates how to use the Composer Skills client to fetch metadata for multiple popular PHP packages in one go, along with repository statistics and security advisories, and finally persist everything to a JSON file.
🎯 Example Positioning
What this example teaches:
- 🏗️ How to create a
ComposerClientinstance with a timeout configuration - 📊 How to call
GetStatistics()to get the Packagist repository's total-scale metrics (downloads, package count, version count) - 🛡️ How to call
GetSecurityAdvisories()to fetch all security advisories as a security pre-check for package selection - 🔁 How to batch-call
GetPackage()in aforloop and gracefully handle individual package failures without aborting the whole flow - 💾 How to use the standard library
encoding/json+os.WriteFileto merge and serialize multi-source results into a local JSON file
The corresponding SDK methods all belong to the pkg/client package (ComposerClient, essentially a remote client for the Packagist API):
| Purpose | Method |
|---|---|
| Repository statistics | GetStatistics() |
| Security advisories | GetSecurityAdvisories() |
| Single-package details | GetPackage(packageName string) |
💻 Full Code
go
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
"github.com/scagogogo/composer-skills/pkg/client"
)
// Some popular Composer packages
var popularPackages = []string{
"symfony/symfony",
"laravel/framework",
"guzzlehttp/guzzle",
"monolog/monolog",
"phpunit/phpunit",
}
func main() {
// Create a Composer client
composerClient := client.NewComposerClient(30 * time.Second)
// Create a map to hold the results
results := make(map[string]interface{})
// Get statistics
fmt.Println("Fetching Composer repository statistics...")
stats, err := composerClient.GetStatistics()
if err != nil {
log.Fatalf("Failed to fetch statistics: %v", err)
}
results["statistics"] = stats
fmt.Printf("Total downloads: %d, package count: %d, version count: %d\n",
stats.Totals.Downloads, stats.Totals.Packages, stats.Totals.Versions)
// Get security advisories
fmt.Println("\nFetching security advisories...")
advisories, err := composerClient.GetSecurityAdvisories()
if err != nil {
log.Fatalf("Failed to fetch security advisories: %v", err)
}
results["advisories"] = advisories
fmt.Printf("Fetched security advisories for %d packages\n", len(advisories.Advisories))
// Get info for popular packages
fmt.Println("\nFetching info for popular packages...")
packageInfos := make(map[string]interface{})
for _, pkgName := range popularPackages {
fmt.Printf(" Fetching info for %s...\n", pkgName)
pkgInfo, err := composerClient.GetPackage(pkgName)
if err != nil {
// Skip a single package failure without aborting the whole batch
fmt.Printf(" Failed to fetch info for %s: %v\n", pkgName, err)
continue
}
packageInfos[pkgName] = pkgInfo
// Display some basic info
p := pkgInfo.Package
fmt.Printf(" Name: %s\n", p.Name)
fmt.Printf(" Description: %s\n", p.Description)
fmt.Printf(" Type: %s\n", p.Type)
fmt.Printf(" Downloads: %d\n", p.Downloads.Total)
fmt.Printf(" Version count: %d\n", len(p.Versions))
fmt.Printf(" GitHub Stars: %d\n", p.GithubStars)
fmt.Println()
}
results["packages"] = packageInfos
// Save all results to a file
outputFile := "popular_packages_results.json"
jsonData, err := json.MarshalIndent(results, "", " ")
if err != nil {
log.Fatalf("Failed to serialize results: %v", err)
}
err = os.WriteFile(outputFile, jsonData, 0644)
if err != nil {
log.Fatalf("Failed to save results to file: %v", err)
}
fmt.Printf("\nResults saved to %s\n", outputFile)
}🧩 Code Walkthrough
- 🏗️ Creating the client:
client.NewComposerClient(30 * time.Second)passes a 30-second HTTP timeout, enough to cover the response time of some large Packagist packages (e.g.symfony/symfonywith hundreds of versions). The client is reusable — no need to recreate it per request. - 🗂️ Results container:
results := make(map[string]interface{})uses an aggregate map to hold three kinds of heterogeneous data — statistics, advisories, and package details — together for one-shot serialization at the end. - 📊 Fetch repository overview:
GetStatistics()returns*domain.StatisticsResponse; read the totals directly fromstats.Totals.Downloads / Packages / Versionsto get a macro view of the repository's scale before pulling individual packages. - 🛡️ Fetch security advisories:
GetSecurityAdvisories()returns all advisories — note that the volume can be large. The example only takeslen(advisories.Advisories)for a count display; in real projects, filter by package name before using it. - 🔁 Batch package fetching: iterate the
popularPackagesslice and callGetPackage(pkgName)one by one. The key point is that error handling usescontinuerather thanlog.Fatalf— a single package request failure (network jitter, package delisting) should not waste the entire batch. - 🏷️ Reading fields:
pkgInfo.Packageis the details body;p.Downloads.Total,p.GithubStars, andlen(p.Versions)are the metrics you care about most during selection, and you can take them straight into a comparison report. - 💾 Persisting JSON:
json.MarshalIndentserializes with indentation, andos.WriteFilewritespopular_packages_results.json, convenient for downstream analysis withjqor scripts.
▶️ How to Run
Run from the example directory (requires internet access to the Packagist API):
bash
cd examples/popular_packages
go run main.goAfter it finishes, the current directory will contain popular_packages_results.json with three sections: statistics, advisories, and per-package details. Since the example makes real API requests, avoid running it at high frequency to avoid burdening Packagist.
🔗 SDK Methods Involved
| Method | Package | Docs |
|---|---|---|
GetStatistics() | pkg/client (ComposerClient) | /sdk/packagist/methods/get-statistics |
GetSecurityAdvisories() | pkg/client (ComposerClient) | /sdk/packagist/methods/get-security-advisories |
GetPackage(packageName string) | pkg/client (ComposerClient) | /sdk/packagist/methods/get-package |
NewComposerClient(timeout time.Duration) | pkg/client | Client constructor; see the client package docs |
🚀 Going Further
- ⚡ Concurrent fetching: turn the
forloop intoerrgroup+ goroutines, with a semaphore rate limiter (e.g. 5 concurrent) for eachGetPackage. Five packages can drop from serial ~5x latency to 1x. - 🔍 Targeted advisories: replace the full
GetSecurityAdvisories()withGetSecurityAdvisoriesForPackages(popularPackages)to fetch advisories only for the packages you actually care about — the response body shrinks dramatically. - 📈 Incremental updates: use
GetSecurityAdvisoriesSince(updatedSince time.Time)instead of the full pull, paired with a local timestamp for incremental sync — suitable for long-running security monitoring. - 📊 Metric comparison: divide
p.Downloads.Totalbystats.Totals.Downloadsto get "this package's share of all repository downloads" and generate a popularity ranking table. - 💾 Structured storage: replace
os.WriteFilewith writes to SQLite or CSV, and add a--sinceparameter to export only packages updated in the last N days — turning it into a reusable CLI tool.