Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Plist Laravel Package

rodneyrehm/plist

PHP library for reading and writing Apple property list (plist) files in both XML and binary formats. Convert plists to PHP arrays and back with a lightweight, straightforward API suited to macOS and iOS tooling, configuration, and data interchange.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the library directly — it has no Laravel service provider, no config file, and no facade. Everything is plain PHP objects you wire up yourself.

composer require rodneyrehm/plist

If you need the actively maintained fork (Moodle's, which backs the 2025 release), it is published under a different Composer name — pin it explicitly so you know which code you're shipping:

"repositories": [{ "type": "vcs", "url": "https://github.com/moodlehq/CFPropertyList" }],
"require": { "rodneyrehm/plist": "dev-master" }

Where to look first: src/CFPropertyList/CFPropertyList.php for the five methods that matter (parse, load, toArray, toXML/toBinary, save), then the type classes (CFDictionary, CFArray, CFString, CFInteger, CFReal, CFBoolean, CFData, CFDate, CFUid). The test suite has binary/XML fixtures you can copy into tests/fixtures.

Smoke test — read a plist and prove you can round-trip it:

use CFPropertyList\CFPropertyList;

$plist = new CFPropertyList();
$plist->parse(file_get_contents(base_path('tests/fixtures/Info.plist')));
$data = $plist->toArray();          // plain PHP array
dump($data, $plist->getFormat());   // CFPropertyList::FORMAT_XML | FORMAT_BINARY

Your first real use case is almost certainly generating .mobileconfig profiles for iOS/macOS (Wi-Fi, VPN, email, MDM enrollment) or bumping CFBundleVersion in an iOS Info.plist during CI. Both are five-line jobs once the parse/write loop clicks — start with a Wi-Fi payload and open it on a device, since that validates both structure and content type end-to-end.

Implementation Patterns

Wrap it in one service class. You do not want new CFDictionary() scattered across controllers. Bind a single class (e.g. App\Support\Plist\PlistWriter) in a service provider and inject it.

Writing: build the CFType tree, not strings. There is no fromArray() inverse, so the highest-leverage thing you'll write is a recursive converter:

use CFPropertyList\{CFType, CFDictionary, CFArray, CFString, CFInteger, CFReal, CFBoolean, CFData, CFDate};

function toCFType(mixed $value): CFType
{
    return match (true) {
        $value instanceof CFType        => $value,
        is_bool($value)                 => new CFBoolean($value),
        is_int($value)                  => new CFInteger($value),
        is_float($value)                => new CFReal($value),
        $value instanceof \DateTimeInterface => new CFDate($value),
        is_array($value) && array_is_list($value) => new CFArray(array_map('toCFType', $value)),
        is_array($value)                => tap(new CFDictionary(), function ($d) use ($value) {
            foreach ($value as $k => $v) { $d->add((string) $k, toCFType($v)); }
        }),
        default                         => new CFString((string) $value),
    };
}

$plist = new CFPropertyList();
$plist->add(toCFType(['PayloadType' => 'Configuration', 'PayloadVersion' => new CFInteger(1)]));
$xml = $plist->toXML(true);         // pretty-printed string, no filesystem touch

Prefer toXML()/toBinary() + Laravel Storage over the library's saveXML($path)/save($path). The library's save methods use raw fopen/file_put_contents, which bypasses your disks, S3 driver, and test fakes:

Storage::disk('profiles')->put("{$uuid}.mobileconfig", $plist->toXML());

Serving a profile over HTTP — iOS is picky about the response:

return response($plist->toXML(), 200, [
    'Content-Type'        => 'application/x-apple-aspen-config',
    'Content-Disposition' => 'attachment; filename="wifi.mobileconfig"',
]);

CI / artisan pattern — an ios:bump-build command that loads Info.plist, mutates CFBundleVersion, writes both XML and binary copies, and exits non-zero on failure. This is where the package pays for itself: no plutil, no macOS runner required.

Parsing user uploads — validate before parsing. Check the extension/mime, then guessFormat() on the raw content and reject anything that throws:

try { $plist->parse($request->file('profile')->get()); }
catch (\CFPropertyList\PListException $e) { throw ValidationException::withMessages([...]); }

Test with fixtures, assert structurally. Keep small XML and binary plists in tests/fixtures/ and assert on toArray() keys plus assertStringStartsWith('bplist00', $binary) for binary output. Round-trip tests (parse → write → parse) catch almost every serialization bug you'll hit.

Gotchas and Tips

  • Empty arrays are ambiguous. [] could be <array/> or <dict/>. Decide explicitly in your converter (or pass CFArray/CFDictionary instances through) or you'll ship profiles Apple silently ignores.
  • CFData wants raw binary, not base64. The XML writer base64-encodes for you. Passing pre-encoded data double-encodes it — a classic certificate-payload bug that only shows up on device.
  • Dates are UTC-sensitive. Plist dates are absolute; if your CFDate is built from a local-timezone DateTime, the device sees a shifted value. Normalize with ->setTimezone(new \DateTimeZone('UTC')) before wrapping.
  • Booleans must be CFBoolean. 1/0 becomes <integer> and iOS treats it as truthy in some keys but not others. Your converter's is_bool branch matters more than it looks.
  • Whole-number floats. 1.0 passed as a PHP float must become CFReal to survive as <real>; PHP's loose typing will otherwise collapse it to CFInteger.
  • Signing is not in this library. It builds the payload only. A signed .mobileconfig needs CMS signing (openssl cms -sign -nodetach) done outside PHP — run it in a queued job with Process, never inline in a request.
  • toArray() date values. Don't assume scalars — dates typically surface as DateTime objects. dd() one real profile before writing assertions.
  • CFUid is required for NSKeyedArchiver blobs (some Apple caches/preferences). toArray() won't necessarily round-trip UIDs, so don't use the array view as your serializer for those.
  • Errors are exceptions, not return codes: CFPropertyList\PListException and IOException. Catch them at the boundary and translate — a raw stack trace leaks absolute paths and looks like a crash to the user.
  • Format detection has no safety net. guessFormat() on empty/whitespace content is unreliable; check strlen($content) > 8 (the bplist00 header) before parsing uploads.
  • Strip BOMs and enforce UTF-8. XML plists with a stray BOM parse fine on desktop tools but fail on strict readers.
  • Memory: the whole file is read into a string and expanded into objects. Fine for config files, wrong for large archives — stream or chunk if you ever hit megabyte-scale plists.
  • Cache generated output. Profile generation is cheap but not free; memoize per-tenant payloads (keyed by config hash) rather than rebuilding per request.
  • Maintenance risk is real. Single original maintainer, tiny dependent count. Pin a version, vendor the fork if you need one, and keep your wrapper thin enough to swap — the public surface you actually use is roughly six methods.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings