beta / bx.model
Requires
- php: >=7.2
Requires (Dev)
- phpunit/phpunit: ^9.5
- dev-master
- 1.26.0
- 1.25.4
- 1.25.3
- 1.25.2
- 1.25.1
- 1.25.0
- 1.24.5
- 1.24.2
- 1.24.1
- 1.24.0
- 1.23.0
- 1.22.0
- 1.21.1
- 1.21.0
- 1.20.2
- 1.20.1
- 1.20.0
- 1.19.0
- 1.18.2
- 1.18.1
- 1.6.1
- 1.6.0
- 1.5.0
- 1.4.0
- 1.3.6
- 1.3.5
- 1.3.4
- 1.3.3
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.1
- 1.2.0
- 1.1.4
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.0
- 0.17.1
- 0.17.0
- 0.16.0
- 0.15.0
- 0.14.2
- 0.14.1
- 0.14.0
- 0.13.1
- 0.13.0
- 0.12.0
- 0.11.1
- 0.11.0
- 0.10.1
- 0.10.0
- 0.9.0
- 0.8.0
- 0.7.0
- 0.6.0
- 0.5.1
- 0.5.0
- 0.4.0
- 0.3.0
- 0.2.1
- 0.2.0
- 0.1.0
This package is auto-updated.
Last update: 2024-09-13 07:47:25 UTC
README
安装
composer require beta/bx.model
模型
模型反映了项目中使用的实体:信息块元素、hl块元素或任意表的记录。它们是形式化的数据,并提供了一个方便操作数据的接口。
每个模型都必须实现ModelInterface接口,该接口继承自ArrayAccess和IteratorAggregate接口,这意味着可以像操作关联数组一样操作模型。同时继承了CollectionItemInterface接口,该接口要求实现方法
- assertValueByKey(string $key, $value): bool - 检查键对应的值是否与传递的值匹配
- hasValueKey(string $key): bool - 检查是否存在键的值
- getValueByKey(string $key) - 返回键的值
在此模块中,存在对模型接口AbsOptimizedModel的不完整实现,预计将其作为创建模型的基础。
模型描述示例
use Bx\Model\AbsOptimizedModel; class CatalogProduct extends AbsOptimizedModel { protected function toArray(): array { return [ 'id' => $this->getId(), 'name' => $this->getName(), ]; } public function getId(): int { return (int)$this['ID']; } public function setId(int $id) { $this['ID'] = $id; } public function getName(): string { return (string)$this['NAME']; } public function setName(string $name) { $this['NAME'] = $name; } }
模型操作示例
$modelData = [ 'ID' => 11, 'NAME' => 'Some product name', ]; $product = new CatalogProduct($modelData); $product->getId(); // 11 $product->getName(); // 'Some product name' $product->setName('New product name'); $product['ID']; // 11 $product['NAME']; // 'New product name' $product['NAME'] = 'One more product name'; $product->hasValueKey('ID'); // true $product->getValueByKey('ID'); // 11 $product->assertValueByKey('ID', 11); // true $product->assertValueByKey('ID', 12); // false /** * Результат: * ID - 11 * NAME - New product name */ foreach($product as $key => $value) { echo "{$key} - {$value}\n"; } $product->getApiModel(); // ['id' => 1, 'name' => 'One more product name'] json_encode($product); // '{"id": 1, "name": "One more product name"}'
聚合模型
在某些情况下,需要根据某些数据集提供形式化的数据。例如,在一个模型中输出特定商品的总价信息:最大/最小价格、平均值等。为此,定义了AggregateModelInterface接口。同样,也存在对此接口的不完整实现 - BaseAggregateModel。
聚合模型描述示例
use Bx\Model\BaseAggregateModel; class AggregatePrice extends BaseAggregateModel { protected function toArray(): array { return [ 'min' => $this->getMin(), 'max' => $this->getMax(), 'actual' => $this->getActual(), 'description' => $this->getDescription(), ]; } public function getMin(): ?Price { $min = null; foreach($this->getCollection() as $price) { if ($min === null) { $min = $price; continue; } if ($min->getValue() > $price->getValue()) { $min = $price; } } return $min; } public function getMax(): ?Price { $max = null; foreach($this->getCollection() as $price) { if ($max === null) { $max = $price; continue; } if ($max->getValue() < $price->getValue()) { $max = $price; } } return $max; } public function getActual(): ?Price { foreach($this->getCollection() as $price) { if (/** Некоторая логика **/) { return $price; } } return null; } public function getDescription(): string { return (string)$this['description']; } public function setDescription(string $description) { $this['description'] = $description; } }
聚合模型操作示例
use Bx\Model\ModelCollection; $price1 = [ 'value' => 1000, 'currency' => 'RUB', ]; $price2 = [ 'value' => 2000, 'currency' => 'RUB', ]; $price3 = [ 'value' => 5000, 'currency' => 'RUB', ]; $priceCollection = new ModelCollection([ $price1, $price2, $price3, ], Price::class); $aggregatePrice = new AggregatePrice($priceCollection, [ 'description' => 'some description', ]); $aggregatePrice->getApiModel(); // ['min' => ['value' => 1000, 'currency' => 'RUB'], 'max' => ['value' => 5000, 'currency' => 'RUB'], 'actual' => null, 'description' => 'some description']
集合
为了处理同类型数据集,创建了另一个实体 - 集合。集合为处理此类数据集提供方便的接口,包括:过滤、搜索、向集合添加/删除元素、按键选择值...
要实现集合,需要实现CollectionInterface接口或ReadableCollectionInterface接口,如果预计元素集不会更改。集合本身可以包含任何实现CollectionItemInterface接口的对象。在此模块中已经有一个完整的集合实现 - Collection。
集合操作示例
use Bx\Model\Collection; $priceItem1 = new Price([ 'value' => 1000, 'currency' => 'RUB', 'group' = 1, ]); $priceItem2 = new Price([ 'value' => 2000, 'currency' => 'RUB', 'group' = 1, ]); $priceItem3 = new Price([ 'value' => 4000, 'currency' => 'RUB', 'group' = 2, ]); $collection = new Collection( $priceItem1, $priceItem2, $priceItem3 ); $collection->findByKey('value', 2000); // $priceItem2 $collection->find(function(Price $price) { // $priceItem1 return $price->getValueByKey('value') === 1000 && $price->getValueByKey('currency') === 'RUB' }); $collection->filterByKey('group', 1); // вернет новую коллекцию состоящую из $priceItem1 и $priceItem2 $collection->filter(function(Price $price) { // вернет новую коллекцию состоящую из $priceItem2 и $priceItem3 return $price->getValueByKey('value') > 1000 && $price->getValueByKey('currency') === 'RUB' }); $collection->column('value'); // [1000, 2000, 4000] $collection->unique('currency'); // ['RUB'] $collection->remove($priceItem2); // удаляем элемент $priceItem2 из коллекции $collection->append(new Price([ // добавляем новый элемент в коллекцию 'value' => 7000, 'currency' => 'RUB', 'group' => 2, ])); $collection->first(); // $priceItem1 $collection->count(); // 3 count($collection); // 3 json_encode($collection); // JSON представление коллекции $collection->jsonSerialize(); // вернет ассоциативный массив
模型集合
在模块中有一个针对模型的集合实现 ModelCollection,该集合的数据可以包含任何实现ModelInterface接口的对象。
使用示例
use Bx\Model\ModelCollection; $productData1 =[ 'ID' => 11, 'NAME' => 'Product name 11', ]; $productData2 = [ 'ID' => 21, 'NAME' => 'Product name 21', ]; $product3 = new CatalogProduct([ 'ID' => 31, 'NAME' => 'Product name 31', ]); $productCollection = new ModelCollection([ $productData1, $productData2, $product3 ], CatalogProduct::class); $productCollection->addModel(new CatalogProduct([ 'ID' => 41, 'NAME' => 'Product name 41', ])); $productCollection->add([ 'ID' => 51, 'NAME' => 'Product name 51', ]);
MappedCollection & MappedCollectionCallback
为了方便,创建了派生集合,其中可以指定对集合元素的任意键访问。
- MappedCollection - 接受构造函数中的任意集合,并创建基于指定键的访问映射。
- MappedCollectionCallback - 接受构造函数中的任意集合,并创建基于可计算键的访问映射。
使用示例
use Bx\Model\MappedCollection; use Bx\Model\MappedCollectionCallback; $mappedCollection = new MappedCollection($productCollection, 'ID'); $mappedCollection[41]->getName(); // Product name 41 $mappedCollectionCallback = new MappedCollectionCallback( $productCollection, function(CatalogProduct $product){ return 'product_'.$product->getId(); } ); $mappedCollectionCallback['product_41']->getName(); // Product name 41
服务
为了与数据库集成,使用服务 - ModelServiceInterface,这些服务实现基本操作
- 查询实体列表(根据特定标准) - 模型列表或集合
- 查询特定实体 - 模型
- 添加/更新实体 - 模型
- 删除实体 - 模型
要实现模型服务,需要实现ModelServiceInterface接口,该接口包含以下
- QueryableModelServiceInterface
- ReadableModelServiceInterface - 操作:getList, getCount 和 getById
- FilterableInterface - 需要指定过滤规则
- SortableInterface - 需要指定排序规则
- LimiterInterface - 需要指定选择限制规则
- SaveableModelServiceInterface - 保存/更新方法
- RemoveableModelServiceInterface - 删除方法
模块中存在基于BaseModelService创建自定义服务的不完整服务实现。
服务描述示例
use Bx\Modle\BaseModelService; use Bx\Modle\ModelCollection; class CatalogProductService extends BaseModelService { protected function getFilterFields(): array { return [ // указываем разрешенные для фильтрации поля ]; } protected function getSortFields(): array { return [ // указываем разрешенные для сортировки поля ]; } public function getList(array $params, UserContextInterface $userContext = null): ModelCollection { $list = CatalogProductTable::getList($params)->fetchAll(); return new ModelCollection($list, CatalogProduct::class); } public function getCount(array $params, UserContextInterface $userContext = null): int { $params['select'] = ['ID']; $params['count_total'] = true; $params['limit'] = 1; return $this->getList($params, $userContext)->first(); } public function getById(int $id, UserContextInterface $userContext = null): ?AbsOptimizedModel; { $params = [ 'filter' => [ '=ID' => $id, ], 'limit' => 1, ]; return $this->getList($params, $userContext)->first(); } function save(AbsOptimizedModel $model, UserContextInterface $userContext = null): Result { $data = [ 'NAME' => $model->getName(), ]; if ($model->getId() > 0) { return CatalogProductTable::update($model->getId(), $data); } $result = CatalogProductTable::add($data); if ($result->isSuccess()) { $model['ID'] = $result->getId(); } return $result; } function delete(int $id, UserContextInterface $userContext = null): Result { return CatalogProductTable::delete($id); } }
服务使用示例
$catalogProductService = new CatalogProductService(); $productCollection = $catalogProductService->getList([ 'filter' => [ '=ID' => [1, 5, 10], ], 'select' => [ 'ID', 'NAME', ], 'order' => [ 'NAME' => 'desc', ], 'limit' => 5, ]); $productCollection->unique('NAME'); $productCollection->column('ID'); $product = $productCollection->findByKey('ID', 5); $product->setName('New product name'); $resultSave = $catalogProductService->save($product); $resultDelete = $catalogProductService->delete(10);
查询
模块中包含从数据库查询参数的对象模型,由QueryInterface接口描述,同时提供了该接口的完整实现 - Query。此类对象可以包含参数:select、filter、order、group、limit、offset。存在派生接口ModelQueryInterface,模块中提供了该接口的完整实现 - QueryModel,该接口直接与模型服务交互,通过构造函数传入服务,并通过双重代理实现数据选择,提供了额外的模型选择方法。
- loadFiler - 根据传入的关联数组形成过滤器,遵循ModelServiceInterface->getFilterFields()中描述的服务过滤规则。
- loadSort - 根据传入的关联数组形成数据排序参数,遵循ModelServiceInterface->getSortFields()中描述的服务排序规则。
- loadPagination - 根据传入的数组形成分页参数,使用键:limit和page。
- getPagination - 返回PaginationInterface分页对象。
- getList - 根据形成的查询参数返回模型集合。
使用示例
use Bitrix\Main\Application; $catalogProductService = new CatalogProductService(); $request = Application::getInstance()->getContext()->getRequest(); $queryParams = $request->getQueryParams(); $query = $catalogProductService->query(); // возвращается объект QueryModel $query->loadFiler($queryParams) // загружаем фильтр из http запроса ->loadSort($queryParams) // загружаем параметры сортировки ->loadPagination($queryParams); // загружаем параметры пагинации $query->hasFilter(); // проверяет наличие параметров для фильтрации $query->getFilter(); // возвращает параметры для фильтрации $query->hasSort(); // проверяет наличие параметров для сортировки $query->getSort(); // возвращает параметры для сортировки $query->hasLimit(); // проверяет наличие параметров для ограничения выборки $query->getLimit(); // возвращает параметры ограничения выборки $query->getPage(); // номер страницы для пагинации $query->getOffset(); // номер элемента с которого начинается выборка $productCollection = $query->getList(); // возвращает коллекцию товаров в соответствии со сформированными параметрами выборки
分页
为了更方便地处理分页,定义了PaginationInterface接口,模块中提供了该接口的完整实现 - Pagination。该类与QueryInterface接口协同工作。
使用示例
use Bitrix\Main\Application; $catalogProductService = new CatalogProductService(); $request = Application::getInstance()->getContext()->getRequest(); $queryParams = $request->getQueryParams(); $query = $catalogProductService->query(); // возвращается объект QueryModel $query->loadFiler($queryParams) // загружаем фильтр из http запроса ->loadSort($queryParams) // загружаем параметры сортировки ->loadPagination($queryParams); // загружаем параметры пагинации $pagination = $query->getPagination(); // возвращается объект Pagination $pagination->getPage(); // номер текущей страницы $pagination->getCountPages(); // общее количество страниц $pagination-getTotalCountElements(); // общее количество элементов $pagination->getCountElements(); // количество элементов на текущей странице $pagination->getLimit(); // максимальное количество элементов на странице json_encode($pagination); // JSON представление $pagination->toArray(); // представление в виде ассоциативного массива
抓取器
在许多情况下,需要获取与相关模型关联的数据模型(根据数据库的外键或其他标准),为了简化此任务,定义了FetcherModelInterface接口,该接口有完整的实现 - FetcherModel。该类与模型服务交互,为了更简单的实现,定义了基于此类的抽象模型服务模型BaseLinkedModelService,可以基于此类定义一个用于选择带有相关模型的服务的自定义服务。
服务描述示例
use Bx\Model\BaseLinkedModelService; use Bx\Model\FetcherModel; use Bx\Model\Query; use Bx\Model\Interfaces\FileServiceInterface; class ExtendedCatalogProductService extends BaseLinkedModelService { /** * @var FileServiceInterface */ private $fileService; public function __construct(FileServiceInterface $fileService) { $this->fileService = $fileService; } protected function getFilterFields(): array { return [ // указываем разрешенные для фильтрации поля ]; } protected function getSortFields(): array { return [ // указываем разрешенные для сортировки поля ]; } protected function getLinkedFields(): array { /** * В данном методе описываются внешние связи через FetcherModelInterface * в виде ассоциативного массива */ $imageQuery = new Query(); $imageQuery->setFetchList([]); // пустой массив указывает на то что связанные модели выбирать не нужно, по-умолчанию выбираются все связанные модели $imageQuery->setSelect(['ID', 'SIZE', 'DESCRIPTION']); $imageQuery->setFilter('=CONTENT_TYPE' => ['jpg', 'png', 'gif']); $docsQuery = new Query(); $docsQuery->setFetchList([]); $docsQuery->setFilter('=CONTENT_TYPE' => ['doc', 'docx', 'pdf']); return [ 'image' => FetcherModel::initAsSingleValue( // будет выбрана одна модель $this->fileService, 'image', // ключ по которому будет доступна связанная модель 'IMAGE_ID', // внешний ключ текущей сущности 'ID', // первичный ключ связанной сущности $imageQuery // указываем доп. параметры выборки ), 'docs' => FetcherModel::initAsMultipleValue( // будет выбрана коллекция моделей $this->fileService, 'docs', 'ID', 'PRODUCT_ID', $docsQuery )->castTo(AggreageDocumentModel::cass), // выбранная коллекция будет преобразована в указанную агрегационную модель ]; } protected function getInternalList(array $params, UserContextInterface $userContext = null): ModelCollection { $list = CatalogProductTable::getList($params)->fetchAll(); return new ModelCollection($list, CatalogProduct::class); } public function getCount(array $params, UserContextInterface $userContext = null): int { $params['select'] = ['ID']; $params['count_total'] = true; $params['limit'] = 1; return $this->getList($params, $userContext)->first(); } public function getById(int $id, UserContextInterface $userContext = null): ?AbsOptimizedModel; { $params = [ 'filter' => [ '=ID' => $id, ], 'limit' => 1, ]; return $this->getList($params, $userContext)->first(); } function save(AbsOptimizedModel $model, UserContextInterface $userContext = null): Result { $data = [ 'NAME' => $model->getName(), ]; if ($model->getId() > 0) { return CatalogProductTable::update($model->getId(), $data); } $result = CatalogProductTable::add($data); if ($result->isSuccess()) { $model['ID'] = $result->getId(); } return $result; } function delete(int $id, UserContextInterface $userContext = null): Result { return CatalogProductTable::delete($id); } }
使用示例
use Bx\Model\Services\FileService; $fileService = new FileService(); $productService = new ExtendedCatalogProductService($fileService); $collection1 = $productService->getList([]); // будут выбраны все связанные модели $collection2 = $productService->getList(['fetch' => []]); // связанные модели не будут выбраны $collection3 = $productService->getList(['fetch' => ['image']]); // из связанных моделей будет выбраны только модели с ключом image $firstModel = $collection1->first(); $firstModel['image']; // Объект File $firstModel['docs']; // Объект AggreageDocumentModel
管理接口
模块中提供了用于在Bitrix的admin界面中快速显示模型列表的工具,具有过滤、搜索、创建自定义复选和单选事件的功能。
使用示例
use Bx\Model\UI\Admin\ModelGrid; require_once($_SERVER['DOCUMENT_ROOT'].'/bitrix/modules/main/include/prolog_admin_before.php'); $fileService = new FileService(); $productService = new ExtendedCatalogProductService($fileService); $grid = new ModelGrid( $productService, // указываем сервис для работы с данными 'product_list', // указываем символьный идентификатор списка сущности 'ID' // ключ свойства модели для идентификации ); /** * Указываем поля фильтрации */ $grid->addSearchFilterField('name', 'Название'); $grid->addNumericFilterField('id', 'ID'); $grid->addBooleanFilterField('active', 'Активность') ->setTrueOption('Активно', 'Y') ->setFalseOption('Не активно', 'N'); $grid->addStringFilterField('article', 'Артикул'); $grid->addDateFilterField('date_create', 'Дата создания'); $grid->addListFilterField('status', 'Статус', [ 1 => 'Новый', 2 => 'Опубликован', 3 => 'Снят с публикации', ]); /** * Указываем колонки для вывода в таблице */ $grid->addColumn('id', 'ID'); $grid->addColumn('name', 'Название'); $grid->addColumn('article', 'Артикул'); $grid->addColumn('date_create', 'Дата создания'); $grid->addCalculateColumn( 'status', function(ExtendedCatalogProduct $product) { return $product->getStatusName(); }, 'Статус' ); /** * Указываем действия над элементами */ $grid->setSingleAction('Удалить', 'delete') ->setCallback(function (int $id) use ($productService) { $productService->delete($id); }); $grid->setSingleAction('Перейти', 'redirect') ->setJs('location.href="/bitrix/admin/product_detail.php?id=#id#"'); /** * Указываем действия над элементами с условием отображения: * если callback возвращает false, то действие на элементе не отобразится */ $grid->setConditionalSingleAction('Удалить', 'delete') ->setShowConditionCallback(function (ExtendedCatalogProduct $model) { return !$model->hasValueKey('skuList') || empty($model->getValueByKey('skuList')); }) ->setCallback(function (int $id) use ($productService) { $productService->delete($id); }); /** * Указываем действия над группой элементов */ $grid->setGroupAction('Удалить', 'delete') ->useConfirm('Подтвердить') ->setCallback(function (array $ids) use ($productService) { foreach ($ids as $id) { $productService->delete((int)$id); } }); $grid->setGroupAction('Опубликовать', 'accept') ->useConfirm('Подтвердить') ->setCallback(function (array $ids) use ($productService) { $productCollection = $productService->getList([ 'filter' => [ '=ID' => $ids, ], 'fetch' => [], ]); foreach ($productCollection as $currentProduct) { $currentProduct->setStatus(2); $productService->save($currentProduct); } }); /** * Добавляем кнопку в шапку справа от фильтра (вторая и последующие добавятся как выпадающее меню) */ $grid->addAdminButtonLink('Добавить', '/bitrix/admin/product_detail.php?lang='.LANG, 'btn_new'); /** * Указываем, показывать ли кнопку экспорта в Excel (формирование excel-файла средствами битрикса) */ $grid->setShowExcelBtn(true); /** * Добавляем ссылку на строку таблицы (переход по двойному клику мышкой) * Если не задать второй аргумент title, по умолчанию будет "Перейти" * Если шаблон не подходит, можно использовать setDefaultRowLinkByCallback(callable $fnCalcLink, ?string $linkTitle = null) */ $grid->setDefaultRowLinkTemplate('/bitrix/admin/product_detail.php?id=#ID#', 'Изменить'); /** * Добавляем ссылку на строку таблицы (переход по двойному клику мышкой, альтернативный метод) */ $grid->setDefaultRowLinkByCallback(function (ExtendedCatalogProduct $product) { return '/bitrix/admin/product_detail.php?id='.$product->getId(); }, 'Изменить'); require($_SERVER['DOCUMENT_ROOT'].'/bitrix/modules/main/include/prolog_admin_after.php'); $grid->show(); // показываем собранную таблицу require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_admin.php");
基本服务
模块中实现了几个服务。
- FileService - 允许处理b_file表中的文件。
- UserService - 允许处理b_user表中的用户。
用户服务
除了标准的服务模型方法之外,还提供了以下方法
- login(string $login, string $password): ?UserContextInterface - 根据用户名和密码进行认证
- isAuthorized(): bool - 检查认证状态
- saveExtendedData(User $user, string ...$keyListForSave): Result - 保存用户和任意数据集
- updatePassword(User $user, string $password): Result - 更新用户密码
- getCurrentUser(): ?UserContextInterface - 获取当前用户,实际上返回一个UserContextInterface对象,可以从该对象中获取用户模型。
如方法描述所示,在许多情况下,服务返回的不是模型本身,而是模型上下文 - UserContextInterface,从上下文中可以请求用户的ID或模型本身 - getUserId或getUser。在此接口中,有两个有趣的方法
- setAccessStrategy(AccessStrategyInterface $accessStrategy) - 允许指定任意的访问策略
- hasAccessOperation(int $operationId): bool - 检查用户是否可以按照指定的策略执行任意操作
使用示例
use Bx\Model\Services\UserService; $userService = new UserService(); $userContext = $userService->login('admin@mail.xyz', 'mypassword'); $isAuthorized = $userService->isAuthorized(); $userId = $userContext->getId(); $user = $userContext->getUser(); $user->getId(); $user->getName(); $userContext->setAccessStrategy(new SimpleAccessStrategy()); $userContext->hasAccessOperation(OperationListInterface::CAN_DELETE_FILES);
文件服务
除了标准的服务模型方法之外,还提供了以下方法
- saveFiles(string $baseDir, string ...$filePaths): ModelCollection - 从指定的url/绝对路径保存文件并返回集合
- function saveUploadFiles(string $baseDir, UploadedFileInterface ...$files): ModelCollection - 从指定的UploadedFileInterface对象保存文件并返回集合
- saveFile(string $baseDir, string $filePath, ?string $name = null, ?string $description = null): ?File - 保存文件,可以指定替代名称和描述
- replaceFile(int $fileId, string $baseDir, string $filePath, ?string $name = null, ?string $description = null): ?File - 删除指定ID的文件并保存新的文件
使用示例
use Bx\Model\Services\FileService; $fileService = new FileService(); $fileCollection = $fileService->saveFiles( 'test_dir', 'https://some-site.xyz/image1.jpg', 'https://some-site.xyz/image2.jpg', 'https://some-site.xyz/image3.jpg' ); $savedFile = $fileService->saveFile('test_dir', 'https://some-site.xyz/image1.jpg', 'new_name1.jpg'); $savedFile = $fileService->replaceFile( $savedFile->getId(), 'test_dir', 'https://some-site.xyz/image2.jpg', 'new_name2.jpg' );