Developers/Library/Managing items

From Aimeos documentation

< Developers
Revision as of 14:26, 28 October 2019 by Aimeos (talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

<languages/>


2019.x version

<translate> In Aimeos, all data like products, categories, texts, prices, attributes, etc. is managed by a persistence layer located in the "./lib/mshoplib/" directory of the Aimeos core. Using managers and items which offer a common interface to the stored data allows accessing the data regardless of where and how it is stored. Thus, it doesn't matter if all data or parts of it is stored in a relational or document-oriented database or if it can be access via SQL or with a different query language.

Data domains

Each kind of data is stored in one "domain", a term is from the domain driven design method. A domain contains the data and code for a common context, like the catalog domain with consists of the categories and related data including the managers which know how to store, manipulate and retrieve those data.

These domains are part of the Aimeos core:

  • attribute (shared attributes between entries)
  • cache (temporary cached content)
  • catalog (category tree and associated data)
  • coupon (reductions and vouchers)
  • customer (customer data and addresses)
  • index (product index for fast lookup)
  • job (jobs created by the admin interface)
  • locale (sites, languages and currencies)
  • log (message logging)
  • media (images, files, documents)
  • order (orders created by customers)
  • plugin (event-driven basket changes)
  • price (for items of other domains)
  • product (basic product and related data)
  • service (delivery and payment options)
  • stock (product stock levels)
  • subscription (repeating customer subscriptions)
  • supplier (information about product suppliers)
  • text (for items of other domains)

Items

Data from a domain is stored in one or more items, objects mainly with getter and setter methods but without much own logic. They are container for transferring data between layers, e.g to the clients or from the controllers back to the storage.

The available properties of each item class depend on the domain and are different for each item. A few properties are part of all domains, which include:

  • id (Unique ID of the entry)
  • siteid (for multi-tenancy support)
  • ctime (creation date/time)
  • mtime (modification date/time)
  • editor (last editor of the entry)

For more information, please have a look into the available getter and setter methods for the domain items you are looking for, e.g. the product item.

Managers

There's exactly one manager for each item type, so a product item has its own manager as well as a product property item has one. All managers know how to create, store, retrieve and delete "their" items and only those. They don't care about items of other domains or of sub-managers of their domain.

If you need a shop related manager to fetch or store items, you can use the factory class to get an instance of the manager:

  1. $manager = \Aimeos\MShop::create( $context, 'product' );
  2. $manager = \Aimeos\MShop::create( $context, 'product/lists' );
  3. $manager = \Aimeos\MShop::create( $context, 'product/property' );

The second argument is the path of the manager. It's in lower case and e.g. "product" for the product manager or "product/property" for property sub-manager in the product domain. The same principle applies to the other domains. For the admin related managers use

  1. $manager = \Aimeos\MAdmin::create( $context, 'job' );
  2. $manager = \Aimeos\MAdmin::create( $context, 'log' );
  3. $manager = \Aimeos\MAdmin::create( $context, 'cache' );

The necessary context object is available in all controllers and clients via $this->getContext() but this doesn't apply to views!

The "cache" and "log" managers are only necessary to have full access to their items. If you only want to log messages or cache some content, you should use the objects from the context instead!

Create item

Creating a new empty item is the easiest part. Simply call createItem() of the manager you need a new item from:

  1. $manager->createItem();

This creates a new object with some default values for the item set. It doesn't have an unique ID and is only available in memory.

Don't create an item (like every other object) with the "new" operator! Depending on the implementation of the manager, different items implementing the same interface can be returned.

Save item

After you've filled the item with data, you can ask the manager to persist it in the storage with a call to the saveItem() method:

  1. $manager->saveItem( $item );

The manager will try to store the data, retrieve the unique ID for the item and updates the ID property of the item. The parameter must be an item object returned by createItem() of the same manager. Passing an item object from another manager will throw an exception.

There's a second parameter available which can be used to optimize performance if you don't need the unique ID afterwards. If you pass false as second parameter, the ID property won't be updated.

Get item

You can retrieve a single item if you know its unique ID. The getItem() method returns exactly one item if the manager found one for the given ID:

  1. $item = $manager->getItem( $id );
  2. $item = $manager->getItem( $id, ['text', 'media', 'price'] );
  3. $item = $manager->getItem( $id, ['attribute' => ['default', 'variant']] );

When none was found, an exception is thrown.

There's a second parameter to fetch items from associated domains too. This can be either a list of domains to fetch all items of that domains or the domain name as key and the list types limiting the fetched items as values.

It's available for all domain items where items from other domains can be associated to them via a "list" table.

Domains with a list table are:

  • attribute
  • catalog
  • customer
  • media
  • price
  • product
  • service
  • supplier
  • text

Items from each domain passed in the second argument will be part of the returned item. You can access these associated items via the getListItems() and getRefItems() methods, e.g.

  1. $textref = $item->getListItems( 'text', 'default' );
  2. $texts = $item->getRefItems( 'text', 'name', 'default' );

The second parameter for getListItems() and the third parameter for getRefItems() defines the type of association between the domain items.

You can also retrieve items stored in the same domain, e.g. product properties or ordered products via the second parameter:

  1. $item = $productManager->getItem( $id, ['product/property'] );
  2. $item = $orderBaseManager->getItem( $id, ['order/base/address', 'order/base/product'] );

Find item

Single items which can be identified by their code or a combination of the code, domain and type can be retrieved by the findItem() method if the manager offers this method:

  1. $item = $manager->findItem( $code );
  2. $item = $manager->findItem( $code, array( 'text' ), $domain, $type );

The first call will return the item for the given code, provided the code alone is unique. This works for categories, coupon codes, customers, customer groups, locale sites, products, product warehouses and suppliers. If the code isn't unique enough, you will need to supply domain and type as well. Domain items that can be fetched this way are attributes and services and all kind of type items.

The second argument for retrieving the associated items too is the same as described for getItem().

Search items

Several items can be retrieved at once via the the searchItems() method. It enables you to specify criteria to exactly get the items you need. To create a required criteria object, use createSearch():

  1. $search = $manager->createSearch();
  2. $search = $manager->createSearch( true );

The method will add default criteria (e.g. the status of entries must be "1") to the new object if true is passed as argument. This search object must then be passed to the searchItems() method as first parameter:

  1. $total = 0;
  2. $items = $manager->searchItems( $search );
  3. $items = $manager->searchItems( $search, ['text'] );
  4. $items = $manager->searchItems( $search, ['attribute' => ['variant']] );
  5. $items = $manager->searchItems( $search, [], $total );

The second argument lists the name of the associated domains whose items should be fetched too. You can either get all referenced items of the given domain or limit the items by the list type(s) of the reference. This is the same as described in the getItem() method.

The last argument is a value/result parameter which will contain the total number of items matching the search criteria. By default, only the first 100 items are returned.

Conditions

Criteria consists of three parts: The search key, the operator and the value. You can set a new criteria using:

  1. $expr = $search->compare( '==', 'product.code', 'test' );
  2. $search->setConditions( $expr );

Available operators are:

  • == (equals)
  •  != (not equals)
  • < (less than)
  • <= (less than or equal)
  • > (greater than)
  • >= (greater than or equal)
  • =~ (string starts with)
  • ~= (string contains, slow!)

The available search keys depend on the used manager and the item properties. Each manager defines its search keys in its class like the product manager. If the list of search keys depend on the implementation or is dynamic, you can retrieve the available search keys by using getSearchAttribute().

Values for criteria can be more than single values. You can hand over a list of values as well:

  1. $expr = $search->compare( '==', 'product.id', array( 1, 5, 7 ) );
  2. $search->setConditions( $expr );

It's also possible to specify several criteria as condition to filter the returned items according to them:

  1. $expr = array(
  2.     $search->compare( '==', 'product.code', 'test' ),
  3.     $search->getConditions(),
  4. );
  5. $search->setConditions( $search->combine( '&&', $expr ) );

These conditions would search for a product item with "test" as code and the default conditions for product items (provided you've used true as argument for createSearch()).

You can combine a list of conditions in several ways:

  • && (AND combination)
  • || (OR combination)
  •  ! (NOT, only for single conditions)

The "!" (NOT operator) is special in this case because its only valid if the array contains a single condition. But you can use the "&&" or "||" operators to combine several conditions to one before using the "!" operator:

  1. $expr = array(
  2.     $search->compare( '==', 'product.code', 'test' ),
  3.     $search->compare( '>', 'product.datestart', '2000-01-01 00:00:00' ),
  4. );
  5. $orCondition = $search->combine( '||', $expr );
  6. $search->setConditions( $search->combine( '!', array( $orCondition )  ) );

If you want to search for items that has referenced data via the list tables or properties, there are two search functions :has and :prop available for each domain, i.e for the product domains their names are product:has and product:prop.

  1. $hasfcn = $search->createFunction( 'product:has', ['attribute', 'default', 123] );
  2. $propfcn = $search->createFunction( 'product:prop', ['ISBN', null, 'abc'] );
  3. $expr = array(
  4.     $search->compare( '==', 'product.type.code', 'select' ),
  5.     $search->compare( '!=', $hasfcn, null ),
  6.     $search->compare( '>', $propfcn, null ),
  7. );
  8. $search->setConditions( $search->combine( '&&', $expr  ) );

These criteria would return all product items that are selection products, have a referenced attribute with list type default and attribute ID 123' and a property of type "ISBN", are not language specific and with the value "abc".

Search functions

There's one further type of conditions named "search functions". They offer complex queries for searching items by a syntax which is easier to use - just like PHP functions hiding complex tasks. You can identify a search function by the round parenthesis at the end of their code, e.g. "product:has()" offered by the product manager. Search functions need one or more parameter which are described in the label of the criteria attribute. For example:

Product list item, parameter(<domain>,<list type>,<reference ID>)

To use a search function and pass the required parameters, there's a little helper named createFunction() available in the search object:

  1. $func = $search->createFunction( 'product:has', ['attribute', 'default', '123'] );
  2. $search->setConditions( $search->compare( '!=', $func, null ) );

This condition would search for all products which has the attribute identified by its ID "123" and the list type "default". The second parameter of createFunction() consists of:

  •  :has : [<domain>, <list type>, <referenced ID>]
  •  :prop : [<property type>, <language ID>, <property value>]

The second and the third array element (<list type> and <referenced ID> as well as <language ID> and <property value>) are optional.

The PHP type of the parameters used in the second argument must be exactly as expected, e.g. if an integer value is required, passing a float value may fail or lead to strange behavior.

Sorting

Each search key can be used for sorting the result set and several search keys at once are allowed too:

  1. $sort = array(
  2.     $search->sort( '+', 'product.code' ),
  3.     $search->sort( '-', 'product.status' ),
  4. );
  5. $search->setSortations( $sort  );

The first parameter the sort() method accepts is the direction of the sorting. Available values are:

  • + (ascending order)
  • - (descending order)

Please make sure an appropriate index is available before using the search key for sorting the result. Otherwise, retrieving the items will be extremely slow!

Paging

If your shop contains more than a few items, paging comes into play. By default, only the first 100 found items will be returned by searchItems(). To retrieve items beyond or with a different slice size, you should use the setSlice() method of the search object:

  1. $search->setSlice( 100, 50  );
  2. $search->setSortations( array( $search->sort( '+', 'product.id' ) )  );

The first argument is the starting point, the second one the slice size when fetching items. The parameters in the example would retrieve the product items sorted by their ID from position 100 to 150.

You should always sort the result set if you need paging. Otherwise, the database will use its default order and this can change between queries!

Delete items

The last action in the life cycle of an item is to delete it from the storage if it's not needed any more. Managers offer the two methods deleteItem() and deleteItems() for removing entries:

  1. $manager->deleteItem( $id );
  2. $manager->deleteItems( array( 2, 5, 9 )  );

Items can only by deleted by using their unique ID. The method deleteItem() can remove a single entry while deleteItems() can do this for several items at once, which is preferable to reduce the number of executed statements.

</translate>