evernote/evernote-cloud-sdk-php
Official PHP SDK for the Evernote Cloud API, providing a clean, generated client to authenticate, read and write notes, notebooks, tags, and attachments. Speeds integration with Evernote's Thrift-based service for PHP apps.
This package is the official Evernote Cloud SDK: generated Thrift stubs for the EDAM API plus the Thrift transport/protocol classes. It is not an ORM or a high-level client — there is no Note::find(), no ENML parser, and typically no OAuth UI. Treat EDAMNoteStoreClient as your API reference; every method mirrors the EDAM Thrift IDL published at dev.evernote.com.
Install and get a token
composer require evernote/evernote-cloud-sdk-php
For a first spike, use a developer token from the sandbox (https://sandbox.evernote.com/api/DeveloperToken.action). Sandbox tokens only work against sandbox.evernote.com; production tokens only against www.evernote.com. Do not mix them.
Minimal bootstrap: resolve the user's note store, then list notebooks
require __DIR__ . '/vendor/autoload.php';
$token = getenv('EVERNOTE_TOKEN');
$host = 'sandbox.evernote.com';
$userStore = new EDAMUserStoreClient(
new TBinaryProtocol(new THttpClient($host, 443, '/edam/user', 'https'))
);
$noteStoreUrl = $userStore->getNoteStoreUrl($token);
$parts = parse_url($noteStoreUrl);
$noteStore = new EDAMNoteStoreClient(
new TBinaryProtocol(new THttpClient($parts['host'], 443, $parts['path'], 'https'))
);
foreach ($noteStore->listNotebooks($token) as $nb) {
printf("%s %s\n", $nb->guid, $nb->name);
}
First real use case: list notebooks, page through recent notes with findNotesMetadata, and only fetch full content for the one note the user opens. That keeps the first integration fast and avoids pulling resources you do not need.
If your bundled Thrift version does not accept the scheme as the fourth THttpClient argument, construct the transport and call setScheme('https'), or build the transport from the URL returned by getNoteStoreUrl which already contains the shard path.
Wrap the client in a small service, not in controllers. Every call takes the auth token as the first argument and most calls need the shard-specific note store URL. Centralize transport construction, timeouts ($transport->setTimeout(30)), token selection (sandbox vs production), and retry/backoff in one class. Refresh the note store URL when a request fails with a shard/user-store error rather than caching it forever.
Search metadata first, then hydrate lazily. findNotesMetadata is the workhorse for listings. The result spec defaults to almost everything false, so you must opt in:
$filter = new EDAMNoteFilter();
$filter->words = 'tag:inbox created:day-7 -tag:archived';
$filter->order = EDAMNoteSortOrder::UPDATED;
$filter->ascending = false;
$spec = new EDAMNotesMetadataResultSpec();
$spec->includeTitle = true;
$spec->includeUpdated = true;
$spec->includeNotebookGuid = true;
$spec->includeTagGuids = true;
$offset = 0;
do {
$page = $noteStore->findNotesMetadata($token, $filter, $offset, 100, $spec);
foreach ($page->notes as $meta) {
// $meta->guid, $meta->title, $meta->updated
}
$offset += count($page->notes);
} while ($offset < $page->totalNotes && count($page->notes) > 0);
Fetch the body only on demand with getNoteContent($token, $guid) (ENML only) or getNote($token, $guid, true, false, false, false) (content, no resource data). Use getResourceData($token, $resourceGuid) for attachments instead of downloading the whole note.
Create notes with valid ENML. Evernote rejects content that is not valid ENML: XML declaration, the ENML doctype, and a single <en-note> root.
$note = new EDAMNote();
$note->title = 'Build log';
$note->content =
'<?xml version="1.0" encoding="UTF-8"?>' .
'<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">' .
'<en-note><div>Deployed at ' . date('c') . '</div></en-note>';
$created = $noteStore->createNote($token, $note);
// use $created->guid — your input object is not the server's source of truth
Attachments: <en-media> plus a Resource. The media hash in ENML is the hex MD5 of the bytes; the resource's data->bodyHash is the binary MD5.
$png = file_get_contents('/tmp/chart.png');
$hash = md5($png, true); // binary, for Data.bodyHash
$data = new EDAMData();
$data->body = $png; // raw bytes; some legacy Thrift builds expect base64
$data->size = strlen($png);
$data->bodyHash = $hash;
$resource = new EDAMResource();
$resource->mime = 'image/png';
$resource->data = $data;
$note->content = str_replace('</en-note>',
'<en-media type="image/png" hash="' . md5($png) . '"/></en-note>', $note->content);
$note->resources = [$resource];
Updates: fetch, modify, save. Do not construct a partial EDAMNote with only a GUID and a new title — fetch the full note (including resources if it has them), mutate fields, then call updateNote. The server validates contentHash/contentLength, and a save with mismatched content can clear fields or fail with BAD_DATA_FORMAT.
Incremental sync for mirrors and indexes. Use getSyncState to read updateCount, then loop getFilteredSyncChunk until chunkHighUSN >= updateCount. Persist the high USN per account. EDAMSyncChunkFilter lets you exclude resources and content-classes you do not care about; full sync with resources is expensive, so seed with metadata only and hydrate resources on demand.
Shared and linked notebooks need a second auth hop. listLinkedNotebooks gives you shareKeys; authenticateToSharedNotebook returns an AuthenticationResult with a different token and note store URL. Use that token for all calls against the shared notebook. Business notes and shared notebooks are not returned by personal-note searches.
THttpClient, TBinaryProtocol, TTransportException, and EDAMNote are not PSR-4 namespaced in many releases. If another Composer dependency bundles Apache Thrift, class name collisions are real. Isolate Evernote calls in a separate process or guard with class_exists before autoloading.Authorization header and set it on THttpClient because the Thrift request body is not part of the OAuth signature for a fixed URL — verify against the current Evernote docs before shipping.getNoteStoreUrl; hardcoding https://www.evernote.com/shard/s1/notestore breaks for users on other shards.EDAMUserException (bad data, missing fields, permission), EDAMSystemException (service errors; errorCode === EDAMErrorCode::RATE_LIMIT_REACHED with rateLimitDuration in seconds — sleep and retry), EDAMNotFoundException (bad GUID), and TTransportException (network/timeouts). Catching only Exception will hide rate limits and cause retry storms.findNotesMetadata with an empty EDAMNotesMetadataResultSpec returns GUIDs and little else. This is the most common "why are my titles null" bug.Data.body is raw bytes and bodyHash is binary MD5; some older SDK bundles base64-encode body. Round-trip a small image through createNote/getResourceData early and assert the bytes match before you build attachment features.<en-media> for attachments; do not embed base64 data URIs. Convert HTML to ENML through a sanitizer rather than string concatenation.deleteNote is trash, expungeNote is permanent. Expunging is irreversible and has no confirmation. Keep destructive operations behind an explicit flag in your service class, and use NoteFilter->inactive = true if you need to see trashed notes.NoteAttributes (sourceApplication, sourceURL) or a tag.findNotesMetadata can fail or become slow. Prefer narrower search filters, incremental sync, or updateSequenceNum-based iteration over offset = 5000.THttpClient or wrap EDAMNoteStoreClient to log method name, duration, and EDAM error code. Log the EDAMSystemException->message and parameter, not just the class name.expungeNote/expungeNotebook, and make tests skip when EVERNOTE_TOKEN is absent. Never run integration tests against a production token.How can I help you explore Laravel packages today?